Bird
Raised Fist0
Blockchain / Solidityprogramming~20 mins

Reentrancy attacks in Blockchain / Solidity - Practice Problems & Coding Challenges

Choose your learning style10 modes available
Challenge - 5 Problems
🎖️
Reentrancy Master
Get all challenges correct to earn this badge!
Test your skills under time pressure!
❓ Predict Output
intermediate
2:00remaining
What is the output of this vulnerable Solidity contract call?

Consider the following simplified Solidity contract vulnerable to reentrancy:

contract Vulnerable {
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint _amount) public {
        require(balances[msg.sender] >= _amount);
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success);
        balances[msg.sender] -= _amount;
    }
}

If an attacker calls withdraw with a crafted fallback function that calls withdraw again before the balance is updated, what will be the final balance of the attacker after one initial withdrawal of 1 ether?

AThe attacker can withdraw multiple times before balance updates, ending with more ether than deposited.
BThe attacker can withdraw more than their balance, ending with a negative balance error.
CThe attacker drains all their balance repeatedly, ending with 0 balance.
DThe contract reverts due to require failure, so no ether is withdrawn.
Attempts:
2 left
💡 Hint

Think about when the balance is updated relative to the external call.

🧠 Conceptual
intermediate
1:30remaining
Which practice prevents reentrancy attacks in Solidity?

Which of the following is the best practice to prevent reentrancy attacks in Solidity smart contracts?

AUpdate the user balance after sending ether to the user.
BUse <code>transfer</code> or <code>send</code> instead of <code>call</code> to send ether.
CUpdate the user balance before sending ether to the user.
DAllow unlimited gas in external calls to avoid failures.
Attempts:
2 left
💡 Hint

Think about the order of state changes and external calls.

🔧 Debug
advanced
2:30remaining
Identify the vulnerability in this Solidity withdraw function

Examine the following Solidity function and identify the vulnerability:

function withdraw(uint _amount) public {
    require(balances[msg.sender] >= _amount);
    (bool success, ) = msg.sender.call{value: _amount}("");
    require(success);
    balances[msg.sender] -= _amount;
}
AThe balance is updated after the external call, allowing reentrancy attacks.
BThe require statement is missing to check the success of the call.
CThe function uses <code>transfer</code> which is unsafe for sending ether.
DThe function does not check if _amount is zero.
Attempts:
2 left
💡 Hint

Consider the order of operations and what happens if the external call triggers fallback code.

📝 Syntax
advanced
2:00remaining
Which option correctly fixes the reentrancy vulnerability?

Given the vulnerable withdraw function below, which option correctly fixes the reentrancy vulnerability by changing the order of operations?

function withdraw(uint _amount) public {
    require(balances[msg.sender] >= _amount);
    (bool success, ) = msg.sender.call{value: _amount}("");
    require(success);
    balances[msg.sender] -= _amount;
}
AReplace <code>msg.sender.call</code> with <code>msg.sender.transfer</code> without changing order.
BMove <code>balances[msg.sender] -= _amount;</code> before the external call to <code>msg.sender.call</code>.
CAdd a mutex lock variable to prevent reentrancy but keep the order same.
DRemove the require statement checking balances.
Attempts:
2 left
💡 Hint

Focus on the order of balance update and external call.

🚀 Application
expert
3:00remaining
How many times can the attacker recursively call withdraw in this scenario?

An attacker deposits 5 ether into the vulnerable contract below and then calls withdraw(1 ether). The fallback function recursively calls withdraw(1 ether) as long as the balance is sufficient.

contract Vulnerable {
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint _amount) public {
        require(balances[msg.sender] >= _amount);
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success);
        balances[msg.sender] -= _amount;
    }
}

How many times will the attacker be able to withdraw 1 ether recursively before the transaction fails or completes?

AZero times, the contract reverts immediately.
B5 times, equal to the initial deposited balance.
COnly once, because the balance check prevents multiple withdrawals.
DMore than 5 times, because the balance is updated after sending ether.
Attempts:
2 left
💡 Hint

Think about when the balance is decreased relative to the external call and recursive calls.

Practice

(1/5)
1. What is a reentrancy attack in blockchain smart contracts?
easy
A. An attack where a contract is called repeatedly before finishing its first call
B. An attack that steals private keys from a wallet
C. An attack that floods the network with transactions
D. An attack that changes the blockchain consensus rules

Solution

  1. Step 1: Understand the concept of reentrancy

    Reentrancy happens when a contract allows another call to itself before the first call finishes, causing unexpected behavior.
  2. Step 2: Identify the correct description

    An attack where a contract is called repeatedly before finishing its first call correctly describes repeated calls before finishing, which is the core of reentrancy attacks.
  3. Final Answer:

    An attack where a contract is called repeatedly before finishing its first call -> Option A
  4. Quick Check:

    Repeated calls before finish = Reentrancy attack [OK]
