Skip to main content

Introduction

  • Welcome to the next stage of your Reentrancy journey!
  • This lesson covers the solution to the Reentrancy Fundamentals: Defend Lab.

Prerequisites

  • Reentrancy Fundamentals: Attack Theory
  • Reentrancy Fundamentals: Attack Lab

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.

Vulnerable.sol
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

Vulnerable.sol
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
}
}
Attacker.sol
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

Slither Output: DIFF View

Tip

  • Green highlights: Correspond to additions.
  • Red highlights: Correspond to removals.

Slither Terminal output
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-vulnerabilities
INFO: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-calls
INFO:Slither:Vulnerable.sol analyzed (2 contracts with 93 detectors), 2 result(s) found
INFO: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:

  • Content should be used for educational purposes only. You should not leverage this content for nefarious purposes. Blockbash's authors are not liable for misuse of this content.
  • Everyone makes mistakes, including the authors of Blockbash. All content and recommendations should be verified via another source. Blockbash's authors are not liable for any mistakes. If you've found an error, please create a Github Issue.