The Reentrancy Riddle — Dissecting The Legendary Bug That Changed Ethereum Forever
Smart contracts are like vending machines you press a button, insert a coin, and something happens. But what if someone could press the button again before the first transaction finishes? That’s what Reentrancy is, and it’s one of the most famous vulnerabilities in Ethereum’s history.
Hi, I’m 0x00Auditor.A smart contract security enthusiast currently studying how the blockchain behaves when you poke it the wrong way.
This article is part of a beginner-friendly series where we learn from real exploits (ethically, of course 👨🔬).
Let’s dive into one of Ethereum’s most legendary bugs: Reentrancy.
This article breaks it down step-by-step for beginners. So buckle up!
What Even Is Reentrancy?
Reentrancy happens when a contract calls an external contract (say, to send ETH), and that external contract calls back into the original one — before the first function call finishes its business.
Imagine handing someone your wallet to take out ₹500 — and before you get it back, they sneak in and take another ₹500 again. That’s reentrancy.
Why Does Reentrancy Even Work?
Here’s the recipe for disaster:
- call() Hands Over Control — It lets the other contract run any logic it wants.
2. It Forwards All Gas — Enough juice to do serious damage.
3. No Automatic Locks — Unlike web backends, Solidity doesn’t block overlapping execution.
4. Delayed State Updates — If you update state after external calls, you open the door.
Types of Reentrancy Attacks
Let’s meet the family of attack variants you need to recognize.
- Single Function Reentrancy
This is the classic hit the function re-enters itself through a callback before finishing its business. In this article we’ll take a dive into Single Function Reentrancy
2. Cross Function Reentrancy
Two functions, one state and one attacker smart enough to play them against each other.
When logic leaks across functions, even innocent ones become dangerous.
3. Cross-Contract Reentrancy
Why attack through one function when you can bounce between contracts?
Contracts that trust each other can become dangerous allies in the wrong hands.
4. Read-Only Reentrancy
Even view functions can be gamed if they’re fed inconsistent state.
Often used in price oracles and DeFi protocols watch those read() calls closely.
Let’s See Some Code (The Bad Kind)
Why This Contract Is Vulnerable to Reentrancy
This contract, ReentrancyVulnerable, contains a critical flaw in the withdraw() function that makes it susceptible to a reentrancy attack.
What’s the Problem?
The core issue is the order of operations. Specifically:
- The contract sends Ether to the caller using
.call(). - Only after sending the Ether does it set the user’s balance to zero.
This creates a window where a malicious contract can re-enter the withdraw() function before their balance is updated, and repeatedly withdraw funds, draining the contract.
Now, let us look at the attacker contract
The Attacker Contract — Let’s Break Stuff
🎮 How the Attacker Plays the Game (Step-by-Step)
Ever wondered how a hacker actually pulls off a reentrancy attack?
Let’s walk through the exact moves like a playbook that an attacker uses to drain funds from a vulnerable smart contract.
🧪 The Recipe for Exploitation:
- Deploy a sneaky contract
The attacker writes a custom contract with areceive()function the trapdoor that gets triggered when ETH is sent. - Make a legit-looking deposit
They act like any normal user and deposit ETH into the vulnerable contract usingdeposit(). - Call
withdraw() - politely (at first)
Now that they’ve got a balance stored, they request a withdrawal. All seems normal so far. - Snatch control via
.call()
The vulnerable contract tries to send ETH using.call(), and BOOM this hands control to the attacker'sreceive()function. - Sneak back in: Call
withdraw()again
Inside thereceive()function, the attacker quickly callswithdraw()again before the original one finishes execution. - Rinse. Repeat. Drain.
This loop continues draining ETH again and again until the contract is empty or the gas runs out.
The complete attack call flow will look like this:
🛡️ How to Prevent Reentrancy Attacks — Defense 101
Now that we’ve seen how reentrancy works and how it wrecks contracts, let’s talk about the good stuff how to stop it before it stops your protocol.
Reentrancy isn’t just a gotcha it’s a logic bug. So fixing it isn’t about patching with duct tape it’s about coding with discipline and intention.
1. Use the “Checks-Effects-Interactions” Pattern
This is the golden rule of writing secure Solidity functions:
- Check: Validate conditions first.
- Effects: Update your contract’s internal state.
- Interactions: Only then interact with external contracts or addresses.
Why it works:
By updating state before making any external call, you make sure that even if someone jumps back into your function, the important stuff is already done.
Bad Example (Vulnerable):
Good Example (Safe):
The key difference? The secure version follows the CEI pattern — updating the state before making the external call, preventing reentrancy.
🧱 2. Use OpenZeppelin’s ReentrancyGuard
Sometimes, you just want a deadbolt on your functions.
The ReentrancyGuard contract from OpenZeppelin adds a modifier nonReentrant that makes sure no function can call itself directly or indirectly during execution.
Example:
✅ One line of defense — big peace of mind.
3. Avoid .call() Unless Absolutely Necessary
- Prefer
.transfer()or.send()when sending ETH to EOA (Externally Owned Accounts). - These methods forward only 2300 gas, not enough to execute complex logic or re-enter.
⚠️ Note: .transfer() and .send() may break in the future due to evolving gas costs (see EIP-1884, EIP-2929). So .call() is often still necessary but always use it cautiously and with guardrails.
🔍 4. Beware of Low-Level Calls
If you’re using:
call()delegatecall()callcode()- External interfaces to untrusted contracts
Then you’re handing over control. Know what you’re calling, or someone might call you back when you least expect it.
Don’t Get Reentered
💣 Risk | ✅ Mitigation
-------------------------- | -----------------------------------
External call before state | Use Checks-Effects-Interactions
Unprotected logic | Add `nonReentrant` modifier
Blind use of `.call()` | Use `.call()` with care or avoid it 🕵️♂️ Real-Life Reentrancy Exploits — When Theory Met Chaos
Learning about reentrancy in theory is cool but seeing how it shook the blockchain world? That’s where the real lessons lie. Let’s look at some of the most iconic real-world hacks caused by this bug.
🧨 1. The DAO Hack (2016) — The One That Changed Everything
💸 Loss: ~$60 million worth of ETH
🧠 What Happened:
The DAO was one of the first major decentralized organizations on Ethereum. It had a withdrawal function that sent ETH to users before updating their balance. This allowed a malicious contract to re-enter the same function again and again — draining funds in a recursive loop.
⚙️ How It Worked (Simplified):
- Attacker deposited ETH.
- Called
withdraw(). - The DAO used
.call()to send ETH → this triggered the attacker’s fallback. - Fallback called
withdraw()again — before the balance was set to 0. - Repeat. Drain. Chaos.
🧯 Impact:
The Ethereum community was split:
- One side pushed for a hard fork to recover funds → became Ethereum (ETH).
- The other side said “Code is law” → remained Ethereum Classic (ETC).
💡 Lesson:
Always update internal state before making external calls. Trust, but don’t re-enter.
🧠 Key Takeaways / TL;DR
Because not everyone has time for 2,000 words and a coffee ☕. Here’s what really matters:
🔐 Reentrancy is sneaky but preventable
It happens when your contract calls another contract and that contract calls back before you’re done cleaning up.
📉 Update state before making external calls
Don’t leave the vault open before checking out — use the Checks-Effects-Interactions pattern.
🧱 Use ReentrancyGuard from OpenZeppelin
A dead-simple and battle-tested way to stop nested calls using a mutex. Just slap nonReentrant on functions you care about.
🚫 Avoid using .call() for sending ETH if you don’t need to
It forwards all gas and opens the door wide. Prefer transfer or send — they’re limited and safer (but know their quirks too).
🔁 Push vs Pull Payments? Go with Pull.
Let users withdraw funds themselves instead of automatically sending it. It’s like letting guests pick snacks instead of throwing food at them.
🚨 Audit like a pessimist
Assume every function could be reentered and design your code like someone’s out to break it. Because they are.
✅ Master this one, and you’ve already dodged one of Ethereum’s most legendary landmines.
🚀 What’s Next?
You’ve just unlocked one of the most infamous smart contract bugs in Ethereum’s history. Congrats, fren!
But this is just the first level.
In the next episode, we’ll dive deeper into another flavor of the same beast: 🔄 Cross-Function Reentrancy — where attackers play functions against each other like chess pieces.
Here’s what you can do before we meet again:
- 🧠 Review this article with the vulnerable & attacker contract in Remix.
- 🧪 Try modifying the code: add a
nonReentrantor reorder the logic and test. - 💬 Join the convo on Twitter/X — share what blew your mind the most.
- 📩 Subscribe to the series so you never miss a vulnerability breakdown.
Until next time,
Stay curious, stay safe — and never trust external calls blindly 😉
👋 About Me — 0x00Auditor
Hey! I’m 0x00Auditor a smart contract security enthusiast currently studying how these magical DeFi vending machines work (and sometimes… break).
I’m not a hacker (yet 😅), but I do spend an unhealthy amount of time reading Solidity code, chasing bugs, and wondering why someone thought delegatecall was a good idea.
My mission? To make smart contract security understandable, practical, and just the right amount of fun (because let’s face it reading code without memes is cruel and unusual punishment).
🤝 Let’s Connect & Learn (Before the Hackers Do)
If you’re into smart contract security, DeFi madness, or just want a study buddy who occasionally cracks bad jokes in between require() statements — I’m your person.
- 🔗 LinkedIn — I pretend to be serious here
- 🐦 X (Twitter) — Where I shout into the void about reentrancy
- 💻 GitHub — Home of my experiments, labs, and learning-by-breaking-things (safely)
Let’s study together, audit together, and maybe one day… secure the next billion-dollar protocol together. 🚀