Hint: Look for repeated calls before state update to spot reentrancy [OK]
Common Mistakes:
  • Confusing reentrancy with key theft
  • Thinking it floods network instead
  • Mixing with consensus attacks
2. Which Solidity code snippet correctly prevents reentrancy by updating state before sending Ether?
easy
A. payable(msg.sender).transfer(amount); balances[msg.sender] = 0;
B. require(balances[msg.sender] > 0); payable(msg.sender).transfer(amount);
C. balances[msg.sender] = 0; payable(msg.sender).transfer(amount);
D. balances[msg.sender] += amount; payable(msg.sender).transfer(amount);

Solution

  1. Step 1: Identify the safe order of operations

    To prevent reentrancy, update the user's balance before sending Ether to avoid repeated withdrawals.
  2. Step 2: Check each option's order

    balances[msg.sender] = 0; payable(msg.sender).transfer(amount); updates balance first, then sends Ether, which is the correct pattern.
  3. Final Answer:

    balances[msg.sender] = 0; payable(msg.sender).transfer(amount); -> Option C
  4. Quick Check:

    Update state before transfer = Safe pattern [OK]
Hint: Always update balances before sending Ether to avoid reentrancy [OK]
Common Mistakes:
  • Sending Ether before updating balance
  • Not checking balance before transfer
  • Increasing balance instead of resetting
3. Consider this Solidity function snippet:
function withdraw(uint amount) public {
  require(balances[msg.sender] >= amount);
  payable(msg.sender).transfer(amount);
  balances[msg.sender] -= amount;
}
What is the likely outcome if this contract is attacked by a reentrancy exploit?
medium
A. The attacker can withdraw more than their balance multiple times
B. The contract will reject the withdrawal due to require failing
C. The contract will revert due to syntax error
D. The attacker cannot withdraw more than their balance

Solution

  1. Step 1: Analyze the order of operations in withdraw

    The contract sends Ether before reducing the balance, allowing reentrant calls to withdraw repeatedly.
  2. Step 2: Understand the attack effect

    Because balance is reduced after transfer, attacker can call withdraw again before balance updates, draining funds.
  3. Final Answer:

    The attacker can withdraw more than their balance multiple times -> Option A
  4. Quick Check:

    Transfer before balance update = Vulnerable to reentrancy [OK]
Hint: Check if balance updates happen after transfer to spot vulnerability [OK]
Common Mistakes:
  • Assuming require protects fully
  • Thinking syntax error occurs
  • Believing attacker can't withdraw extra
4. This Solidity function is vulnerable to reentrancy. What is the best fix?
function withdraw(uint amount) public {
  require(balances[msg.sender] >= amount);
  payable(msg.sender).transfer(amount);
  balances[msg.sender] -= amount;
}
medium
A. Add a loop to call transfer multiple times
B. Remove the require statement
C. Increase balances[msg.sender] after transfer
D. Move balances[msg.sender] -= amount; before transfer call

Solution

  1. Step 1: Identify vulnerability cause

    Balance is updated after sending Ether, allowing reentrant calls to exploit the contract.
  2. Step 2: Apply fix by reordering

    Moving balance update before transfer prevents reentrancy by blocking repeated withdrawals.
  3. Final Answer:

    Move balances[msg.sender] -= amount; before transfer call -> Option D
  4. Quick Check:

    Update state before transfer = Fix reentrancy [OK]
Hint: Update balances before sending Ether to fix reentrancy [OK]
Common Mistakes:
  • Removing require weakens security
  • Adding loops increases risk
  • Increasing balance after transfer worsens issue
5. You want to secure a smart contract against reentrancy attacks. Which combination of techniques is best practice?
hard
A. Allow multiple reentrant calls to improve performance
B. Update state before external calls and use a reentrancy guard modifier
C. Use only require statements without updating state
D. Send Ether first, then update state, and avoid using modifiers

Solution

  1. Step 1: Recall best practices for reentrancy protection

    Update contract state before external calls and use reentrancy guard modifiers to block repeated calls.
  2. Step 2: Evaluate options

    Update state before external calls and use a reentrancy guard modifier combines both safe state update order and reentrancy guard, providing strong protection.
  3. Final Answer:

    Update state before external calls and use a reentrancy guard modifier -> Option B
  4. Quick Check:

    State update + guard = Best reentrancy defense [OK]
Hint: Combine state update and guard modifier for strong defense [OK]
Common Mistakes:
  • Sending Ether before state update
  • Ignoring reentrancy guard usage
  • Allowing reentrant calls for speed
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