Skip to content
← Intel Brief
Onchain · Audit

Reentrancy in 2026: the bugs that refuse to die

Read-only, cross-function, and callback-chain reentrancy still slip past scanners. A field note on what the call graph actually looks like in production DeFi.

Abstract smart contract call loops representing a reentrancy vulnerability
AN3 · Offensive Research · · 10 min read

Reentrancy did not die with The DAO. It changed shape.

The textbook pattern (external call before state update, then a recursive drain) is still in every junior auditor's checklist and in every static analyzer's default ruleset. Production protocols mostly stopped shipping that bug in its original form. What they ship instead is harder to name in a one-line finding: read-only reentrancy through view functions, cross-function reentrancy across a protocol's own surface, and token callbacks that re-enter mid-transfer.

This note is for teams that think "we use OpenZeppelin ReentrancyGuard, so we're done," and for reviewers who stop when Slither is green.

What "reentrancy" actually means now

Classically, reentrancy meant an attacker contract calling back into the victim before the victim finished updating balances. The invariant that broke was usually accounting: credits and debits got out of order.

In 2026 the same underlying failure shows up as:

  • Control-flow reentrancy: execution returns into a mutable path before state settles
  • View / read-only reentrancy: a view function reports stale or inconsistent state that another protocol treats as ground truth
  • Cross-function / cross-contract reentrancy: the callback lands on a different entrypoint than the one that made the external call
  • Callback-token reentrancy: ERC-777, ERC-1155, or custom hooks fire during transfer and re-enter the protocol

If your threat model only covers "withdraw calls an untrusted address," you are protecting a 2016 attack surface.

The patterns we still find

### 1. Cross-function reentrancy

State is updated in function A. The external call happens. The callback hits function B, which still trusts the old world.

Common shapes:

  • Vault deposit / withdraw pair that share a price or share-supply read
  • Lending pool that updates collateral in one path and liquidates in another
  • Router that marks a swap "in flight" in memory but not in storage the callback can see

Guards on a single function do not help if the sibling is unprotected and shares the invariant. The question is not "does this function have a nonReentrant modifier?" It is "what can run while this external call is outstanding?"

### 2. Read-only reentrancy

No funds move in the reentrant call. A view function is enough.

Classic case: a Curve-style pool updates balances, then makes an external call, then finishes accounting. During the gap, get_virtual_price or a similar view still reflects the intermediate state. A lending market, stablecoin, or aggregator that samples that price mid-callback prices collateral wrong. Liquidations and minting follow the bad number.

Scanners miss this when they only track value-moving calls. The exploit does not steal from the pool directly. It steals from whoever trusted the pool's view as an oracle.

Audit questions that matter: which views are consumed by other protocols, and are those views safe during every external call in the callee's control flow?

### 3. ERC-777 and other transfer hooks

tokensReceived (and cousins) run mid-transfer, before the sender's transaction finishes. If your protocol:

  • Credits shares before the transfer completes
  • Relies on balanceOf snapshots across a hook
  • Assumes ERC-20 semantics for every token it allowlists

…then a malicious or merely hook-enabled token is a reentrancy gadget you invited in.

Allowlists that only check "is this a known Stablecoin address" without checking transfer semantics are not allowlists. They are invitations.

### 4. Cross-contract composition

Protocol A calls Protocol B. B calls back into A, or into C that A also trusts. Each contract may look safe in isolation. The composed system is not.

This is where "we audited each piece" fails. Reentrancy across a DeFi stack is a system property. Review the call graph of the product, not only the bytecode of one repo.

Why scanners still miss the variants

Automated tools are good at:

  • External call before storage write in the same function
  • Missing nonReentrant on an obvious withdraw path

They are weak at:

  • View functions used as oracles by unknown consumers
  • Shared invariants across functions without a shared lock
  • Token hooks whose presence depends on runtime allowlists
  • Multi-contract paths that only exist after deployment wiring

If the finding requires modeling "who calls whom with what state mid-flight," a human still has to draw that graph. Treat scanner output as coverage of the textbook case, not as a clearance.

A practical review boundary

When we scope reentrancy work on a protocol, we force the diagram past the modifier list:

  1. External call sites — every call, transfer, safeTransfer, hook, and callback-capable token path
  2. State at risk during the call — balances, shares, prices, flags, authorizations
  3. Re-enterable surface — which public/external functions remain callable, including views that other systems read
  4. Locks and their scope — per-function vs protocol-wide; does the lock cover siblings and callbacks?
  5. Token assumptions — ERC-20 only, or hooks allowed; what happens if an allowlisted token upgrades its behavior
  6. Downstream consumers — who prices off your views, and are they in your blast radius write-up

If any row is "the integrator handles that," it is still in your incident report when the number is wrong.

Closing note

Checks-effects-interactions and a reentrancy guard are necessary. They are not sufficient.

The bugs that still clear eight-figure amounts are usually not "forgot nonReentrant on withdraw." They are inconsistent state visible through a view, a sibling function left unlocked, or a token hook nobody modeled. Teams that review the full call graph and the consumers of their views find those gaps before the mempool does.


AN3 Intel · field notes on smart contract control-flow risk.