Smart Contract Security 101 — Reentrancy & Common AI‑Generated Mistakes
Source: Dev.to
By day 30 of this Web3 journey, “security” stopped being a scary audit word and started feeling very real.
Because the first time an “innocent” withdraw function drained an entire contract, it didn’t look like a hack at all. It looked like… a normal payment going through.
- The user clicked Withdraw.
- The contract sent them some ETH.
- Everything looked fine—except the payment never really ended. It kept calling back. And back. And back again.
By the time the contract “realized” what happened, its balance was gone.
That’s reentrancy—and with AI helping us write Solidity faster than ever, it’s now dangerously easy to ship this bug by accident.
What Reentrancy Really Is
Think of a contract like a vending machine:
- You insert a coin.
- The machine checks your balance.
- Then it updates the balance and drops your snack.
Now imagine a bug where, while the snack door is still open, you can hit the “dispense” button again and again before the machine updates your balance. You keep grabbing snacks while the machine still thinks you’ve only taken one.
That’s reentrancy in Web3 terms:
- A smart contract sends ETH or tokens to another address/contract.
- While that transfer is happening, the receiver runs code.
- That code calls back into the original contract’s function before the balance or state is updated.
- The attacker repeats this loop, draining funds.
Key idea: the contract trusts that the external call is “just a transfer.” But in Ethereum, that recipient can be a contract with its own logic.
The Classic Reentrancy Trap (And Why AI Loves It)
Here’s the vulnerable pattern AI tools often generate when you ask for a “simple withdraw function”:
// 1️⃣ Check if msg.sender has enough balance
require(balances[msg.sender] >= amount, "Insufficient balance");
// 2️⃣ Send ETH
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Transfer failed");
// 3️⃣ Update balance
balances[msg.sender] = 0;
This looks perfectly logical in plain English, but the order of operations is deadly:
- External call goes out (to an attacker contract).
- Attacker’s fallback function runs and calls
withdraw()again. - The original contract still thinks the attacker has a balance, because it hasn’t set it to 0 yet.
- Funds keep flowing until the contract is empty.
AI models often:
- Use
call{value: …}("")without mentioning reentrancy risks. - Forget to use a reentrancy guard or the checks‑effects‑interactions pattern.
- Prioritize a “working example” over a “secure example,” especially when you ask for “simple,” “minimal,” or “gas‑efficient” code.
The code compiles. The tests pass. Mainnet doesn’t forgive.
Checks‑Effects‑Interactions: Your First Shield
To fight reentrancy, flip the vending‑machine logic:
- Instead of: “Send snack → then update balance”
- Do: “Update balance → then send snack”
In Solidity this is called checks‑effects‑interactions:
| Phase | What you do |
|---|---|
| Checks | Validate conditions (require statements). |
| Effects | Update internal state (balances, mappings). |
| Interactions | Call external contracts or send ETH last. |
Why it works: When the attacker tries to re‑enter, your contract’s state already says “You have no balance.” So even if they call back in, the require fails.
Any time you see call, delegatecall, or transfer followed by a state change, your “reentrancy radar” should beep.
Reentrancy Guards & Safe Patterns
Modern Solidity provides extra safety nets you should almost always use:
ReentrancyGuard– a simple “locked” flag that blocks a function from being entered again before it finishes.- Pull‑over‑push payments – let users withdraw in a controlled, minimal function instead of sending ETH automatically.
- Minimize external calls – the fewer external calls, the fewer reentrancy surfaces.
Think of ReentrancyGuard like putting a “Busy” sign on the vending‑machine door: while one snack is being dispensed, no one can press buttons—including the same person trying to spam the button through a backdoor.
As a beginner (and especially when using AI):
- Default to using
ReentrancyGuardon any function that sends ETH or calls untrusted contracts. - Only remove it when you fully understand why it’s safe without it.
Common AI‑Generated Security Mistakes (Beyond Reentrancy)
Reentrancy is just one category. AI tends to repeat a few dangerous patterns:
| Mistake | Why it’s risky |
|---|---|
Missing access control – admin‑only functions (setPrice, pause, withdrawAll) left as public. | Anyone can call them. |
Blind trust in msg.sender – no role checks (onlyOwner, AccessControl). | No protection against unauthorized actors. |
| Unbounded loops over arrays – “simple” loops over large user lists that can run out of gas. | Perfect for griefing or DoS. |
Ignoring return values – not checking if token transfer or call actually succeeded. | Silent failures that leave funds stuck. |
All of these look reasonable at first glance. That’s what makes them dangerous: they fail not in the compiler, but in production.
Your job as a beginner isn’t to write “perfect” code. It’s to notice when something could go wrong, and slow down there.
Why This Matters for Developers
Reentrancy is not “just another bug.” It’s the kind of bug that:
- Empties treasuries.
- Breaks user trust.
- Follows your name around on‑chain forever.
Think About This (Mini Challenge)
Next time you ask an AI tool:
“Write a simple Solidity contract that lets users deposit and withdraw ETH.”
Do this:
- Scan for external calls (
call,transfer,send). - Check the order: Does the function update state before or after sending ETH?
- Ask yourself: “What happens if the receiver is a contract that calls back right here?”
If you feel even a tiny bit of doubt, good—that’s your security instinct waking up.
Key Takeaway
Reentrancy isn’t magic. It’s just “I trusted external code before I locked my own house.”
As AI starts writing more of our smart‑contract code, make sure you always:
- Apply checks‑effects‑interactions.
- Use
ReentrancyGuard(or an equivalent). - Review every external call carefully.
Stay safe, stay curious, and keep building responsibly.
Solidity, your edge won’t be typing faster.
It will be spotting the trapdoors AI leaves behind.
Don’t aim to be the smartest auditor in the room.
Aim to be the developer who never ships the obvious bug.
Because in Web3, one “innocent” withdraw function can be the difference between:
- “Nice little dApp you deployed.”
- “Remember that contract that got drained? Yeah… that was mine.”
Resources to Go Deeper
-
Solidity Docs — Security Considerations – Reentrancy section
Official language docs explaining why external calls are dangerous and how to structure state changes safely. -
Consensys Diligence — Reentrancy (Smart Contract Best Practices) – Link
Classic reference for the attack pattern, checks‑effects‑interactions, and common pitfalls. -
OpenZeppelin Contracts — ReentrancyGuard – Link
The de‑facto standard implementation of a reentrancy lock; perfect follow‑up for readers who want to actually use the pattern you describe. -
Other resources:
-
Follow the series:
-
Join Web3ForHumans on Telegram and we’ll brainstorm Web3 together.