What if a tiny missing lock lets hackers steal millions from your smart contract?
Why Reentrancy guard pattern in Blockchain / Solidity? - Purpose & Use Cases
Imagine you have a digital wallet smart contract that lets users withdraw funds. Without protection, a hacker could trick the contract into sending money multiple times before the first withdrawal finishes.
Manually checking and updating states without a guard is slow and risky. It's easy to miss a step, letting attackers exploit the contract to drain funds quickly and silently.
The reentrancy guard pattern acts like a lock on the contract's critical functions. It stops the same function from running again before the first call finishes, blocking repeated attacks automatically.
function withdraw() {
if (balance >= amount) {
send(amount);
balance -= amount;
}
}bool locked = false;
function withdraw() {
require(!locked);
require(balance >= amount);
locked = true;
send(amount);
balance -= amount;
locked = false;
}This pattern makes smart contracts safe from reentrancy attacks, letting developers build trustable and secure blockchain apps.
Decentralized finance (DeFi) platforms use reentrancy guards to protect users' funds during withdrawals and trades, preventing hackers from stealing money through repeated calls.
Manual checks can miss reentrancy risks and cause big losses.
Reentrancy guard locks functions to block repeated calls.
It's essential for secure and trustworthy blockchain contracts.
Practice
What is the main purpose of the reentrancy guard pattern in blockchain smart contracts?
Solution
Step 1: Understand the problem reentrancy causes
Reentrancy happens when a function is called again before the first call finishes, causing unexpected behavior or exploits.Step 2: Identify the role of the reentrancy guard
The reentrancy guard uses a lock to block repeated calls until the first call completes, preventing reentrancy attacks.Final Answer:
To prevent a function from being called again before it finishes execution -> Option AQuick Check:
Reentrancy guard = prevent repeated calls [OK]
- Thinking it speeds up contract execution
- Believing it allows simultaneous access
- Confusing it with gas optimization
Which of the following is the correct way to declare a reentrancy guard variable in Solidity?
contract Example {
// Fill in the blank
_____ private locked = false;
}Solution
Step 1: Identify the variable type for a lock
The lock variable is used as a flag to indicate if the function is running, so it should be a boolean (true/false).Step 2: Match the correct Solidity type
In Solidity,boolis the type for true/false values, suitable for a lock variable.Final Answer:
bool -> Option BQuick Check:
Lock variable type = bool [OK]
- Using int instead of bool for lock
- Using string or address types incorrectly
- Forgetting to initialize the variable
Consider this Solidity function using a reentrancy guard:
bool private locked = false;
function withdraw(uint amount) public {
require(!locked, "Reentrant call detected");
locked = true;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
locked = false;
}What happens if msg.sender.call triggers a fallback that calls withdraw again?
Solution
Step 1: Analyze the lock status during the first call
Whenwithdrawis called,lockedis set to true before sending funds.Step 2: Understand the effect on reentrant call
Ifmsg.sender.calltriggers a fallback that callswithdrawagain, the require(!locked) check fails becauselockedis true, blocking the call.Final Answer:
The second call is blocked by the lock and reverts -> Option AQuick Check:
Lock true blocks reentrant call [OK]
- Assuming reentrancy still happens
- Thinking lock resets before second call
- Confusing gas failure with reentrancy
Identify the bug in this reentrancy guard implementation:
bool private locked = false;
function withdraw(uint amount) public {
require(!locked, "Reentrant call detected");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
locked = true;
locked = false;
}Solution
Step 1: Check when the lock is set
The lock is set to true only after the external call, which means the function is not protected during the call.Step 2: Understand reentrancy risk
Because the lock is set too late, a reentrant call can happen during the external call, causing vulnerability.Final Answer:
The lock is set after the external call, allowing reentrancy -> Option CQuick Check:
Lock must be set before external calls [OK]
- Setting lock after external call
- Forgetting to set lock at all
- Misplacing require statements
You want to protect a function that updates user balances and sends Ether. Which is the best way to apply the reentrancy guard pattern?
function safeWithdraw(uint amount) public {
// Your code here
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}Solution
Step 1: Understand the order of operations for safety
To prevent reentrancy, set a lock before any state changes and external calls, then release it after.Step 2: Apply the reentrancy guard pattern correctly
Set the lock variable to true before updating balances and sending Ether, then set it back to false after the transfer completes.Final Answer:
Set a lock variable before updating balances and sending Ether, then release it after -> Option DQuick Check:
Lock before state change and external call [OK]
- Setting lock after sending Ether
- Updating balances after external call
- Not using any lock at all
Ready to prove it?
Pass the test and this pattern moves unknown
