What Is a Re-entrancy Attack?
A re-entrancy attack smart contract definition starts with a deceptively simple premise: a contract sends ETH to an external address before updating its own internal state. If that external address is a malicious contract with a fallback function, it can call back into the original contract — again and again — until the vault is empty. The victim contract never "sees" the repeated withdrawals coming because it hasn't recorded the first one yet.
Think of it like a broken ATM that dispenses cash before logging the transaction. You hit "withdraw $100", the machine hands you the bills, and before it writes the debit to your account, you hit the button again. Repeat until the machine is empty.
The DAO Hack: Where It All Started
June 2016. The DAO — a decentralized venture fund holding roughly 3.6 million ETH at the time — got drained through a re-entrancy exploit. The attacker found a splitDAO function that sent ETH to a child DAO before zeroing out the sender's balance. Their malicious contract's fallback function kept re-calling splitDAO, siphoning funds in a loop.
The Ethereum community's response — a hard fork to reverse the theft — remains controversial to this day and directly produced Ethereum Classic. No other smart contract vulnerability has reshaped a blockchain's entire history quite like this one.
How the Attack Works, Step by Step
- Attacker deploys a malicious contract with a fallback function designed to call back into the target.
- Attacker deposits funds into the vulnerable protocol to establish a legitimate balance.
- Attacker calls
withdraw()on the target contract. - Target sends ETH to the attacker's contract address.
- Fallback function triggers automatically, re-calling
withdraw()before the target updates its balance mapping. - Target checks the attacker's balance — it still shows the original deposit, so the withdrawal passes validation again.
- Steps 4–6 repeat until the target's ETH is exhausted or gas runs out.
The vulnerable code pattern looks something like this:
// VULNERABLE — DO NOT USE
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}(""); // external call FIRST
require(success);
balances[msg.sender] -= amount; // state update LAST — too late
}
The Fix: Checks-Effects-Interactions
The Solidity community codified the solution as the Checks-Effects-Interactions (CEI) pattern. Update state before making any external calls. Always.
// SAFE — CEI pattern applied
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount); // Check
balances[msg.sender] -= amount; // Effect (state update first)
(bool success, ) = msg.sender.call{value: amount}(""); // Interaction last
require(success);
}
A second line of defense is the ReentrancyGuard — OpenZeppelin's implementation uses a simple mutex (lock) that prevents a function from being called while it's already executing. It's two lines of code that have saved hundreds of millions of dollars in protocols that correctly implement it. OpenZeppelin's ReentrancyGuard documentation is the canonical reference.
Cross-Function Re-entrancy: The Underrated Variant
Most tutorials get this wrong by only showing single-function re-entrancy. Cross-function re-entrancy is subtler and arguably more dangerous. An attacker exploits shared state between two functions — say, withdraw() and transfer() — where neither function alone is vulnerable, but together they create a window.
Warning: Even with CEI applied to individual functions, shared mutable state across functions in the same contract can still create re-entrancy windows. Audit every function that reads state another function writes.
Cross-function attacks are harder to detect in manual code reviews, which is why automated static analysis tools and formal verification matter. DeFi protocols with complex multi-function interactions — lending markets, yield aggregators, DEX routers — face elevated exposure.
Read-Only Re-entrancy: The 2023 Evolution
In 2023, a more sophisticated variant emerged: read-only re-entrancy. Here, the attacker doesn't drain funds directly. Instead, they call back into a view function during execution to read temporarily incorrect state — for example, a price that hasn't been updated yet — and use that stale data to exploit a different protocol. Curve Finance's price oracle was implicated in multiple incidents stemming from this pattern.
The attack surface expanded from "one vulnerable contract" to "any protocol that reads state from a contract mid-execution." That's a genuinely difficult problem to solve. Protocols relying on an oracle network for pricing data are especially exposed when that data can be read at an inconsistent moment mid-execution.
For a broader look at how these vulnerabilities compound with other attack vectors, Smart Contract Security Vulnerabilities in DeFi Protocols covers the full threat landscape.
Myth vs Reality
| Myth | Reality |
|---|---|
| Re-entrancy only affects ETH transfers | ERC-20 transferFrom hooks (ERC-777) have enabled token-based re-entrancy too |
| ReentrancyGuard makes you fully safe | Cross-function and read-only variants can bypass standard guards |
| Only old contracts are vulnerable | New protocols shipping in 2024–2025 have still fallen to this attack |
| Audits catch all re-entrancy bugs | Complex cross-contract interaction re-entrancy regularly slips past audits |
Why It Still Matters in 2026
Ethereum's EIP-1884 raised the gas cost of SLOAD, which incidentally made some re-entrancy attacks more expensive — but didn't eliminate them. Cross-chain bridges, which often manage large ETH or token balances and interact with multiple external contracts in sequence, are a particularly high-risk category. Several bridge hacks have featured re-entrancy as a contributing factor alongside other vulnerabilities — for a deeper look at how bridge architectures vary in their exposure, see Cross-Chain Bridge Security Analysis: Multi-Sig vs Optimistic vs Light Client.
I've seen developers dismiss re-entrancy as "a solved problem" after adding a single mutex. That confidence is unwarranted. The attack surface evolves as smart contract complexity grows, and cross-function and read-only variants don't care about your nonReentrant modifier if you've applied it to the wrong functions.
Any serious protocol deployment should include independent smart contract audits with explicit re-entrancy test suites — not just automated scanners, but manual adversarial review focused on state transition ordering across every external call. Protocols with elevated total value locked face the highest incentive for attackers to invest time in finding these edge cases.