0
0
Blockchain / Solidityprogramming~20 mins

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

Choose your learning style9 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.