Consider this simplified Solidity contract using a reentrancy guard. What will be the value of counter after calling increment() once?
pragma solidity ^0.8.0; contract ReentrancyGuard { bool private locked; uint public counter; modifier noReentrant() { require(!locked, "No reentrancy"); locked = true; _; locked = false; } function increment() public noReentrant { counter += 1; if(counter < 3) { increment(); } } }
Think about what happens when the function calls itself recursively with the reentrancy guard active.
The noReentrant modifier sets locked to true before the function body and resets it after. The recursive call to increment() happens while locked is true, so the require fails and the call reverts.
Which of the following best explains why a reentrancy guard is used in smart contracts?
Think about what happens if a function changes state and then calls an external contract that calls back before the first call finishes.
A reentrancy guard prevents a function from being entered again before the first call completes, which stops attackers from exploiting state changes by reentering the function unexpectedly.
What is wrong with this reentrancy guard modifier?
modifier noReentrant() {
require(!locked, "No reentrancy");
_;
locked = true;
}Look at when locked is set to true and if it ever resets.
The modifier sets locked to true only after the function body runs, but never resets it to false. This means once locked, the guard never unlocks, blocking all future calls.
Choose the correct Solidity code for a reentrancy guard modifier that prevents reentrant calls.
The guard must check locked before setting it to true and running the function body.
Option A correctly checks locked is false, sets it true before the function runs, then resets it to false after. Others either check after setting or have wrong conditions.
Given this contract snippet, how many times will the fallback function be called if an attacker tries to reenter during withdraw()?
pragma solidity ^0.8.0; contract Vulnerable { mapping(address => uint) public balances; bool private locked; modifier noReentrant() { require(!locked, "No reentrancy"); locked = true; _; locked = false; } function deposit() public payable { balances[msg.sender] += msg.value; } function withdraw(uint amount) public noReentrant { require(balances[msg.sender] >= amount, "Insufficient balance"); (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed"); balances[msg.sender] -= amount; } fallback() external payable { if (balances[msg.sender] > 0) { withdraw(balances[msg.sender]); } } }
Consider how the noReentrant modifier affects the recursive call inside fallback().
The noReentrant modifier blocks reentrant calls during withdraw(). So the fallback function can only be called once before the guard blocks further calls, preventing multiple reentries.