Bird
Raised Fist0
Blockchain / Solidityprogramming~15 mins

Reentrancy guard pattern in Blockchain / Solidity - Deep Dive

Choose your learning style10 modes available
Overview - Reentrancy guard pattern
What is it?
The reentrancy guard pattern is a way to protect smart contracts from a specific attack called reentrancy. Reentrancy happens when a contract calls another contract that then calls back into the first contract before the first call finishes. This can cause unexpected behavior and let attackers steal funds or break the contract. The pattern uses a simple lock to prevent the contract from being entered again while it is already running.
Why it matters
Without the reentrancy guard, attackers can exploit contracts to drain money or corrupt data by repeatedly calling functions before the first call finishes. This has caused millions of dollars in losses in blockchain projects. The guard pattern stops these attacks by making sure only one call can run at a time, protecting users and their assets.
Where it fits
Before learning this, you should understand how smart contracts work and basic Solidity programming. After this, you can learn about other security patterns and advanced contract design to build safer decentralized applications.
Mental Model
Core Idea
The reentrancy guard pattern prevents a function from being called again before its first execution finishes, like locking a door to stop repeated entries.
Think of it like...
Imagine a bathroom with a lock on the door. When someone is inside, they lock the door so no one else can enter until they leave. This prevents multiple people from using the bathroom at the same time and causing confusion or accidents.
┌───────────────────────────────┐
│ Function call starts          │
│ ┌───────────────────────────┐ │
│ │ Check if locked?           │ │
│ │   No -> Lock and proceed   │ │
│ │   Yes -> Reject call       │ │
│ └───────────────────────────┘ │
│ Execute function logic         │
│ Unlock when done               │
└───────────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding reentrancy basics
🤔
Concept: Introduce what reentrancy means in smart contracts and why it is a problem.
Reentrancy happens when a contract calls another contract, and that second contract calls back into the first contract before the first call finishes. This can cause the first contract to run code multiple times unexpectedly, leading to bugs or theft. For example, if a contract sends money before updating balances, an attacker can repeatedly call back and drain funds.
Result
You understand the risk of reentrancy and why it can cause serious security issues.
Knowing how reentrancy works helps you see why contracts need protection to avoid repeated, unexpected calls.
2
FoundationBasic Solidity function flow
🤔
Concept: Learn how Solidity functions execute and how external calls work.
In Solidity, when a function calls another contract, control transfers to that contract. If the called contract calls back into the original contract, the original function is paused and then resumed after the call returns. This pause allows reentrancy if the contract is not careful. Understanding this flow is key to preventing attacks.
Result
You can trace how function calls and external calls work in Solidity.
Understanding the call stack and execution order is essential to grasp why reentrancy happens.
3
IntermediateImplementing a simple reentrancy guard
🤔Before reading on: do you think a boolean lock is enough to prevent reentrancy in all cases? Commit to your answer.
Concept: Introduce the pattern of using a boolean variable to lock a function during execution.
A common way to prevent reentrancy is to use a boolean variable called 'locked'. Before running the function logic, check if 'locked' is false. If so, set it to true, run the logic, then set it back to false. If 'locked' is true, reject the call. This stops the function from running again while it is already running.
Result
The function cannot be reentered during its execution, preventing reentrancy attacks.
Using a lock variable creates a simple but effective barrier against repeated calls during execution.
4
IntermediateUsing modifiers for cleaner guards
🤔Before reading on: do you think using a modifier changes how the guard works or just how it looks? Commit to your answer.
Concept: Learn how Solidity modifiers can wrap function logic to apply the guard pattern cleanly.
Solidity modifiers let you add reusable code before and after functions. You can create a 'nonReentrant' modifier that checks and sets the lock, then runs the function, then unlocks. This keeps your code clean and avoids repeating the lock logic in every function.
Result
Your contract code is easier to read and maintain while still protected from reentrancy.
Modifiers separate guard logic from business logic, improving code clarity and reducing errors.
5
IntermediateCommon pitfalls with reentrancy guards
🤔Before reading on: can a single reentrancy guard protect multiple functions safely? Commit to your answer.
Concept: Explore limitations and mistakes when using reentrancy guards, such as shared locks or forgetting to unlock.
If multiple functions share the same lock, calling one guarded function from another can cause failures. Also, forgetting to unlock after execution can lock the contract forever. Understanding these pitfalls helps you design guards correctly, like using separate locks or careful unlocking.
Result
You avoid common mistakes that break contract functionality or security.
Knowing guard limitations prevents introducing new bugs while fixing reentrancy.
6
AdvancedOpenZeppelin ReentrancyGuard implementation
🤔Before reading on: do you think OpenZeppelin's ReentrancyGuard uses a boolean or a different approach internally? Commit to your answer.
Concept: Study the widely used OpenZeppelin ReentrancyGuard contract and how it uses a status variable for better safety.
OpenZeppelin uses a uint256 status variable with two states: _NOT_ENTERED and _ENTERED. The guard modifier checks if status is _NOT_ENTERED, sets it to _ENTERED, runs the function, then resets it. This avoids some edge cases with booleans and gas refunds. It is a battle-tested pattern used in many projects.
Result
You understand a professional, secure implementation of the reentrancy guard.
Seeing a real-world implementation reveals subtle improvements over naive boolean locks.
7
ExpertReentrancy guard limits and advanced attacks
🤔Before reading on: can reentrancy guards protect against all types of reentrancy attacks? Commit to your answer.
Concept: Explore cases where reentrancy guards do not help, such as cross-function or cross-contract reentrancy, and advanced attack vectors.
Reentrancy guards protect only the functions they wrap. If a contract has multiple entry points or complex call flows, attackers may find ways around guards. Also, reentrancy can happen across contracts or via fallback functions. Experts combine guards with careful state updates, pull payments, and other patterns to fully secure contracts.
Result
You realize the guard is one tool among many for contract security.
Understanding guard limits prevents overconfidence and encourages layered security design.
Under the Hood
When a guarded function is called, the guard sets a status variable indicating the function is running. If the function tries to reenter (call itself or another guarded function) before finishing, the guard detects the status and blocks the call. This works because the Ethereum Virtual Machine executes calls in a stack, and the guard status persists during the call stack. After the function finishes, the status resets, allowing future calls.
Why designed this way?
The pattern was designed to be simple, gas-efficient, and easy to use. Early attacks exploited contracts that updated state after sending funds, so the guard forces a single execution path. Alternatives like complex state machines or external checks were more error-prone or costly. The guard balances security and usability, becoming a standard in Solidity development.
┌───────────────┐
│ Call function │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Check status  │
│ (locked?)     │
└──────┬────────┘
       │ No
       ▼
