Reentrancy attack

On this page 7

A reentrancy attack is an exploit against smart contracts in which a malicious contract calls back into the victim contract before the victim has finished its first execution, typically before it updates balances or other state. Because the victim still sees the old state, the attacker can repeat an action such as a withdrawal many times within a single transaction.

Reentrancy is one of the oldest and most expensive classes of vulnerability in EVM-based smart contracts. It led to the loss of millions of ether from The DAO in 2016 and the resulting Ethereum hard fork, and variants still appear in audits today because external calls hand control to untrusted code.

How a reentrancy attack works

On the Ethereum Virtual Machine, sending ether or calling another contract transfers execution control to that contract. If the recipient is a contract, its receive or fallback function runs automatically, and it can call the original contract again while the first call is still on the stack.

The classic vulnerable pattern is a withdraw function that sends funds first and sets the balance to zero afterwards. The attacker deposits a small amount, calls withdraw, and in its fallback function calls withdraw again. Each nested call passes the balance check because the state update has not happened yet, so the loop continues until the contract is empty or the gas runs out.

The sequence below shows a single-function attack step by step.

StepVictim contractAttacker contract
1Records a deposit of 1 ETHDeposits 1 ETH
2Checks balance, sends 1 ETHCalls withdraw
3Balance still shows 1 ETHFallback re-enters withdraw
4Sends another 1 ETHRepeats until funds are gone
5Sets balance to zero too lateKeeps the drained ether
Steps of a classic reentrancy exploit

Types of reentrancy

Reentrancy is broader than one function calling itself. Modern exploits often re-enter through a different function or even a different contract that shares state with the victim.

VariantHow the attacker re-entersTypical target
Single-functionCalls the same function againSimple withdraw logic
Cross-functionCalls another function sharing the same stateTransfer and withdraw pairs
Cross-contractUses a second contract reading stale shared stateMulti-contract protocols
Read-only reentrancyCalls a view function during an inconsistent statePrice oracles and lending markets
Token hook reentrancyUses callbacks in token standardsContracts accepting ERC-777 or NFT transfers
Main reentrancy variants

Read-only reentrancy is especially subtle. The attacked contract itself may lose nothing, but another protocol reads a temporarily wrong price or balance from it mid-transaction and makes a bad lending or liquidation decision. Token standards that call the recipient, such as ERC-777 hooks or the onERC721Received callback, create hidden external calls that developers often overlook.

Defenses and secure coding patterns

The primary defense is the checks-effects-interactions pattern: first validate inputs, then update all internal state, and only then make external calls. If the balance is set to zero before the ether leaves, a nested call finds nothing to withdraw.

A second layer is a reentrancy guard, a mutex flag that makes a function revert if it is entered again before it finishes. Widely used libraries such as OpenZeppelin provide a nonReentrant modifier, and since the Cancun upgrade transient storage allows cheaper guards that reset automatically at the end of the transaction.

DefenseWhat it preventsLimitation
Checks-effects-interactionsStale state during external callsHard to apply across many contracts
Reentrancy guardRe-entering protected functionsDoes not cover view functions or other contracts
Pull paymentsPush-style transfers inside loopsUsers must claim funds themselves
Gas-limited transfersComplex fallback logicNo longer reliable after gas cost changes
Formal verificationWhole classes of state violationsCostly and needs precise specifications
Common defenses and their limits

Relying on the 2,300 gas stipend of transfer and send was once common advice. It is now discouraged because gas costs of opcodes have changed in network upgrades, which can break legitimate recipients and gives no guarantee of safety in the future.

Reentrancy is a logic flaw inside one transaction, which distinguishes it from attacks that exploit transaction ordering or external data.

RiskRoot causeMain mitigation
Reentrancy attackExternal call before state updateChecks-effects-interactions and guards
Front-running and MEVVisible pending transactionsPrivate order flow, commit-reveal
Oracle manipulationPrice from a thin or spot sourceTime-weighted or decentralized oracles
Access control flawMissing permission checksRole management and audits
Reentrancy next to neighboring attack classes

In practice these risks combine. Attackers often use flash loans to fund a reentrancy exploit or pair read-only reentrancy with oracle manipulation, so a smart contract audit evaluates them together rather than one by one.

Detection, testing and common mistakes

Static analysis tools flag state writes after external calls, and fuzzing frameworks such as those integrated with Hardhat or Foundry simulate malicious callers. Even so, many incidents involve code that passed automated checks, because the vulnerable path crosses contracts or depends on unusual tokens.

  • Guarding only one function while a sibling function updates the same state unprotected.
  • Trusting view functions of external protocols during a transaction that is still in progress.
  • Ignoring token callbacks when integrating NFT or hook-enabled token standards.
  • Trusting compiler-provided locks without testing; a 2023 bug in certain Vyper versions broke reentrancy locks and caused large losses.

A plain-language summary

Picture a cash machine that pays out before it updates the account. A clever thief presses the button again and again. Each press lands before the balance changes. The thief leaves with far more than was ever deposited.

Smart contracts can fail in the same way. The fix is easy to state: update the records first, pay out last. Then lock the door so nobody can come back in until the first request is done.

An example from practice

A team building a small lending protocol prepares for launch. During the smart contract audit, a reviewer notices that the withdrawCollateral function sends ether to the user and reduces the stored collateral afterwards. A separate function, borrow, reads the same collateral value without a guard.

The auditor writes a test in which an attacker contract withdraws collateral and, from its fallback function, calls borrow against the collateral that has not yet been reduced. In the test environment, an attacker with 10 ETH of collateral walks away with the collateral plus a loan of about 7 ETH that is no longer backed.

The team reorders the logic to follow checks-effects-interactions, adds a shared reentrancy guard to both functions and repeats the test. The exploit now reverts, and the issue is fixed before any user funds are at risk.