Solidity里的自毁(Self-Destruct)功能,这可是智能合约里一个有点“狠”的功能,能让合约在区块链上“自我了断”,彻底销毁自己,释放存储空间,还能把余额转给指定地址。不过,自毁功能是个双刃剑,用不好可能会引发安全问题,比如误操作、恶意攻击,甚至影响去中心化应用的可靠性。自毁功能核心
Solidity里的自毁(Self-Destruct)功能,这可是智能合约里一个有点“狠”的功能,能让合约在区块链上“自我了断”,彻底销毁自己,释放存储空间,还能把余额转给指定地址。不过,自毁功能是个双刃剑,用不好可能会引发安全问题,比如误操作、恶意攻击,甚至影响去中心化应用的可靠性。
先来搞明白几个关键点:
selfdestruct
函数,删除合约的字节码,清空存储,并将合约余额转给指定地址。Ownable
和ReentrancyGuard
确保安全。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
为部署者。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
重复调用,多次提取余额。用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");
});
});
owner1
提交自毁提案。owner2
和owner3
确认后执行,余额转给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");
});
});
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
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!