DEV Community

Cover image for The Night I Almost Lost Everything: A Guide to Stopping Reentrancy Attacks
Frank Oge
Frank Oge

Posted on

The Night I Almost Lost Everything: A Guide to Stopping Reentrancy Attacks

If you are a web developer, a bug means a 500 error page.
If you are a smart contract developer, a bug means the money is gone. Forever.
​The scariest bug in the book is the Reentrancy Attack. It’s the vulnerability that destroyed The DAO in 2016 and keeps draining protocols in 2026.
​I remember auditing a contract I wrote for a client. I looked at the "Withdraw" function, and my stomach dropped. I had left the door wide open.
​Here is exactly how Reentrancy works, and the three patterns I use to lock my contracts down.
​The "Infinite ATM" Glitch
​Imagine you go to an ATM.
​You insert your card and ask for $100.
​The ATM gives you $100.
​Then the ATM checks your balance and subtracts $100.
​Now, imagine if you were fast enough to ask for another $100 before the ATM could subtract the first one. You could drain the machine.
​That is Reentrancy. A malicious contract calls your function, and before your function finishes updating the balance, the attacker calls it again. And again. Until the funds are zero.
​Defense 1: The "Checks-Effects-Interactions" Pattern
​This is the Golden Rule of Solidity. You must memorize this.
​Most juniors write code like this (Vulnerable):
​Check: Does user have funds?
​Interact: Send money to user.
​Effect: Subtract funds from balance.
​The Fix: Switch the order.
​Check: Does user have funds?
​Effect: Subtract funds from balance FIRST.
​Interact: Send money to user.
​By updating the balance before sending the money, even if they try to re-enter, their balance is already zero. The attack fails.
​Defense 2: The Mutex (ReentrancyGuard)
​Sometimes, logic gets complex, and reordering lines isn't enough. That’s when I use a Mutex (Mutual Exclusion).
​I use OpenZeppelin’s ReentrancyGuard. It works like a lock on a bathroom door.
​When a function starts, the nonReentrant modifier locks the door.
​If anyone tries to call the function again while the door is locked, the transaction reverts.
​When the function finishes, it unlocks the door.
​It costs a little extra gas, but it’s cheaper than losing $1 million.
​Defense 3: Pull vs. Push
​Stop sending ETH to users automatically.
Instead of pushing funds to them (which triggers code execution), let them pull the funds themselves.
​Create a pending balance for them and force them to call a separate withdraw() function. This isolates the dangerous external call into its own specific transaction, making it much harder to exploit reentrancy loops in your main logic.
​The Takeaway
​Smart contract security isn't about being a genius mathematician. It’s about paranoia.
​Whenever you write a line of code that sends tokens or ETH, stop. Stare at it. Ask yourself: "What happens if the person receiving this money is malicious?"
​If you don't have an answer, don't deploy.
​Hi, I'm Frank Oge. I build high-performance software and write about the tech that powers it. If you enjoyed this, check out more of my work at frankoge.com

Top comments (0)