# reentrancy is possible whenever external calls precede state updates Four types: single-function (re-entering the same function), cross-function (calling another function sharing state), cross-contract (exploiting shared state across boundaries), and [[read-only reentrancy exploits view functions to cause other protocols to read inconsistent state|read-only reentrancy]] (targeting view functions other protocols depend on). The root mechanism is identical: an external call transfers execution before state is updated, allowing the callee to operate on stale values. Since [[checks-effects-interactions pattern prevents reentrancy by updating state before external calls]], the defense is well-understood. Critically, `nonReentrant` mutex guards do NOT prevent read-only reentrancy, which targets view functions that do not modify state. Named audit examples: In the **Hypercerts audit** (Cyfrin), `_splitValue()` called `ERC1155._mintBatch()` before writing decreased `valueLeft`. An attacker hooked `onERC1155BatchReceived()` to re-enter before the decrement, creating unlimited token fractions ([[ERC-1155 mintBatch callback creates reentrancy before valueLeft storage write enabling unlimited token fraction splitting]]). In the **Papr contest**, `ERC721.safeTransferFrom()` returned an NFT before checking the vault's debt constraint; an attacker hooked `onERC721Received()` to manipulate collateral. Both cases: library function hides the external call, state update comes after. --- Relevant Notes: - [[read-only reentrancy exploits view functions to cause other protocols to read inconsistent state]]: a variant targeting view functions - [[checks-effects-interactions pattern prevents reentrancy by updating state before external calls]]: the primary defense pattern - [[Uniswap V4 hooks introduce arbitrary external code execution into pool swap paths creating new reentrancy attack surfaces]]: hooks architecture creates new reentrancy vectors in AMM swap paths - [[vyper compiler reentrancy lock storage slot bug broke cross-function reentrancy protection in versions 0.2.15 through 0.3.0]]: compiler bug that silently broke cross-function reentrancy protection, weaponizing this fundamental vulnerability - [[vyper nonreentrant decorator uses a single global storage lock eliminating cross-function reentrancy by design in versions 0.4.0 and later]]: language-level defense that addresses cross-function reentrancy structurally - [[the Fei Protocol Rari Capital exploit demonstrated that incomplete reentrancy guard coverage enables cross-function reentrancy through unprotected state-sharing functions]]: $80M cross-function reentrancy where incomplete mutex coverage left exitMarket() unguarded - [[the Curve Finance Vyper compiler exploit proved that source-level reentrancy guards can be silently broken by compiler bugs invisible to auditors]]: $50-70M compiler-level reentrancy where correct source-level guards produced broken bytecode - [[ERC-721 safeTransferFrom and ERC-777 tokensReceived callbacks create reentrancy entry points in any protocol handling these token standards]]: token standard callbacks as hidden external calls that create reentrancy entry points - [[ERC-1155 mintBatch callback creates reentrancy before valueLeft storage write enabling unlimited token fraction splitting]]: named Hypercerts audit example: ERC-1155 mintBatch as a hidden external call exploited in production - [[the dForce exploit demonstrated cross-contract read-only reentrancy where stale Curve get_virtual_price values enabled artificial liquidation of lending positions]]: $3.7M cross-contract read-only reentrancy via stale Curve oracle values - [[reentrancy attacks have caused over 500 million dollars in cumulative losses across 70 plus incidents from 2016 through 2025 despite being the most well-known smart contract vulnerability]]: cumulative evidence that this root cause persists across 7 variant types despite universal awareness Topics: - [[vulnerability-patterns]]