🏠 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 22
Lesson 22 of 25

Reentrancy Attacks

How the DAO hack happened and how to prevent reentrancy in your code.

Advanced

The DAO Hack Explained

In 2016, the DAO contract lost $60M in ETH due to a reentrancy vulnerability. The attack exploited the pattern of sending ETH before updating state — the attacker's fallback function called back into withdraw() repeatedly before the balance was updated.
Solidity
// ❌ VULNERABLE TO REENTRANCY
contract VulnerableBank {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

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

        // ❌ DANGER: ETH sent before state update
        (bool ok,) = msg.sender.call{value: amount}("");
        require(ok, "Transfer failed");

        // Attacker's fallback() calls withdraw() again BEFORE this line!
        balances[msg.sender] = 0;  // Too late — already drained!
    }
}

The Fix — Checks-Effects-Interactions

The CEI pattern is the fundamental defense: update state BEFORE making external calls.
Solidity
// ✅ SAFE — Checks-Effects-Interactions pattern
contract SafeBank {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() public {
        // 1. CHECKS — validate conditions
        uint256 amount = balances[msg.sender];
        require(amount > 0, "Nothing to withdraw");

        // 2. EFFECTS — update state FIRST
        balances[msg.sender] = 0;

        // 3. INTERACTIONS — external call last
        (bool ok,) = msg.sender.call{value: amount}("");
        require(ok, "Transfer failed");
    }
}

ReentrancyGuard Mutex

For complex functions, use a reentrancy guard mutex as an additional layer of protection.
Solidity
contract ReentrancyGuard {
    bool private _locked;

    modifier nonReentrant() {
        require(!_locked, "Reentrant call");
        _locked = true;
        _;
        _locked = false;
    }
}

contract SecureBank is ReentrancyGuard {
    mapping(address => uint256) public balances;

    function withdraw() public nonReentrant {
        uint256 amount = balances[msg.sender];
        require(amount > 0);
        balances[msg.sender] = 0;
        (bool ok,) = msg.sender.call{value: amount}("");
        require(ok);
    }
}