跳转到内容

多签钱包

多签钱包

本页内容正在整理中,欢迎贡献

概述

多签钱包(Multi-Signature Wallet)要求多个账户中的 M 个共同签名才能执行操作,常用于 DAO 资金管理、团队共管账户等场景。本教程实现一个 M-of-N 多签钱包合约。

主要内容

工作原理

M-of-N 多签(例如 2-of-3):
- 3 个 Owner:Alice、Bob、Carol
- 需要任意 2 个 Owner 确认才能执行交易
流程:
1. 任意 Owner 提交交易(propose)
2. 其他 Owner 确认(confirm)
3. 达到 M 个确认后,任意人可执行
4. 执行失败/不需要时可以撤销确认或吊销提交

合约实现

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract MultisigWallet {
event TransactionSubmitted(uint256 indexed txId, address indexed proposer, address to, uint256 value, bytes data);
event TransactionConfirmed(uint256 indexed txId, address indexed owner);
event TransactionRevoked(uint256 indexed txId, address indexed owner);
event TransactionExecuted(uint256 indexed txId);
event OwnerAdded(address indexed owner);
event OwnerRemoved(address indexed owner);
struct Transaction {
address to;
uint256 value;
bytes data;
bool executed;
uint256 confirmations;
}
address[] public owners;
mapping(address => bool) public isOwner;
uint256 public required; // 需要的最少签名数
Transaction[] public transactions;
// txId => owner => 是否已确认
mapping(uint256 => mapping(address => bool)) public hasConfirmed;
error NotOwner();
error TxNotExists();
error AlreadyExecuted();
error AlreadyConfirmed();
error NotConfirmed();
error InsufficientConfirmations();
error ExecutionFailed();
modifier onlyOwner() {
if (!isOwner[msg.sender]) revert NotOwner();
_;
}
modifier txExists(uint256 txId) {
if (txId >= transactions.length) revert TxNotExists();
_;
}
modifier notExecuted(uint256 txId) {
if (transactions[txId].executed) revert AlreadyExecuted();
_;
}
constructor(address[] memory _owners, uint256 _required) {
require(_owners.length >= _required && _required > 0, "Invalid params");
for (uint256 i = 0; i < _owners.length; ) {
address owner = _owners[i];
require(owner != address(0) && !isOwner[owner], "Invalid owner");
isOwner[owner] = true;
owners.push(owner);
unchecked { ++i; }
}
required = _required;
}
receive() external payable {}
// 提交交易
function submitTransaction(
address to,
uint256 value,
bytes calldata data
) external onlyOwner returns (uint256 txId) {
txId = transactions.length;
transactions.push(Transaction({
to: to,
value: value,
data: data,
executed: false,
confirmations: 0
}));
emit TransactionSubmitted(txId, msg.sender, to, value, data);
}
// 确认交易
function confirmTransaction(uint256 txId)
external onlyOwner txExists(txId) notExecuted(txId)
{
if (hasConfirmed[txId][msg.sender]) revert AlreadyConfirmed();
hasConfirmed[txId][msg.sender] = true;
transactions[txId].confirmations++;
emit TransactionConfirmed(txId, msg.sender);
}
// 撤销确认
function revokeConfirmation(uint256 txId)
external onlyOwner txExists(txId) notExecuted(txId)
{
if (!hasConfirmed[txId][msg.sender]) revert NotConfirmed();
hasConfirmed[txId][msg.sender] = false;
transactions[txId].confirmations--;
emit TransactionRevoked(txId, msg.sender);
}
// 执行交易
function executeTransaction(uint256 txId)
external onlyOwner txExists(txId) notExecuted(txId)
{
Transaction storage tx_ = transactions[txId];
if (tx_.confirmations < required) revert InsufficientConfirmations();
tx_.executed = true;
(bool success,) = tx_.to.call{value: tx_.value}(tx_.data);
if (!success) revert ExecutionFailed();
emit TransactionExecuted(txId);
}
// 查询函数
function getOwners() external view returns (address[] memory) {
return owners;
}
function getTransactionCount() external view returns (uint256) {
return transactions.length;
}
function getTransaction(uint256 txId) external view returns (
address to, uint256 value, bytes memory data, bool executed, uint256 confirmations
) {
Transaction storage t = transactions[txId];
return (t.to, t.value, t.data, t.executed, t.confirmations);
}
}

测试

describe("MultisigWallet", function () {
async function deployMultisigFixture() {
const [alice, bob, carol, dave] = await ethers.getSigners();
const Multisig = await ethers.getContractFactory("MultisigWallet");
// 3-of-3 多签(alice, bob, carol)
const multisig = await Multisig.deploy(
[alice.address, bob.address, carol.address],
2 // 需要 2 个签名
);
// 向多签合约存入 ETH
await alice.sendTransaction({ to: await multisig.getAddress(), value: parseEther("1") });
return { multisig, alice, bob, carol, dave };
}
it("应该需要足够确认才能执行", async function () {
const { multisig, alice, bob, carol } = await loadFixture(deployMultisigFixture);
// 提交转账交易
await multisig.connect(alice).submitTransaction(bob.address, parseEther("0.1"), "0x");
// 只有 1 个确认,应该失败
await multisig.connect(alice).confirmTransaction(0);
await expect(multisig.connect(alice).executeTransaction(0))
.to.be.revertedWithCustomError(multisig, "InsufficientConfirmations");
// 第 2 个确认,可以执行
await multisig.connect(bob).confirmTransaction(0);
await expect(multisig.connect(alice).executeTransaction(0))
.to.emit(multisig, "TransactionExecuted");
});
});

使用 Gnosis Safe

生产环境推荐使用经过安全审计的 Gnosis Safe

// 使用 Safe SDK 创建多签交易
import Safe, { EthersAdapter } from "@safe-global/protocol-kit";
const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer });
const safeSdk = await Safe.create({ ethAdapter, safeAddress: SAFE_ADDRESS });
const safeTransaction = await safeSdk.createTransaction({
transactions: [{
to: TARGET_ADDRESS,
value: "0",
data: encodedData,
}],
});
const signedTx = await safeSdk.signTransaction(safeTransaction);
await safeSdk.executeTransaction(signedTx);

深入阅读