A reentrancy attack exploits a smart contract that sends ETH or calls an external contract before updating its own state. The attacker's contract receives the ETH, immediately calls back into the vulnerable function (re-entering it), and withdraws again before the original transaction finishes updating the balance. This loop drains the contract's funds repeatedly in a single transaction.
What Is a Reentrancy Attack (Plain Language)?
3 min read
The short version
Imagine a bank teller who hands you cash before marking the withdrawal in the ledger. You grab the cash, run back to the same window, and say "I want to withdraw again." The teller checks the ledger, it still shows your full balance (not yet updated), and hands you more cash. You repeat until the vault is empty. That is reentrancy: exploiting the gap between "send funds" and "update the record."
How It Works
The vulnerable pattern: function withdraw() { require(balances[msg.sender] > 0); (bool sent,) = msg.sender.call{value: balances[msg.sender]}(""); require(sent); balances[msg.sender] = 0; }. The bug: the balance is set to zero AFTER sending ETH. If msg.sender is a contract with a receive() function, that receive() function can call withdraw() again. The balance check passes (still non-zero), ETH is sent again, triggering receive() again, looping until the contract is drained. The fix (Checks-Effects-Interactions pattern): update state BEFORE external calls: balances[msg.sender] = 0; then send. Or use a reentrancy guard (mutex lock). The most famous reentrancy: The DAO hack (2016), $60M drained, leading to the Ethereum/Ethereum Classic fork.
The DAO hack simplified
June 17, 2016 (block 1,718,497): The DAO contract held $150M in ETH. Its splitDAO function sent ETH to the caller before updating their internal balance. An attacker deployed a contract whose fallback function immediately called splitDAO again. The loop: (1) Attacker calls splitDAO, contract sends 3.5M ETH to attacker. (2) Attacker's fallback triggers, calls splitDAO again. (3) Contract checks balance, not yet updated, still shows full amount, sends another 3.5M ETH. (4) Repeat. Total drained: ~$60M (3.6M ETH). The Ethereum community hard-forked to reverse the theft, creating ETH (with rollback) and ETC (without). This single bug changed Ethereum's history.
What People Get Wrong
Reentrancy only affects ETH transfers
Any external call (token transfers via transferFrom, callbacks in ERC-777, flash loan callbacks) can enable reentrancy. The vulnerability is about control flow, not specifically ETH. Read-only reentrancy (re-entering a view function mid-state-change) is a newer variant affecting protocols that rely on real-time state reads.
Modern contracts are immune to reentrancy
The pattern is well-known and preventable, but new developers still make this mistake, and novel variants (cross-function reentrancy, read-only reentrancy) continue to cause exploits. Curve Finance was exploited in 2023 via a Vyper compiler reentrancy bug.
Only Solidity contracts are vulnerable
Any language that compiles to EVM bytecode and makes external calls before updating state is vulnerable. Vyper, Huff, and assembly-level contracts can all have reentrancy bugs if coded incorrectly.
Keep Reading
Sources & Further Reading
- SWC-107: Reentrancy
Smart Contract Weakness Classification entry for reentrancy vulnerabilities
- OpenZeppelin ReentrancyGuard
The standard reentrancy protection pattern used in production contracts
Questions People Also Ask
- How do I protect my contract from reentrancy?
- Three methods: (1) Checks-Effects-Interactions pattern, update state before making external calls. (2) Reentrancy guard, a boolean mutex that locks the function during execution. (3) Use transfer() or send() instead of call() for ETH (limited gas, but discouraged in modern Solidity due to gas cost changes). The CEI pattern is the primary defense.
- Can reentrancy steal my tokens from MetaMask?
- No. Reentrancy is a smart contract vulnerability, it affects contracts that hold pooled funds (DeFi protocols, DAOs), not individual user wallets. Your EOA (MetaMask) cannot be reentrancy-attacked because it has no code to re-enter.
- Has reentrancy been fixed in newer Solidity versions?
- The language has not made reentrancy impossible, the issue is a logic pattern, not a compiler bug (except the 2023 Vyper case). Solidity provides tools (ReentrancyGuard from OpenZeppelin) and documentation, but developers must apply them correctly. It remains the developer's responsibility.