Solidity自毁合约:让你的区块链代码优雅谢幕

Solidity里的自毁(Self-Destruct)功能,这可是智能合约里一个有点“狠”的功能,能让合约在区块链上“自我了断”,彻底销毁自己,释放存储空间,还能把余额转给指定地址。不过,自毁功能是个双刃剑,用不好可能会引发安全问题,比如误操作、恶意攻击,甚至影响去中心化应用的可靠性。自毁功能核心

Solidity里的自毁(Self-Destruct)功能,这可是智能合约里一个有点“狠”的功能,能让合约在区块链上“自我了断”,彻底销毁自己,释放存储空间,还能把余额转给指定地址。不过,自毁功能是个双刃剑,用不好可能会引发安全问题,比如误操作、恶意攻击,甚至影响去中心化应用的可靠性。

自毁功能核心概念

先来搞明白几个关键点:

  • 自毁(Self-Destruct):Solidity提供的selfdestruct函数,删除合约的字节码,清空存储,并将合约余额转给指定地址。
  • 作用
    • 清理:移除无用合约,释放区块链存储。
    • 资金转移:将合约余额转到指定账户。
    • 终止:停止合约功能,防止进一步操作。
  • 安全风险
    • 权限控制:谁能触发自毁?没有限制可能被恶意调用。
    • 资金安全:余额转到错误地址怎么办?
    • 不可逆:自毁后,合约无法恢复,数据全丢。
  • Solidity 0.8.x:自带溢出/下溢检查,配合OpenZeppelin的OwnableReentrancyGuard确保安全。
  • Hardhat:开发和测试工具,方便部署和验证自毁行为。
  • EVM:以太坊虚拟机,selfdestruct在EVM层面销毁合约并返还部分gas。

咱们用Solidity 0.8.20,结合OpenZeppelin和Hardhat,从简单自毁到带权限控制、多签确认、延迟执行等场景,逐步实现安全的自毁功能。

环境准备

用Hardhat搭建开发环境,写和测试合约。

mkdir selfdestruct-demo
cd selfdestruct-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts
npm install ethers

初始化Hardhat:

npx hardhat init

选择TypeScript项目,安装依赖:

npm install --save-dev ts-node typescript @types/node @types/mocha

目录结构:

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

{
  "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

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
  solidity: "0.8.20",
  networks: {
    hardhat: {
      chainId: 1337,
    },
  },
};

export default config;

跑本地节点:

npx hardhat node

基础自毁合约

先写一个简单的自毁合约,理解selfdestruct的基本用法。

合约代码

contracts/SimpleSelfDestruct.sol

// 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

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

跑测试:

npx hardhat test
  • 解析
    • 部署:owner为部署者。
    • 存款:发送1 ETH到合约。
    • 自毁:owner调用destroy,余额转给addr1,合约代码清空。
    • 权限:addr1无法调用destroy
    • 销毁后:调用getBalance失败,代码为0x

防止重入攻击

自毁可能被恶意合约利用,触发重入攻击。

漏洞示例

contracts/VulnerableSelfDestruct.sol

// 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

// 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

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

// 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

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

// 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

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提交自毁提案。
    • 确认:owner2owner3确认后执行,余额转给nonOwner
    • 撤销:owner2撤销确认,阻止执行。
  • 安全:多签机制防止单人误操作。

延迟执行自毁

添加延迟机制,防止立即销毁。

contracts/MultiSigSelfDestruct.sol(更新):

// 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(更新):

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

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

跑部署:

npx hardhat run scripts/deploy.ts --network hardhat
  • 解析:部署所有合约,记录地址。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
天涯学馆
天涯学馆
0x9d6d...50d5
资深大厂程序员,12年开发经验,致力于探索前沿技术!