# selfdestruct详解 **Published by:** [xyyme.eth](https://paragraph.com/@xyyme/) **Published on:** 2022-03-30 **URL:** https://paragraph.com/@xyyme/selfdestruct ## Content Solidity 中的 selfdestruct 函数是一个内置函数,需要接收一个参数。我们先来看看文档是怎么描述 selfdestruct 的:The only way to remove code from the blockchain is when a contract at that address performs the selfdestruct operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state. Removing the contract in theory sounds like a good idea, but it is potentially dangerous, as if someone sends Ether to removed contracts, the Ether is forever lost.selfdestruct 可以清除合约的代码,且是唯一清除代码的方法。当调用 selfdestruct 时,合约的所有 ETH 余额会发送给 selfdestruct 的参数地址,然后合约的代码和内存都被移除。销毁之后,如果给这个合约地址发送 ETH,那么就永久销毁了。代码实践我们来写段代码测试一下:pragma solidity 0.8.13; contract TestDestruct { uint public age; string public name; // 构造标记为payable,我们直接在部署的时候就打入ETH constructor(uint _age, string memory _name) payable { age = _age; name = _name; } function destruct() external { selfdestruct(payable(msg.sender)); } function balance() external view returns (uint) { return address(this).balance; } } 部署合约,参数为(88,Tom),同时传入 1 ETH,查看状态变量与合约余额分别为:age: 88name: Tombalance: 1 ETH没有问题。同时注意到,Etherscan中的合约界面为(没有上传代码):那么我们再调用一下 destruct 函数,此时再查看状态变量与合约余额分别为:age: 0name: (为空)balance: 0同时再看 Etherscan 的这笔交易:很明显能看出,销毁时,合约余额转给了 selfdestruct 的参数地址,也就是 msg.sender。 此时,我们再看 Etherscan 的合约页面:可以看到合约代码已经被销毁了。 其实,当合约调用 selfdestruct 被销毁时,由于合约本身的代码和数据都已经销毁,因此合约地址此时就已经变成了一个 EOA 地址。那么如果向这个地址发送 ETH,除非能够找到这个地址所对应的私钥,那么这些 ETH 便是永久销毁了。但是这里有一个特例,就是与 create2 相结合,就可以碰撞出神奇的火花。与 create2 结合之前的文章我们介绍过 create2 操作码,对于同一个合约,如果参数与盐都相同,那么工厂合约部署的时候,得到的一定是相同的地址。但是由于第一次已经部署了,所以第二次部署是会失败的。但是如果第一次部署的合约中可以 selfdestruct,那么按照我们前面的结论,selfdestruct 之后这个地址已经变成 EOA 地址了,那么能不能再在相同地址上部署一下呢。来试试:pragma solidity 0.8.13; import "@openzeppelin/contracts/utils/Create2.sol"; contract Create2WithDestruct { event Deployed(address addr); function deploy() public { // 部署合约,参数传88 address addr = Create2.deploy( 0, keccak256("Here is salt"), abi.encodePacked(type(DemoOne).creationCode, abi.encode(88)) ); // 记录地址 emit Deployed(addr); } } contract DemoOne { uint public age; constructor(uint _age) { age = _age; } function destruct() external { selfdestruct(payable(msg.sender)); } } 这段代码中,我们在 Create2WithDestruct 合约中,使用 create2 操作码部署 DemoOne 合约,参数传 88。部署后,事件记录为:即通过 create2 部署得到的 DemoOne 地址为:0x10CEBC6a50B65320578bBfA6b96503c75f745424通过这个地址来读取合约数据:数据正确。我们尝试通过 create2 再次部署 DemoOne 合约:交易失败,这是因为这个地址上已经有数据了,因此不能再次部署,这与我们的预期一致。 现在我们尝试调用 DemoOne 合约的 selfdestruct 函数。调用成功,同时,age已经变成0:按照我们之前的结论,此时之前的合约地址已经变成了 EOA,而在任何一个 EOA 地址上都是可以部署合约的,那么我们再来试试调用 deploy 部署一次合约:部署成功,同时得到的地址为:0x10CEBC6a50B65320578bBfA6b96503c75f745424这与我们之前第一次部署的时候得到的地址相同。也就是说,我们可以通过 selfdestruct 与 create2 相结合在同一个地址上多次部署合约。这样,我们前面说的当合约销毁后,向合约发送 ETH 将被永久销毁的说法在这里就不准确了。如果合约是由 create2 部署的,那么在合约被销毁之后,仍然可以通过再次部署合约得到这个地址的控制权。selfdestruct 发送 ETH 的神奇用法前面我们说到,selfdestruct 中的参数地址会接收到合约的余额,而这个行为是强制性的,也就是说,即使这个参数地址是个合约,并且合约中通过 receive 或者 fallback 限制了接收 ETH,而 selfdestruct 中的发送行为仍然是可以发送给这个地址的。 来看段代码:pragma solidity 0.8.13; contract DestructTransfer { constructor() payable { } function destruct(address recipient) external { selfdestruct(payable(recipient)); } } contract DemoOne { constructor() { } fallback() external payable { revert("You can not send ether"); } receive() external payable { revert("You can not send ether"); } function balance() external view returns (uint) { return address(this).balance; } } DemoOne 合约通过 fallback 和 receive 的限制禁止接收 ETH。我们部署并转一点 ETH 试试:交易失败,报错原因也是我们设定的信息。现在我们通过 DestructTransfer 来强行转入 ETH。部署合约的同时打入 1 ETH,并且调用 destruct 函数,参数为 DemoOne 的地址,交易成功后查看 DemoOne 的余额:可以看到即使合约中有接收限制,我们仍然将 ETH 强行打入了合约中。 实际上,一共有两种方法来强行向一个地址转入 ETH:ETH 挖矿时区块头中的 coinbase 字段设置为接收地址selfdestruct 的参数地址设置为接收地址关于强行转入 ETH 这个话题,还有一些黑客游戏就是利用这个原理,比如这道 Ethernaut 中的题目。总结selfdestruct 是可以销毁合约的代码和数据,接收一个参数,销毁时会将合约中剩余的 ETH 全部打入改地址,并且是强制性的。selfdestruct 与 create2 结合可以实现在同一个地址上多次部署合约。参考https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html#deactivate-and-self-destructWhat happens after selfdestruct is called?https://ropsten.etherscan.io/address/0xf825d3b3d06a4b46379a3c276df7f26abd055463 After selfdestruct call was made, all the balance was transferred to the deployer of the contract. Why are withdrawhttps://ethereum.stackexchange.com ## Publication Information - [xyyme.eth](https://paragraph.com/@xyyme/): Publication homepage - [All Posts](https://paragraph.com/@xyyme/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@xyyme): Subscribe to updates - [Twitter](https://twitter.com/xyymeeth): Follow on Twitter