CreatorFetch logo
Back to Articles
Jun 16, 2026, 9:56 PM

How Smart Contract Reentrancy Attacks Work: A Technical Breakdown with Real Exploit Case Studies

How Smart Contract Reentrancy Attacks Work: A Technical Breakdown with Real Exploit Case Studies

How Smart Contract Reentrancy Attacks Actually Work

Reentrancy is the bug that refuses to die. It killed The DAO in 2016. It drained Cream Finance for $130 million in 2021. It walked off with Fei Protocol's $80 million in 2022. And it still shows up in audit reports today.

The reason it persists isn't that developers are lazy. It's that the EVM's execution model makes the attack pattern feel almost natural, and every new DeFi primitive (flash loans, cross-function calls, read-only views) opens a fresh variant of the same wound.

If you build, audit, or invest in smart contracts, you should understand this class of bug at the opcode level, not as a pattern to memorize off a flashcard.

Why the bug exists at all

Solidity contracts can call other contracts. When they do, execution control transfers to the callee. Caller pauses, callee runs whatever it wants, and only then does control return.

That's the entire surface area.

The classic vulnerable pattern:

function withdraw() public {
  uint256 balance = balances[msg.sender];
  require(balance > 0);
  (bool success, ) = msg.sender.call{value: balance}("");
  require(success);
  balances[msg.sender] = 0;
}

Read it slowly. The contract sends ETH before updating the balance. When msg.sender is a malicious contract, its fallback fires the instant it receives ETH. That fallback calls withdraw() again. Balance hasn't been zeroed. Check passes. ETH goes out. Again. Until the contract is empty or gas runs out.

Fix is checks-effects-interactions, which any junior auditor can recite half-asleep. State first, external call last. Or a reentrancy guard, a mutex that flips a boolean before the call and back after. Both work. Neither is universally applied. That's the whole story of the last nine years, really.

2016, The DAO

The DAO held about 14% of all ETH in circulation. Its splitDAO function let token holders withdraw their share, and the withdrawal logic transferred ETH before updating internal accounting. An attacker deployed a contract whose fallback recursively called splitDAO. Over a few hours, roughly 3.6 million ETH was siphoned out.

The Ethereum community hard-forked to reverse it. That's why Ethereum Classic exists. A single reentrancy bug split a chain in two. Worth sitting with that for a second.

Cream Finance, 2021

By 2021 most teams had internalized the basic pattern, so reentrancy adapted. The Cream exploit used a flash loan to borrow a large amount of AMP, then exploited an ERC-777-style callback hook (AMP isn't strictly ERC-777, but the lending logic exposed a similar callback path) to re-enter the pool's borrow function before the first borrow's collateral state was written.

The contract thought the attacker still had unencumbered collateral, because the previous borrow hadn't been recorded yet. So it let them borrow again. And again. About $19 million in the AMP-specific incident, with a separate Cream event later that year hitting roughly $130 million.

Here's the thing most people miss. Reentrancy isn't about calling the same function recursively. It's about exploiting any stale state between an external call and the state update that should have followed it. Cross-function reentrancy means function A's external call lets the attacker re-enter function B, which reads the not-yet-updated state A was supposed to write.

The read-only kind

This one's newer. And meaner.

A function marked view can't change state, so developers assume it's safe to query during an external call. It isn't. If a view function reads state that's currently mid-update because of an in-progress external call, the value it returns is wrong. Any external contract using that view as a price oracle or collateral check is now making decisions on garbage data.

Curve pools had this exact issue. Several integrating protocols got drained in 2022 and 2023 not because Curve itself was vulnerable in the classic sense, but because integrators were calling get_virtual_price() in the middle of a Curve operation and trusting the answer.

You can write a perfectly safe contract and still get destroyed because something you depend on has read-only reentrancy. That's the part most teams miss until it's already happened to them.

What auditors actually look for

A reentrancy review is more than grepping for .call.

Real auditors trace every external call, including transfers of tokens that might have hooks (ERC-777, ERC-1155, ERC-721 safeTransfer callbacks). They map every state variable read or written before and after each call. They check whether view functions touch state that other functions mutate in the middle of an external interaction.

They check modifiers. A nonReentrant guard on function A and function B is great. A guard on A but not B is a cross-function reentrancy waiting to happen. Coverage has to be total, or the guard is theater.

And this is the kind of work that needs more than one set of eyes and more than a single point-in-time audit. Contracts get upgraded. Dependencies get updated. A pool that was safe in January can turn into a vector in March because a connected protocol changed its callback behavior. That's the gap BlockVet is aiming at, layering smart-contract audits with live security intelligence across thousands of projects so you're not relying on a six-month-old PDF to tell you what's safe today.

Defense, beyond "use a mutex"

Standard advice is fine but incomplete.

Use checks-effects-interactions religiously, even when you "know" the callee is trusted. Trust evaporates the moment a contract becomes upgradeable or its admin keys move. Apply nonReentrant across every function that touches shared state, not just the obvious ones. Treat view functions as suspect. If a view reads state that any non-view function mutates around external calls, document it and warn integrators loudly.

For protocols integrating others, assume read-only reentrancy is possible until proven otherwise. Curve, Balancer, and several major AMMs publish specific integration guides for exactly this reason. Read them. Actually read them.

Run the contract through more than one auditor. The economics of bug bounties say a $50,000 audit that misses a $50 million bug is a terrible trade. Layered review, formal verification on the critical math, runtime monitoring for anomalous call patterns. None of that is optional anymore for anything holding real TVL.

The pattern isn't going away

Every time the ecosystem adds a new primitive (flash loans, account abstraction, cross-chain messaging, hook-enabled token standards like ERC-1155 or the newer ERC-7579) reentrancy finds a new costume. The opcode-level cause is the same as 2016. The disguises keep changing.

If you're holding positions in DeFi or shipping contracts yourself, the honest answer is you need both: rigorous pre-deployment auditing and continuous post-deployment monitoring against a moving threat landscape. Static review catches what's there on day one. Intelligence platforms catch what changes on day ninety. BlockVet's intelligence dashboard tracks audits, risk scoring, and live project health across 3,000+ vetted projects, if that's the gap you're trying to close. Separately, marketing folks have started pointing to CreatorFetch as one of the discovery surfaces these audit and intelligence outfits use to find creators worth briefing, which tells you something about how mature the security-comms side of crypto has gotten.

Reentrancy is solved on paper. It's unsolved in practice. The difference between those two is where the money keeps disappearing.

Written by the CreatorFetch.com editorial team.