Solidity里的自毁(Self-Destruct)功能,这可是智能合约里一个有点"狠"的功能,能让合约在区块链上"自我了断",彻底销毁自己,释放存储空间,还能把余额转给指定地址。不过,自毁功能是个双刃剑,用不好可能会引发安全问题,比如误操作、恶意攻击,甚至影响去中心化应用的可靠性。
自毁功能核心概念
先来搞明白几个关键点:
- 自毁(Self-Destruct) :Solidity提供的
selfdestruct函数,删除合约的字节码,清空存储,并将合约余额转给指定地址。 - 作用 :
- 清理:移除无用合约,释放区块链存储。
- 资金转移:将合约余额转到指定账户。
- 终止:停止合约功能,防止进一步操作。
- 安全风险 :
- 权限控制:谁能触发自毁?没有限制可能被恶意调用。
- 资金安全:余额转到错误地址怎么办?
- 不可逆:自毁后,合约无法恢复,数据全丢。
- Solidity 0.8.x :自带溢出/下溢检查,配合OpenZeppelin的
Ownable和ReentrancyGuard确保安全。 - Hardhat:开发和测试工具,方便部署和验证自毁行为。
- EVM :以太坊虚拟机,
selfdestruct在EVM层面销毁合约并返还部分gas。
咱们用Solidity 0.8.20,结合OpenZeppelin和Hardhat,从简单自毁到带权限控制、多签确认、延迟执行等场景,逐步实现安全的自毁功能。
环境准备
用Hardhat搭建开发环境,写和测试合约。
bash
mkdir selfdestruct-demo
cd selfdestruct-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts
npm install ethers
初始化Hardhat:
bash
npx hardhat init
选择TypeScript项目,安装依赖:
bash
npm install --save-dev ts-node typescript @types/node @types/mocha
目录结构:
lua
selfdestruct-demo/
├── contracts/
│ ├── SimpleSelfDestruct.sol
│ ├── SecureSelfDestruct.sol
│ ├── MultiSigSelfDestruct.sol
├── scripts/
│ ├── deploy.ts
├── test/
│ ├── SelfDestruct.test.ts
├── hardhat.config.ts
├── tsconfig.json
├── package.json
tsconfig.json:
json
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./"
},
"include": ["hardhat.config.ts", "scripts", "test"]
}
hardhat.config.ts:
typescript
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: "0.8.20",
networks: {
hardhat: {
chainId: 1337,
},
},
};
export default config;
跑本地节点:
bash
npx hardhat node
基础自毁合约
先写一个简单的自毁合约,理解selfdestruct的基本用法。
合约代码
contracts/SimpleSelfDestruct.sol:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleSelfDestruct {
address public owner;
uint256 public balance;
event Deposit(address indexed sender, uint256 amount);
event SelfDestruct(address indexed beneficiary, uint256 amount);
constructor() {
owner = msg.sender;
}
receive() external payable {
balance += msg.value;
emit Deposit(msg.sender, msg.value);
}
function destroy(address payable beneficiary) public {
require(msg.sender == owner, "Only owner can destroy");
emit SelfDestruct(beneficiary, address(this).balance);
selfdestruct(beneficiary);
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
解析
- 功能 :
owner:记录合约部署者。receive:接收ETH,更新balance。destroy:只有owner能调用,触发selfdestruct,将余额转给beneficiary。getBalance:查询合约余额。
- 自毁 :
selfdestruct(beneficiary):销毁合约字节码,将余额转给beneficiary。- 触发后,合约地址的代码和存储清空,后续调用失败。
- 安全特性 :
onlyOwner检查,限制自毁权限。- 事件日志记录存款和自毁操作。
测试
test/SelfDestruct.test.ts:
typescript
import { ethers } from "hardhat";
import { expect } from "chai";
import { SimpleSelfDestruct } from "../typechain-types";
describe("SimpleSelfDestruct", function () {
let contract: SimpleSelfDestruct;
let owner: any, addr1: any, addr2: any;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const ContractFactory = await ethers.getContractFactory("SimpleSelfDestruct");
contract = await ContractFactory.deploy();
await contract.deployed();
});
it("should initialize with owner", async function () {
expect(await contract.owner()).to.equal(owner.address);
});
it("should accept deposits", async function () {
await owner.sendTransaction({ to: contract.address, value: ethers.parseEther("1") });
expect(await contract.getBalance()).to.equal(ethers.parseEther("1"));
});
it("should allow owner to destroy", async function () {
await owner.sendTransaction({ to: contract.address, value: ethers.parseEther("1") });
const addr1BalanceBefore = await ethers.provider.getBalance(addr1.address);
await contract.destroy(addr1.address);
const addr1BalanceAfter = await ethers.provider.getBalance(addr1.address);
expect(addr1BalanceAfter.sub(addr1BalanceBefore)).to.equal(ethers.parseEther("1"));
const code = await ethers.provider.getCode(contract.address);
expect(code).to.equal("0x");
});
it("should restrict destroy to owner", async function () {
await expect(contract.connect(addr1).destroy(addr1.address)).to.be.revertedWith("Only owner can destroy");
});
it("should fail calls after destruction", async function () {
await contract.destroy(addr1.address);
await expect(contract.getBalance()).to.be.reverted;
});
});
跑测试:
bash
npx hardhat test
- 解析 :
- 部署:
owner为部署者。 - 存款:发送1 ETH到合约。
- 自毁:
owner调用destroy,余额转给addr1,合约代码清空。 - 权限:
addr1无法调用destroy。 - 销毁后:调用
getBalance失败,代码为0x。
- 部署:
防止重入攻击
自毁可能被恶意合约利用,触发重入攻击。
漏洞示例
contracts/VulnerableSelfDestruct.sol:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract VulnerableSelfDestruct {
address public owner;
uint256 public balance;
constructor() {
owner = msg.sender;
}
receive() external payable {
balance += msg.value;
}
function destroy(address payable beneficiary) public {
require(msg.sender == owner, "Only owner can destroy");
(bool success, ) = beneficiary.call{value: address(this).balance}("");
require(success, "Transfer failed");
selfdestruct(beneficiary);
}
}
攻击合约:
contracts/Attacker.sol:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./VulnerableSelfDestruct.sol";
contract Attacker {
VulnerableSelfDestruct public target;
uint256 public count;
constructor(address _target) {
target = VulnerableSelfDestruct(_target);
}
receive() external payable {
if (count < 2) {
count++;
target.destroy(payable(address(this)));
}
}
function attack() external {
target.destroy(payable(address(this)));
}
}
测试:
test/VulnerableSelfDestruct.test.ts:
typescript
import { ethers } from "hardhat";
import { expect } from "chai";
import { VulnerableSelfDestruct, Attacker } from "../typechain-types";
describe("VulnerableSelfDestruct", function () {
let contract: VulnerableSelfDestruct;
let attacker: Attacker;
let owner: any, attackerAddr: any;
beforeEach(async function () {
[owner, attackerAddr] = await ethers.getSigners();
const ContractFactory = await ethers.getContractFactory("VulnerableSelfDestruct");
contract = await ContractFactory.deploy();
await contract.deployed();
const AttackerFactory = await ethers.getContractFactory("Attacker");
attacker = await AttackerFactory.deploy(contract.address);
await attacker.deployed();
await owner.sendTransaction({ to: contract.address, value: ethers.parseEther("2") });
});
it("should be vulnerable to reentrancy", async function () {
const attackerBalanceBefore = await ethers.provider.getBalance(attacker.address);
await attacker.attack();
const attackerBalanceAfter = await ethers.provider.getBalance(attacker.address);
expect(attackerBalanceAfter.sub(attackerBalanceBefore)).to.equal(ethers.parseEther("2"));
});
});
- 问题 :
destroy在调用beneficiary.call后执行selfdestruct,攻击合约通过receive重复调用,多次提取余额。 - 结果:攻击者获得2 ETH,而应为1 ETH。
使用ReentrancyGuard
用OpenZeppelin的ReentrancyGuard修复。
contracts/SecureSelfDestruct.sol:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SecureSelfDestruct is ReentrancyGuard, Ownable {
uint256 public balance;
event Deposit(address indexed sender, uint256 amount);
event SelfDestruct(address indexed beneficiary, uint256 amount);
constructor() Ownable() {}
receive() external payable {
balance += msg.value;
emit Deposit(msg.sender, msg.value);
}
function destroy(address payable beneficiary) public onlyOwner nonReentrant {
require(beneficiary != address(0), "Invalid beneficiary");
uint256 amount = address(this).balance;
emit SelfDestruct(beneficiary, amount);
selfdestruct(beneficiary);
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
测试:
test/SecureSelfDestruct.test.ts:
typescript
import { ethers } from "hardhat";
import { expect } from "chai";
import { SecureSelfDestruct, Attacker } from "../typechain-types";
describe("SecureSelfDestruct", function () {
let contract: SecureSelfDestruct;
let attacker: Attacker;
let owner: any, attackerAddr: any;
beforeEach(async function () {
[owner, attackerAddr] = await ethers.getSigners();
const ContractFactory = await ethers.getContractFactory("SecureSelfDestruct");
contract = await ContractFactory.deploy();
await contract.deployed();
const AttackerFactory = await ethers.getContractFactory("Attacker");
attacker = await AttackerFactory.deploy(contract.address);
await attacker.deployed();
await owner.sendTransaction({ to: contract.address, value: ethers.parseEther("2") });
});
it("should prevent reentrancy", async function () {
await expect(attacker.attack()).to.be.revertedWith("ReentrancyGuard: reentrant call");
expect(await contract.getBalance()).to.equal(ethers.parseEther("2"));
});
it("should allow owner to destroy", async function () {
const attackerBalanceBefore = await ethers.provider.getBalance(attacker.address);
await contract.destroy(attacker.address);
const attackerBalanceAfter = await ethers.provider.getBalance(attacker.address);
expect(attackerBalanceAfter.sub(attackerBalanceBefore)).to.equal(ethers.parseEther("2"));
const code = await ethers.provider.getCode(contract.address);
expect(code).to.equal("0x");
});
it("should restrict destroy to owner", async function () {
await expect(contract.connect(attackerAddr).destroy(attacker.address)).to.be.revertedWith("Ownable: caller is not the owner");
});
it("should prevent invalid beneficiary", async function () {
await expect(contract.destroy(ethers.constants.AddressZero)).to.be.revertedWith("Invalid beneficiary");
});
});
- 修复 :
nonReentrant:锁住destroy,防止重入。Ownable:更简洁的权限控制。- 检查
beneficiary不为零地址。
- 结果:攻击失败,余额安全转移。
多签确认自毁
为自毁添加多签机制,需多人同意。
contracts/MultiSigSelfDestruct.sol:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MultiSigSelfDestruct is ReentrancyGuard {
address[] public owners;
uint256 public required;
uint256 public transactionCount;
mapping(uint256 => Transaction) public transactions;
mapping(uint256 => mapping(address => bool)) public confirmations;
uint256 public balance;
struct Transaction {
address payable beneficiary;
bool executed;
uint256 confirmationCount;
}
event Deposit(address indexed sender, uint256 amount);
event SubmitSelfDestruct(uint256 indexed txId, address indexed beneficiary);
event ConfirmSelfDestruct(uint256 indexed txId, address indexed owner);
event ExecuteSelfDestruct(uint256 indexed txId, address indexed beneficiary);
event RevokeConfirmation(uint256 indexed txId, address indexed owner);
modifier onlyOwner() {
bool isOwner = false;
for (uint256 i = 0; i < owners.length; i++) {
if (owners[i] == msg.sender) {
isOwner = true;
break;
}
}
require(isOwner, "Not owner");
_;
}
constructor(address[] memory _owners, uint256 _required) {
require(_owners.length > 0, "Owners required");
require(_required > 0 && _required <= _owners.length, "Invalid required confirmations");
owners = _owners;
required = _required;
}
receive() external payable {
balance += msg.value;
emit Deposit(msg.sender, msg.value);
}
function submitSelfDestruct(address payable beneficiary) public onlyOwner {
require(beneficiary != address(0), "Invalid beneficiary");
uint256 txId = transactionCount++;
transactions[txId] = Transaction({
beneficiary: beneficiary,
executed: false,
confirmationCount: 0
});
emit SubmitSelfDestruct(txId, beneficiary);
}
function confirmSelfDestruct(uint256 txId) public onlyOwner nonReentrant {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(!confirmations[txId][msg.sender], "Already confirmed");
confirmations[txId][msg.sender] = true;
transaction.confirmationCount++;
emit ConfirmSelfDestruct(txId, msg.sender);
if (transaction.confirmationCount >= required) {
executeSelfDestruct(txId);
}
}
function executeSelfDestruct(uint256 txId) internal nonReentrant {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmationCount >= required, "Insufficient confirmations");
transaction.executed = true;
emit ExecuteSelfDestruct(txId, transaction.beneficiary);
selfdestruct(transaction.beneficiary);
}
function revokeConfirmation(uint256 txId) public onlyOwner {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(confirmations[txId][msg.sender], "Not confirmed");
confirmations[txId][msg.sender] = false;
transaction.confirmationCount--;
emit RevokeConfirmation(txId, msg.sender);
}
function getTransaction(uint256 txId)
public
view
returns (address beneficiary, bool executed, uint256 confirmationCount)
{
Transaction memory transaction = transactions[txId];
return (transaction.beneficiary, transaction.executed, transaction.confirmationCount);
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
解析
- 多签机制 :
owners:所有者列表。required:所需确认数。transactions:存储自毁提案。confirmations:记录确认状态。
- 功能 :
submitSelfDestruct:提交自毁提案。confirmSelfDestruct:确认提案,达标后执行。executeSelfDestruct:触发selfdestruct。revokeConfirmation:撤销确认。
- 安全特性 :
onlyOwner:限制操作者。nonReentrant:防止重入。- 检查
beneficiary有效性。
测试
test/MultiSigSelfDestruct.test.ts:
typescript
import { ethers } from "hardhat";
import { expect } from "chai";
import { MultiSigSelfDestruct } from "../typechain-types";
describe("MultiSigSelfDestruct", function () {
let contract: MultiSigSelfDestruct;
let owner1: any, owner2: any, owner3: any, nonOwner: any;
beforeEach(async function () {
[owner1, owner2, owner3, nonOwner] = await ethers.getSigners();
const ContractFactory = await ethers.getContractFactory("MultiSigSelfDestruct");
contract = await ContractFactory.deploy([owner1.address, owner2.address, owner3.address], 2);
await contract.deployed();
await owner1.sendTransaction({ to: contract.address, value: ethers.parseEther("2") });
});
it("should initialize correctly", async function () {
expect(await contract.owners(0)).to.equal(owner1.address);
expect(await contract.required()).to.equal(2);
});
it("should allow submitting self-destruct", async function () {
await contract.submitSelfDestruct(nonOwner.address);
const [beneficiary, ,] = await contract.getTransaction(0);
expect(beneficiary).to.equal(nonOwner.address);
});
it("should restrict submit to owners", async function () {
await expect(contract.connect(nonOwner).submitSelfDestruct(nonOwner.address)).to.be.revertedWith("Not owner");
});
it("should allow confirming and executing self-destruct", async function () {
await contract.submitSelfDestruct(nonOwner.address);
const nonOwnerBalanceBefore = await ethers.provider.getBalance(nonOwner.address);
await contract.connect(owner2).confirmSelfDestruct(0);
await contract.connect(owner3).confirmSelfDestruct(0);
const nonOwnerBalanceAfter = await ethers.provider.getBalance(nonOwner.address);
expect(nonOwnerBalanceAfter.sub(nonOwnerBalanceBefore)).to.equal(ethers.parseEther("2"));
const code = await ethers.provider.getCode(contract.address);
expect(code).to.equal("0x");
});
it("should not execute without enough confirmations", async function () {
await contract.submitSelfDestruct(nonOwner.address);
await contract.connect(owner2).confirmSelfDestruct(0);
const [, executed,] = await contract.getTransaction(0);
expect(executed).to.be.false;
});
it("should allow revoking confirmation", async function () {
await contract.submitSelfDestruct(nonOwner.address);
await contract.connect(owner2).confirmSelfDestruct(0);
await contract.connect(owner2).revokeConfirmation(0);
await contract.connect(owner3).confirmSelfDestruct(0);
const [, executed,] = await contract.getTransaction(0);
expect(executed).to.be.false;
});
it("should prevent invalid beneficiary", async function () {
await expect(contract.submitSelfDestruct(ethers.constants.AddressZero)).to.be.revertedWith("Invalid beneficiary");
});
});
- 解析 :
- 部署:3个所有者,需2人确认。
- 提交:
owner1提交自毁提案。 - 确认:
owner2和owner3确认后执行,余额转给nonOwner。 - 撤销:
owner2撤销确认,阻止执行。
- 安全:多签机制防止单人误操作。
延迟执行自毁
添加延迟机制,防止立即销毁。
contracts/MultiSigSelfDestruct.sol(更新):
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MultiSigSelfDestruct is ReentrancyGuard {
address[] public owners;
uint256 public required;
uint256 public transactionCount;
uint256 public delayDuration = 1 days;
mapping(uint256 => Transaction) public transactions;
mapping(uint256 => mapping(address => bool)) public confirmations;
uint256 public balance;
struct Transaction {
address payable beneficiary;
bool executed;
uint256 confirmationCount;
uint256 confirmedAt;
}
event Deposit(address indexed sender, uint256 amount);
event SubmitSelfDestruct(uint256 indexed txId, address indexed beneficiary);
event ConfirmSelfDestruct(uint256 indexed txId, address indexed owner);
event ExecuteSelfDestruct(uint256 indexed txId, address indexed beneficiary);
event RevokeConfirmation(uint256 indexed txId, address indexed owner);
event SetDelayDuration(uint256 duration);
modifier onlyOwner() {
bool isOwner = false;
for (uint256 i = 0; i < owners.length; i++) {
if (owners[i] == msg.sender) {
isOwner = true;
break;
}
}
require(isOwner, "Not owner");
_;
}
constructor(address[] memory _owners, uint256 _required) {
require(_owners.length > 0, "Owners required");
require(_required > 0 && _required <= _owners.length, "Invalid required confirmations");
owners = _owners;
required = _required;
}
receive() external payable {
balance += msg.value;
emit Deposit(msg.sender, msg.value);
}
function setDelayDuration(uint256 duration) public onlyOwner {
delayDuration = duration;
emit SetDelayDuration(duration);
}
function submitSelfDestruct(address payable beneficiary) public onlyOwner {
require(beneficiary != address(0), "Invalid beneficiary");
uint256 txId = transactionCount++;
transactions[txId] = Transaction({
beneficiary: beneficiary,
executed: false,
confirmationCount: 0,
confirmedAt: 0
});
emit SubmitSelfDestruct(txId, beneficiary);
}
function confirmSelfDestruct(uint256 txId) public onlyOwner nonReentrant {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(!confirmations[txId][msg.sender], "Already confirmed");
confirmations[txId][msg.sender] = true;
transaction.confirmationCount++;
emit ConfirmSelfDestruct(txId, msg.sender);
if (transaction.confirmationCount >= required && transaction.confirmedAt == 0) {
transaction.confirmedAt = block.timestamp;
}
}
function executeSelfDestruct(uint256 txId) public onlyOwner nonReentrant {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmationCount >= required, "Insufficient confirmations");
require(transaction.confirmedAt > 0, "Not fully confirmed");
require(block.timestamp >= transaction.confirmedAt + delayDuration, "Delay not elapsed");
transaction.executed = true;
emit ExecuteSelfDestruct(txId, transaction.beneficiary);
selfdestruct(transaction.beneficiary);
}
function revokeConfirmation(uint256 txId) public onlyOwner {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(confirmations[txId][msg.sender], "Not confirmed");
confirmations[txId][msg.sender] = false;
transaction.confirmationCount--;
if (transaction.confirmationCount < required) {
transaction.confirmedAt = 0;
}
emit RevokeConfirmation(txId, msg.sender);
}
function getTransaction(uint256 txId)
public
view
returns (address beneficiary, bool executed, uint256 confirmationCount, uint256 confirmedAt)
{
Transaction memory transaction = transactions[txId];
return (transaction.beneficiary, transaction.executed, transaction.confirmationCount, transaction.confirmedAt);
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
解析
- 延迟机制 :
delayDuration:默认1天,可调整。confirmedAt:记录达到required确认的时间。executeSelfDestruct:检查延迟时间是否已过。
- 功能 :
setDelayDuration:调整延迟时间。confirmSelfDestruct:记录确认时间。revokeConfirmation:撤销确认重置时间。
- 安全特性 :
- 延迟防止立即销毁。
onlyOwner限制操作。nonReentrant防止重入。
测试
test/MultiSigSelfDestruct.test.ts(更新):
typescript
import { ethers } from "hardhat";
import { expect } from "chai";
import { MultiSigSelfDestruct } from "../typechain-types";
describe("MultiSigSelfDestruct", function () {
let contract: MultiSigSelfDestruct;
let owner1: any, owner2: any, owner3: any, nonOwner: any;
beforeEach(async function () {
[owner1, owner2, owner3, nonOwner] = await ethers.getSigners();
const ContractFactory = await ethers.getContractFactory("MultiSigSelfDestruct");
contract = await ContractFactory.deploy([owner1.address, owner2.address, owner3.address], 2);
await contract.deployed();
await owner1.sendTransaction({ to: contract.address, value: ethers.parseEther("2") });
});
it("should enforce delay", async function () {
await contract.submitSelfDestruct(nonOwner.address);
await contract.connect(owner2).confirmSelfDestruct(0);
await contract.connect(owner3).confirmSelfDestruct(0);
await expect(contract.executeSelfDestruct(0)).to.be.revertedWith("Delay not elapsed");
await ethers.provider.send("evm_increaseTime", [86400 + 1]);
const nonOwnerBalanceBefore = await ethers.provider.getBalance(nonOwner.address);
await contract.executeSelfDestruct(0);
const nonOwnerBalanceAfter = await ethers.provider.getBalance(nonOwner.address);
expect(nonOwnerBalanceAfter.sub(nonOwnerBalanceBefore)).to.equal(ethers.parseEther("2"));
const code = await ethers.provider.getCode(contract.address);
expect(code).to.equal("0x");
});
it("should allow setting delay duration", async function () {
await contract.setDelayDuration(3600); // 1 hour
expect(await contract.delayDuration()).to.equal(3600);
await contract.submitSelfDestruct(nonOwner.address);
await contract.connect(owner2).confirmSelfDestruct(0);
await contract.connect(owner3).confirmSelfDestruct(0);
await ethers.provider.send("evm_increaseTime", [3601]);
await contract.executeSelfDestruct(0);
const code = await ethers.provider.getCode(contract.address);
expect(code).to.equal("0x");
});
it("should reset confirmedAt on revoke", async function () {
await contract.submitSelfDestruct(nonOwner.address);
await contract.connect(owner2).confirmSelfDestruct(0);
await contract.connect(owner3).confirmSelfDestruct(0);
await contract.connect(owner2).revokeConfirmation(0);
const [, , , confirmedAt] = await contract.getTransaction(0);
expect(confirmedAt).to.equal(0);
});
it("should restrict delay setting to owners", async function () {
await expect(contract.connect(nonOwner).setDelayDuration(3600)).to.be.revertedWith("Not owner");
});
});
- 解析 :
- 延迟:需等待1天后执行。
- 设置延迟:调整为1小时,验证执行。
- 撤销:重置
confirmedAt,需重新确认。
- 安全:延迟机制增加保护层。
部署脚本
scripts/deploy.ts:
typescript
import { ethers } from "hardhat";
async function main() {
const [owner1, owner2, owner3] = await ethers.getSigners();
const SimpleFactory = await ethers.getContractFactory("SimpleSelfDestruct");
const simple = await SimpleFactory.deploy();
await simple.deployed();
console.log(`SimpleSelfDestruct deployed to: ${simple.address}`);
const SecureFactory = await ethers.getContractFactory("SecureSelfDestruct");
const secure = await SecureFactory.deploy();
await secure.deployed();
console.log(`SecureSelfDestruct deployed to: ${secure.address}`);
const MultiSigFactory = await ethers.getContractFactory("MultiSigSelfDestruct");
const multiSig = await MultiSigFactory.deploy([owner1.address, owner2.address, owner3.address], 2);
await multiSig.deployed();
console.log(`MultiSigSelfDestruct deployed to: ${multiSig.address}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
跑部署:
bash
npx hardhat run scripts/deploy.ts --network hardhat
- 解析:部署所有合约,记录地址。