Reentrancy attacks in Blockchain / Solidity - Time & Space Complexity
When analyzing reentrancy attacks, we want to understand how the number of repeated calls affects the execution time.
We ask: How does the cost grow as the attacker repeatedly calls back into the contract?
Analyze the time complexity of the following vulnerable Solidity function.
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}();
require(success);
balances[msg.sender] -= amount;
}
This function sends Ether to the caller before updating their balance, allowing repeated calls before state changes.
Look for repeated calls that cause the function to run multiple times.
- Primary operation: Recursive external calls triggered by the attacker.
- How many times: Up to the attacker's balance, potentially many times.
Each recursive call repeats the withdrawal until the balance is drained.
| Input Size (balance n) | Approx. Operations |
|---|---|
| 10 | About 10 calls |
| 100 | About 100 calls |
| 1000 | About 1000 calls |
Pattern observation: The number of calls grows linearly with the attacker's balance.
Time Complexity: O(n)
This means the execution time grows directly with the number of repeated calls made by the attacker.
[X] Wrong: "The function runs only once per withdrawal call."
[OK] Correct: Because the attacker can reenter the function multiple times before the balance updates, causing many executions.
Understanding how repeated calls affect execution helps you spot vulnerabilities and reason about contract safety in real projects.
What if the balance was updated before sending Ether? How would the time complexity change?
Practice
Solution
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.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.Final Answer:
An attack where a contract is called repeatedly before finishing its first call -> Option AQuick Check:
Repeated calls before finish = Reentrancy attack [OK]
- Confusing reentrancy with key theft
- Thinking it floods network instead
- Mixing with consensus attacks
Solution
Step 1: Identify the safe order of operations
To prevent reentrancy, update the user's balance before sending Ether to avoid repeated withdrawals.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.Final Answer:
balances[msg.sender] = 0; payable(msg.sender).transfer(amount); -> Option CQuick Check:
Update state before transfer = Safe pattern [OK]
- Sending Ether before updating balance
- Not checking balance before transfer
- Increasing balance instead of resetting
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?Solution
Step 1: Analyze the order of operations in withdraw
The contract sends Ether before reducing the balance, allowing reentrant calls to withdraw repeatedly.Step 2: Understand the attack effect
Because balance is reduced after transfer, attacker can call withdraw again before balance updates, draining funds.Final Answer:
The attacker can withdraw more than their balance multiple times -> Option AQuick Check:
Transfer before balance update = Vulnerable to reentrancy [OK]
- Assuming require protects fully
- Thinking syntax error occurs
- Believing attacker can't withdraw extra
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
payable(msg.sender).transfer(amount);
balances[msg.sender] -= amount;
}Solution
Step 1: Identify vulnerability cause
Balance is updated after sending Ether, allowing reentrant calls to exploit the contract.Step 2: Apply fix by reordering
Moving balance update before transfer prevents reentrancy by blocking repeated withdrawals.Final Answer:
Move balances[msg.sender] -= amount; before transfer call -> Option DQuick Check:
Update state before transfer = Fix reentrancy [OK]
- Removing require weakens security
- Adding loops increases risk
- Increasing balance after transfer worsens issue
Solution
Step 1: Recall best practices for reentrancy protection
Update contract state before external calls and use reentrancy guard modifiers to block repeated calls.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.Final Answer:
Update state before external calls and use a reentrancy guard modifier -> Option BQuick Check:
State update + guard = Best reentrancy defense [OK]
- Sending Ether before state update
- Ignoring reentrancy guard usage
- Allowing reentrant calls for speed
Ready to prove it?
Pass the test and this pattern moves unknown