┌───────────────┐
│ Set locked    │
│ Execute logic │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Reset locked  │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Return result │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does a reentrancy guard protect all functions automatically? Commit yes or no.
Common Belief:A single reentrancy guard protects the entire contract and all its functions.
Tap to reveal reality
Reality:Reentrancy guards only protect the functions they are applied to. Other functions without the guard remain vulnerable.
Why it matters:Assuming full protection can leave critical functions exposed, leading to attacks despite using a guard.
Quick: Is a simple boolean lock always safe and gas efficient? Commit yes or no.
Common Belief:Using a boolean variable as a lock is always the best and safest way to prevent reentrancy.
Tap to reveal reality
Reality:Boolean locks can cause issues with gas refunds and may be less safe than using a status variable with explicit states, as done in OpenZeppelin's implementation.
Why it matters:Using naive boolean locks can cause subtle bugs or higher gas costs, reducing contract efficiency and security.
Quick: Can reentrancy guards stop all types of reentrancy attacks? Commit yes or no.
Common Belief:Reentrancy guards completely eliminate all reentrancy attack risks in smart contracts.
Tap to reveal reality
Reality:Reentrancy guards only prevent reentry into guarded functions; complex attacks involving multiple functions or contracts may still succeed without additional protections.
Why it matters:Overreliance on guards can lead to overlooked vulnerabilities and costly exploits.
Quick: Does locking a function prevent it from calling other guarded functions? Commit yes or no.
Common Belief:You can safely call one guarded function from another guarded function without issues.
Tap to reveal reality
Reality:Calling a guarded function from another guarded function sharing the same lock causes the second call to fail due to the lock being active.
Why it matters:Not understanding this can break contract logic and cause unexpected failures in production.
Expert Zone
1
The status variable in OpenZeppelin's guard uses uint256 instead of boolean to optimize gas refunds and avoid certain EVM quirks.
2
Reentrancy guards do not protect against attacks that exploit logic flaws unrelated to reentry, so they must be combined with other security patterns.
3
Stacking multiple reentrancy guards or using them improperly can cause deadlocks or lock the contract permanently.
When NOT to use
Avoid using reentrancy guards when your contract logic requires nested guarded calls or complex state transitions that the guard would block. Instead, use pull payment patterns, checks-effects-interactions order, or design contracts to minimize external calls.
Production Patterns
In production, reentrancy guards are often combined with the checks-effects-interactions pattern, where state changes happen before external calls. Many projects use OpenZeppelin's ReentrancyGuard as a base contract and apply the nonReentrant modifier to all sensitive functions handling funds.
Connections
Mutex locks in concurrent programming
The reentrancy guard pattern is a form of mutex lock that prevents multiple simultaneous accesses to a critical section.
Understanding mutex locks in programming helps grasp how reentrancy guards serialize function execution to avoid conflicts.
Bank vault security
Both use a locking mechanism to prevent unauthorized repeated access during sensitive operations.
Seeing reentrancy guards as vault locks clarifies why preventing multiple entries at once protects valuable assets.
Transaction isolation in databases
Reentrancy guards ensure a function's execution is isolated from concurrent calls, similar to how databases isolate transactions to maintain consistency.
Knowing transaction isolation concepts helps understand why guarding function execution prevents inconsistent contract states.
Common Pitfalls
#1Using a shared boolean lock for multiple functions causes failures when one guarded function calls another.
Wrong approach:bool locked = false; function funcA() public { require(!locked, "Locked"); locked = true; funcB(); locked = false; } function funcB() public { require(!locked, "Locked"); locked = true; // logic locked = false; }
Correct approach:uint256 private status; uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; constructor() { status = _NOT_ENTERED; } modifier nonReentrant() { require(status != _ENTERED, "Reentrant call"); status = _ENTERED; _; status = _NOT_ENTERED; } function funcA() public nonReentrant { funcB(); } function funcB() internal { // logic }
Root cause:Misunderstanding that a single boolean lock cannot handle nested guarded calls, causing lock conflicts.
#2Forgetting to reset the lock after function execution causes the contract to lock permanently.
Wrong approach:bool locked = false; function withdraw() public { require(!locked, "Locked"); locked = true; // send funds // forgot to set locked = false }
Correct approach:bool locked = false; function withdraw() public { require(!locked, "Locked"); locked = true; // send funds locked = false; }
Root cause:Neglecting to unlock the guard after execution traps the contract in a locked state.
#3Applying the guard only after external calls instead of before allows reentrancy during the call.
Wrong approach:bool locked = false; function withdraw() public { // send funds require(!locked, "Locked"); locked = true; // update balance locked = false; }
Correct approach:bool locked = false; function withdraw() public { require(!locked, "Locked"); locked = true; // update balance // send funds locked = false; }
Root cause:Incorrect order of operations allows reentrancy before the guard activates.
Key Takeaways
Reentrancy attacks exploit the ability to call a contract function repeatedly before the first call finishes, risking theft or corruption.
The reentrancy guard pattern uses a lock to prevent a function from being entered again during its execution, stopping these attacks.
Using Solidity modifiers to implement the guard keeps code clean and reduces errors compared to manual locking.
Professional implementations like OpenZeppelin's use status variables instead of booleans for better safety and gas efficiency.
Reentrancy guards are a vital tool but must be combined with other security practices to fully protect smart contracts.

Practice

(1/5)
1.

What is the main purpose of the reentrancy guard pattern in blockchain smart contracts?

easy
A. To prevent a function from being called again before it finishes execution
B. To speed up the execution of smart contracts
C. To allow multiple users to access a function simultaneously
D. To reduce the gas cost of transactions

Solution

  1. 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.
  2. 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.
  3. Final Answer:

    To prevent a function from being called again before it finishes execution -> Option A
  4. Quick Check:

    Reentrancy guard = prevent repeated calls [OK]
Hint: Reentrancy guard stops repeated calls during execution [OK]
Common Mistakes:
  • Thinking it speeds up contract execution
  • Believing it allows simultaneous access
  • Confusing it with gas optimization
2.

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;
}
easy
A. string
B. bool
C. int
D. address

Solution

  1. 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).
  2. Step 2: Match the correct Solidity type

    In Solidity, bool is the type for true/false values, suitable for a lock variable.
  3. Final Answer:

    bool -> Option B
  4. Quick Check:

    Lock variable type = bool [OK]
Hint: Lock variables are boolean flags in Solidity [OK]
Common Mistakes:
  • Using int instead of bool for lock
  • Using string or address types incorrectly
  • Forgetting to initialize the variable
3.

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?

medium
A. The second call is blocked by the lock and reverts
B. The second call succeeds and causes reentrancy
C. The contract runs out of gas and fails
D. The lock variable resets before the second call

Solution

  1. Step 1: Analyze the lock status during the first call

    When withdraw is called, locked is set to true before sending funds.
  2. Step 2: Understand the effect on reentrant call

    If msg.sender.call triggers a fallback that calls withdraw again, the require(!locked) check fails because locked is true, blocking the call.
  3. Final Answer:

    The second call is blocked by the lock and reverts -> Option A
  4. Quick Check:

    Lock true blocks reentrant call [OK]
Hint: Lock true means no reentrant calls allowed [OK]
Common Mistakes:
  • Assuming reentrancy still happens
  • Thinking lock resets before second call
  • Confusing gas failure with reentrancy
4.

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;
}
medium
A. The require statement is missing
B. The lock variable is never set to true
C. The lock is set after the external call, allowing reentrancy
D. The call to msg.sender is incorrect syntax

Solution

  1. 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.
  2. Step 2: Understand reentrancy risk

    Because the lock is set too late, a reentrant call can happen during the external call, causing vulnerability.
  3. Final Answer:

    The lock is set after the external call, allowing reentrancy -> Option C
  4. Quick Check:

    Lock must be set before external calls [OK]
Hint: Set lock before external calls to prevent reentrancy [OK]
Common Mistakes:
  • Setting lock after external call
  • Forgetting to set lock at all
  • Misplacing require statements
5.

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");
}
hard
A. Update balances after sending Ether without any lock
B. Do not use any lock, just rely on require statements
C. Use a lock variable but set it after sending Ether
D. Set a lock variable before updating balances and sending Ether, then release it after

Solution

  1. 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.
  2. 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.
  3. Final Answer:

    Set a lock variable before updating balances and sending Ether, then release it after -> Option D
  4. Quick Check:

    Lock before state change and external call [OK]
Hint: Lock before balance update and transfer, unlock after [OK]
Common Mistakes:
  • Setting lock after sending Ether
  • Updating balances after external call
  • Not using any lock at all
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard