Security in Solidity

Common Vulnerabilities

Beginner Level

Smart contracts are immutable once deployed, so vulnerabilities can lead to loss of funds or system failure. Understanding common vulnerabilities is crucial for writing secure contracts.

Common vulnerabilities

1) Re-entrancy attack

Definition:

Occurs when a contract calls an external contract, and the external contract re-enters the calling contract before the first execution finishes, potentially draining funds.

Example:


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Vulnerable {
    mapping(address => uint256) public balances;

    function withdraw() public {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "Insufficient balance");

        (bool success, ) = msg.sender.call{value: amount}(""); // Vulnerable
        require(success, "Transfer failed");

        balances[msg.sender] = 0; // State updated after sending Ether
    }
}

Solution:

  • Update state before making external calls.
  • Use ReentrancyGuard from OpenZeppelin.

balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");

2) Integer overflow / underflow

Definition:

Occurs when arithmetic exceeds the maximum or minimum value of a variable type.

Example:


uint8 x = 255;
x += 1; // Overflow: x becomes 0

Solution:

  • Use Solidity 0.8.x (built-in overflow and underflow checks).
  • For older versions, use the SafeMath library from OpenZeppelin.

using SafeMath for uint256;
uint256 result = a.add(b);

3) Front-running

Definition:

An attacker observes pending transactions and submits a transaction with higher gas to execute before the original transaction, exploiting transaction ordering.

Example:

  • Bidding or trading contracts are susceptible if the highest bidder is updated after external calls.

Solution:

  • Use commit-reveal schemes.
  • Avoid trusting transaction order.

4) Denial of service (DoS)

Definition:

Prevents users from executing contract functions by exploiting logic flaws or gas exhaustion.

Example:

  • A contract iterates over an array of users; if the array grows too large, the function runs out of gas.

Solution:

  • Avoid unbounded loops on storage arrays.
  • Use mappings instead of arrays for critical logic.

5) Tx.origin attack

Definition:

Attackers trick users into calling malicious contracts by using tx.origin for authentication instead of msg.sender.

Example:


if (tx.origin == owner) {
    // Grants access, unsafe
}

Solution:

  • Always use msg.sender for authorization, never tx.origin.

Best practices for secure coding

  • Use the latest Solidity version – includes built-in security checks.
  • Mark functions payable carefully to avoid accidental Ether transfers.
  • Use require and assert for validations.
  • Avoid unbounded loops to prevent gas exhaustion attacks.
  • Check external calls carefully, especially call and delegatecall.
  • Restrict access with modifiers such as onlyOwner.
  • Use OpenZeppelin libraries for trusted, audited implementations.
  • Write unit tests and perform audits before deployment.

Using OpenZeppelin contracts

OpenZeppelin is a widely used library of audited, reusable smart contract components. It provides:

  • ERC20, ERC721, and ERC1155 token standards.
  • Access control (Ownable, AccessControl).
  • Security modules (ReentrancyGuard, SafeMath).
  • Upgradeable contract patterns.

Example: Secure ERC20 token


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    constructor() ERC20("MyToken", "MTK") {
        _mint(msg.sender, 1000 * 10**18);
    }
}

Explanation:

  • ERC20 provides the standard token implementation.
  • Ownable adds ownership-based access control.
  • Reduces the risk of implementing insecure token logic from scratch.

Summary table

Vulnerability Description / Fix
Re-entrancy External calls re-enter contract / Use ReentrancyGuard, update state first
Integer overflow / underflow Arithmetic exceeds type limits / Use Solidity >= 0.8 or SafeMath
Front-running Transaction order exploited / Use commit-reveal, improve logic
Denial of service (DoS) Functions fail due to gas limits / Avoid unbounded loops, use mappings
Tx.origin attack Using tx.origin for auth / Always use msg.sender
Best practices Latest Solidity, modifiers, require/assert, OpenZeppelin
OpenZeppelin contracts Audited libraries for tokens, access control, and security

Continue Learning

Explore more topics in Solidity or browse other tutorials.