Introduction
- Welcome to the next stage of your Reentrancy journey!
- This lesson covers the solution to the Reentrancy Fundamentals: Defend Lab.
Prerequisites
Code Solution: DIFF View
In the below DIFF, you'll witness the differences between the original
Vulnerable.sol
and the updated Vulnerable.sol
that implements the Checks-Effects-Interactions pattern.
Tip
- Green highlights: Correspond to additions.
- Red highlights: Correspond to removals.
23 collapsed lines
// SPDX-License-Identifier: MIT // WARNING: This is buggy code. Do NOT use in the real world.
pragma solidity 0.8.24;
import "./shared/VulnerableBase.sol";
contract Vulnerable is VulnerableBase { mapping(address => uint256) public balances;
constructor() payable VulnerableBase() {}
// Allow a customer the ability to deposit funds into their account function deposit() public payable { uint256 initialBalance = balances[msg.sender]; balances[msg.sender] += msg.value; emit AttackerDepositBalance( initialBalance / 1 ether, balances[msg.sender] / 1 ether ); emit VulnerableContractBalance(address(this).balance / 1 ether); }
// Allow a customer the ability to withdraw all ETH from their account. function withdrawAll() public { uint256 beginExecutionBalance = balances[msg.sender]; // COMMENT GROUP A: START if (beginExecutionBalance <= 0) { revert("Customer has insufficient balance"); } balances[msg.sender] = 0; (bool success, ) = msg.sender.call{value: beginExecutionBalance}(""); require(success, "Failed to send ETH"); balances[msg.sender] = 0; // COMMENT GROUP A: END } }
Code Solution
Tip
- To maximize viewing space, click the icon within the bottom left corner
23 collapsed lines
// SPDX-License-Identifier: MIT// WARNING: This is buggy code. Do NOT use in the real world.
pragma solidity 0.8.24;
import "./shared/VulnerableBase.sol";
contract Vulnerable is VulnerableBase { mapping(address => uint256) public balances;
constructor() payable VulnerableBase() {}
// Allow a customer the ability to deposit funds into their account function deposit() public payable { uint256 initialBalance = balances[msg.sender]; balances[msg.sender] += msg.value; emit AttackerDepositBalance( initialBalance / 1 ether, balances[msg.sender] / 1 ether ); emit VulnerableContractBalance(address(this).balance / 1 ether); }
// Allow a customer the ability to withdraw all ETH from their account. function withdrawAll() public { uint256 beginExecutionBalance = balances[msg.sender]; // COMMENT GROUP A: START if (beginExecutionBalance <= 0) { revert("Customer has insufficient balance"); } balances[msg.sender] = 0; (bool success, ) = msg.sender.call{value: beginExecutionBalance}(""); require(success, "Failed to send ETH"); // COMMENT GROUP A: END }}
15 collapsed lines
// SPDX-License-Identifier: MIT// WARNING: This is buggy code. Do NOT use in the real world.
pragma solidity 0.8.24;
import "./shared/AttackerBase.sol";
contract Attacker is AttackerBase { // IMPORTANT: ONLY CHANGE THE FUNCTIONS THAT HAVE BEEN OUTLINED IN THE INSTRUCTIONS uint256 private constant _ATTACK_AMOUNT = 1 ether;
constructor( address _vulnerableContractAddress ) public payable AttackerBase(_vulnerableContractAddress) {}
function attack() external override { // Helpful for debugging emit AttackerContractBalance_(address(this).balance / 1 ether);
// TIP: You can call methods on the vulnerableContract // E.g., `vulnerableContract.withdrawAll()`
// COMMENT GROUP A: START vulnerableContract.deposit{value: _ATTACK_AMOUNT}(); vulnerableContract.withdrawAll(); // COMMENT GROUP A: END
// Helpful for debugging emit AttackerContractBalance_(address(this).balance / 1 ether); emit VulnerableContractBalance_( address(vulnerableContract).balance / 1 ether ); }
// Function is executed when VulnerableContract sends ETH. receive() external payable override { // Helpful for debugging emit AttackerContractBalance_(address(this).balance / 1 ether); emit VulnerableContractBalance_( address(vulnerableContract).balance / 1 ether ); emit AttackerDepositBalance_( vulnerableContract.balances(address(this)) / 1 ether );
// TIP: You can call methods on the vulnerableContract // E.g., `vulnerableContract.withdrawAll()`
// COMMENT GROUP A: START if (address(vulnerableContract).balance > 0) { vulnerableContract.withdrawAll(); } // COMMENT GROUP A: END }}
Explanation
Challenge: Attacker.sol should NOT be able to steal all ETH from Vulnerable.sol- Marker
1
: As thisif
statement is checking a condition, it is classified as a Check. - Marker
2
: As this statement is affecting the contract state, it's considered an Effect. - Marker
3
: As this statement is interacting with an external contract, it's considered an Interaction. - By following the Checks-Effects-Interactions pattern, the Reentrancy vulnerability was mitigated.
Slither Output: DIFF View
Tip
- Green highlights: Correspond to additions.
- Red highlights: Correspond to removals.
INFO:Detectors:Reentrancy in Vulnerable.withdrawAll() (Vulnerable.sol#25-35): External calls: - (success,None) = msg.sender.call{value: beginExecutionBalance}() (Vulnerable.sol#31) State variables written after the call(s): - balances[msg.sender] = 0 (Vulnerable.sol#33) Vulnerable.balances (Vulnerable.sol#9) can be used in cross function reentrancies: - Vulnerable.balances (Vulnerable.sol#9) - Vulnerable.deposit() (Vulnerable.sol#14-22) - Vulnerable.withdrawAll() (Vulnerable.sol#25-35)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilitiesINFO:Detectors:Low level call in Vulnerable.withdrawAll() (Vulnerable.sol#25-35): - (success,None) = msg.sender.call{value: beginExecutionBalance}() (Vulnerable.sol#31) - (success,None) = msg.sender.call{value: beginExecutionBalance}() (Vulnerable.sol#32)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-callsINFO:Slither:Vulnerable.sol analyzed (2 contracts with 93 detectors), 2 result(s) foundINFO:Slither:Vulnerable.sol analyzed (2 contracts with 93 detectors), 1 result(s) found
In the
slither
output DIFF (above), you'll witness the differences between the original Vulnerable.sol
and the updated Vulnerable.sol
that implements the Checks-Effects-Interactions pattern. Notice that slither
's reentrancy findings no longer appear.
Disclosures
Warning: