Loading...
The DAO hack drained $60M in 2016 by exploiting a reentrancy bug. You WILL write this class of bug unless you know the pattern.
When contract A calls contract B (e.g., sends ETH to B), B can call BACK INTO A before A finishes. If A didn't update its state first, B can re-execute the same withdrawal multiple times.
soliditycontract Vulnerable { mapping(address => uint256) public balances; function withdraw() public { uint256 bal = balances[msg.sender]; require(bal > 0); // ❌ EXTERNAL CALL BEFORE STATE UPDATE (bool ok,) = msg.sender.call{value: bal}(""); require(ok); balances[msg.sender] = 0; // too late } }
An attacker contract's receive() function re-enters withdraw() before balances[msg.sender] = 0 runs. Drains the whole contract.
solidityfunction withdraw() public { uint256 bal = balances[msg.sender]; require(bal > 0); balances[msg.sender] = 0; // ✅ state update FIRST (bool ok,) = msg.sender.call{value: bal}(""); require(ok); }
solidityimport "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; contract Safe is ReentrancyGuard { function withdraw() public nonReentrant { // attack is impossible — nonReentrant flips a flag before and after } }
Industry standard today: always use ReentrancyGuard + checks-effects-interactions on any function that makes an external call.
Fix the vulnerable function below. Use the checks-effects-interactions pattern.