Lesson 22 of 25
Reentrancy Attacks
How the DAO hack happened and how to prevent reentrancy in your code.
AdvancedThe 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);
}
}