🏠 Home
Beginner
01 — Introduction to Solidity 02 — Setting Up Your Environment 03 — Your First Smart Contract 04 — Data Types & Variables 05 — Functions & Visibility 06 — Control Flow 07 — Arrays & Mappings 08 — Structs & Enums
Intermediate
09 — Events & Logging 10 — Modifiers 11 — Inheritance 12 — Interfaces & Abstract Contracts 13 — Error Handling 14 — Ether & Wei 15 — Payable Functions 16 — msg.sender & msg.value 17 — Storage vs Memory vs Stack
Advanced
18 — Gas Optimization 19 — ERC-20 Tokens 20 — ERC-721 NFT Standard 21 — Contract Security 22 — Reentrancy Attacks 23 — Oracles & Chainlink 24 — Upgradeable Contracts 25 — Deploying to Mainnet
SolidityMaster / Lesson 21
Lesson 21 of 25

Contract Security

Common vulnerabilities, audit checklists, and defensive programming practices.

Advanced

Integer Overflow (Pre-0.8)

Before Solidity 0.8, arithmetic silently overflowed. Now it reverts by default. Use unchecked blocks ONLY when you are certain overflow cannot occur.
Solidity
// In Solidity < 0.8: uint8(255) + 1 == 0  (overflow!)
// In Solidity >= 0.8: reverts automatically

// Using unchecked for gas savings when safe:
function safeIncrement(uint256 i) public pure returns (uint256) {
    unchecked { return i + 1; } // safe — i cannot be uint256 max in context
}

Access Control Vulnerabilities

Missing or incorrect access control is the #1 cause of hacks. Always verify who can call sensitive functions.
Solidity
// ❌ VULNERABLE — anyone can become owner!
contract VulnerableOwnable {
    address public owner;
    function setOwner(address newOwner) public {
        owner = newOwner; // no access check!
    }
}

// ✅ SECURE
contract SecureOwnable {
    address public owner;
    event OwnershipTransferred(address indexed prev, address indexed next);

    constructor() { owner = msg.sender; }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0), "Zero address");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }
}

Front-Running

Miners (and MEV bots) can see pending transactions and insert their own before yours. Avoid designs where the order of transactions gives an unfair advantage.
Solidity
// ❌ Vulnerable to front-running
// An attacker sees your commitHash and submits the same answer first
contract GuessingGame {
    bytes32 public commitHash;
    function guess(string memory answer) public {
        if (keccak256(bytes(answer)) == commitHash) {
            payable(msg.sender).transfer(address(this).balance);
        }
    }
}

// ✅ Commit-reveal scheme
contract SafeGame {
    mapping(address => bytes32) public commits;

    // Phase 1: commit a hash of (answer + salt)
    function commit(bytes32 hash) public {
        commits[msg.sender] = hash;
    }

    // Phase 2: reveal after commit period
    function reveal(string memory answer, bytes32 salt) public {
        bytes32 hash = keccak256(abi.encodePacked(answer, salt));
        require(commits[msg.sender] == hash, "Hash mismatch");
        // process answer
    }
}