๐ Checks-Effects-Interactions: Preventing Reentrancy
Master reentrancy guards, mutex locks, and the CEI pattern
Your Progress
0 / 5 completed๐ก๏ธ Preventing Reentrancy Attacks
Now that you understand how reentrancy attacks work, let's explore the battle-tested methods developers use to protect their smart contracts.
๐ฏ Interactive: Prevention Methods
Explore 4 proven techniques to prevent reentrancy attacks:
Checks-Effects-Interactions
BasicFundamental pattern: check conditions, update state, then call externally
Pattern Order:
๐ฏ Interactive: Code Pattern Comparison
See the difference between vulnerable and secure implementations:
โ Vulnerable: External Call Before State Update
function withdraw(uint amount) external {
// โ
Check condition
require(balances[msg.sender] >= amount, "Insufficient balance");
// โ DANGER: External call before state update
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
// โ TOO LATE: State update after external call
// Attacker can reenter before reaching here
balances[msg.sender] -= amount;
emit Withdrawal(msg.sender, amount);
}๐จ Problems:
- โข Balance check passes on reentry (balance unchanged)
- โข Attacker withdraws multiple times
- โข Contract drained before balance update
Best Practices Checklist
Import OpenZeppelin's ReentrancyGuard and add nonReentrant modifier to functions with external calls.
Order matters: validate inputs, update state, then make external calls. Never reverse this order.
transfer() or send() for simple ETH transfersThese methods have 2,300 gas limit, preventing complex reentrancy. Only use call when necessary.
Let users withdraw funds rather than pushing payments. Eliminates external calls from core logic.
Every external call is a potential attack vector. Document why each is necessary and ensure proper guards.
Write test cases that attempt reentrancy. If tests pass, your contract is vulnerable!
Common Mistakes to Avoid
Never assume external contracts are safe. Always use guards even with "trusted" addresses.
Update ALL related state before external calls, not just the obvious variable.
Cross-function reentrancy is harder to spot. Protect all functions that share state.
Remember that receive() and fallback() can contain attack code.