美文网首页合约安全
合约安全: selfdestruct自毁函数

合约安全: selfdestruct自毁函数

作者: 梁帆 | 来源:发表于2022-11-21 17:23 被阅读0次

1.selfdestruct功能介绍

selfdestruct函数,功能是销毁当前合约,而且可以输入一个参数,这个参数是payable address类型,自毁之后该合约的余额可以被全部传入参数地址中。参数地址无论是普通账户地址还是合约账户地址,都可以被动接受来自自毁合约的余额。我们写个例子。

用hardhat写Test合约:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

// Uncomment this line to use console.log
import "hardhat/console.sol";

contract Test {
    constructor() payable {
        console.log("msg.sender: ", msg.sender);
        console.log("msg.value: ", msg.value);
    }

    function kill() external {
        selfdestruct(payable(0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199));
    }
}

大概意思就是外部执行kill函数,然后就执行自毁函数selfdestruct,输入的参数地址是最后接收Test合约中所有资产的账户。
contructor函数是payable的,我们可以在部署的时候就传入以太坊资产,console.log是hardhat专用。
然后部署,部署日志中可以看到上面的两个console.log信息:

Test合约部署 ,即当前合约已有1个Ether。
我们写个task来测试:
task("test-transaction", "This task is broken")
    .setAction(async () => {
        const contractAddress = "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707";
        const test = await ethers.getContractAt('Test', contractAddress);

        const receiver = await ethers.getSigner("0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199");
        console.log("Before, balance of receiver = ", ethers.utils.formatEther(await ethers.provider.getBalance(receiver.address)));
        console.log("Before, balance of contract = ", ethers.utils.formatEther(await ethers.provider.getBalance(contractAddress)));


        const tx = await test.kill();
        await tx.wait();

        console.log("After, balance of receiver = ", ethers.utils.formatEther(await ethers.provider.getBalance(receiver.address)));
        console.log("After, balance of contract = ", ethers.utils.formatEther(await ethers.provider.getBalance(contractAddress)));
    });

输出:

Before, balance of receiver =  10001.0
Before, balance of contract =  1.0
After, balance of receiver =  10002.0
After, balance of contract =  0.0

可以看到自毁函数执行后,合约中的资产被清空了,而我们接受者receiver拿到了之前合约的资产。
这个selfdestruct函数的参数不仅仅可以是普通用户的地址,也可以是合约地址,而且,不管你的接受者合约有没有receive()fallback(),资产都可以被转过去,举个例子:

contract Receiver {

}

比如上面这个空白合约,也可以接受selfdestrut函数传来的资产。这是唯一的一个后门,这个后门也可以造成合约被攻击,看看下面一个案例。

2.攻击案例

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

这个被攻击的EtherGame的合约,逻辑比较简单,就是每个用户都可以调用deposit函数,一次有且仅能存入1个Ether,当用户存入1Ether导致合约余额超过targetAmount后,该用户就是winner,此时deposit功能被锁定。

攻击者Attack合约,首先用户可以给Attack合约存入一些Ether,然后执行selfdestruct,自毁函数的参数填EtherGame合约的地址,这样EtherGame合约的余额就增多了,而一旦余额超出了它的targetAmount,这样deposit就被锁定了,而且也没有winner,也不能claimReward,这个合约就直接废了。

在这个案例中,攻击者消耗了自己的以太,使得被攻击者的合约失效,被攻击的合约中的以太也永远没有办法取出来。总之,这对双方都没有好处。

3.拯救措施

EtherGame中,我们要避免使用合约自身的余额属性address(this).balance,可以用一个变量balance存起来,每次逻辑操作都经过balance验证,这样就不会被自毁函数所破坏。

修改后的合约如下图所示:

contract EtherGame {
    uint public targetAmount = 3 ether;
    uint 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 <= 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");
    }
}

相关文章

  • 合约安全: selfdestruct自毁函数

    1.selfdestruct功能介绍 selfdestruct函数,功能是销毁当前合约,而且可以输入一个参数,这个...

  • Solidity__合约销毁

    selfdestruct(minter); // 销毁合约

  • 关于ETH生态的一些知识

    关于合约安全 fallback函数,是一个匿名不带参数的函数。 当外部账户或其他合约向该合约地址发送 ether ...

  • 理解 gas refund

    以太坊为了鼓励大家节省状态存储空间,提供了一种 gas refund 机制。 合约调用的 selfdestruct...

  • 合约继承、自毁、事件、Address

    继承 在Solidity中使用is关键字来表示继承关系,子类可以访问父类的除private限制的属性和方法,包括i...

  • 以太坊的ABI编码

    ABI全称Application Binary Interface, 是调用智能合约函数以及合约之间函数调用的消息...

  • 自毁

    我这种算另一种形式的自毁了吧 不能说自己高要求,只能说说的另一半高要求。看着我这么久了没什么起色,就不回家了…… ...

  • 自毁

    想来个刺激的游戏,用一把小刀轻轻划开自己的胸膛,让血液流满全身洗涮自己的污秽。眼神透露恐慌心脏在自己的手中肆意的跳...

  • 自毁

    我完全就是在毁灭自己的终极者。 我一直想忘了以前的自己,找个自己的路,看着别人我羡慕极了,他们有各种条见件,在我...

  • 自毁

    一,毁掉一个人最快的方法就是让他闲着,这个我是认同的,大部分人是无法处理大段自由时间的。 很简单的例子:上学上班的...

网友评论

    本文标题:合约安全: selfdestruct自毁函数

    本文链接:https://www.haomeiwen.com/subject/mudnxdtx.html