在加密货币世界里,资产安全始终是首要命题。对于个人投资者而言,助记词丢失是最大的风险来源;对于项目方、DAO金库或机构投资者而言,单点故障更是不可接受的安全隐患。多签钱包(Multi-Signature Wallet)正是为解决这些问题而生的核心基础设施。
Gnosis Safe作为以太坊生态中最成功的多签钱包协议,管理着价值数百亿美元的资产。其设计理念和技术实现值得所有区块链开发者深入学习。本文将从零开始,完整实现一个功能完备的多签钱包合约。

多签钱包的核心设计原则
为什么需要多签?
传统单签钱包存在两个核心风险:
- 单点故障:私钥丢失或泄露意味着资产永久损失
- 权力集中:单一私钥持有者拥有完全控制权,无法实现权限分离
多签钱包通过”M-of-N”机制解决这些问题:N个私钥持有者中,至少需要M个确认才能执行交易。这意味着:
- 即使1个私钥丢失,只要M≤N-1,资产仍然安全
- 大额交易需要多人审批,避免内部欺诈
- 策略性地分配签名权,实现权限层级管理
Gnosis Safe的设计哲学
Gnosis Safe的多签设计有几个关键原则:
- 模块化:Safe本身是轻量核心,通过模块扩展功能
- 交易队列:所有交易必须经过确认队列,避免丢失或遗漏
- 执行延迟:可配置的延迟机制,给撤销操作留出窗口
- 邀请制:新签名者需要现有签名者集体确认
基础架构实现
合约结构概览
我们的多签钱包包含以下核心组件:
plaintext
MultiSigWallet
├── 状态变量:所有者列表、阈值、交易计数
├── 交易存储:pendingTransactions, executedTransactions
├── 核心函数:submitTransaction, confirmTransaction, executeTransaction
└── 事件:提交、确认、执行、撤销、所有者变更
基础版本实现
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title MultiSigWallet
* @notice 基础版多签钱包合约
* @dev 实现 M-of-N 多签机制
*/
contract MultiSigWallet {
// ============ 状态变量 ============
// 所有者地址列表
address[] public owners;
// 地址是否为所有者
mapping(address => bool) public isOwner;
// M-of-N: 需要多少个确认才能执行
uint256 public required;
// 交易计数
uint256 public transactionCount;
// 交易结构
struct Transaction {
address to; // 目标地址
uint256 value; // 转账金额(wei)
bytes data; // 调用数据
bool executed; // 是否已执行
uint256 numConfirmations; // 当前确认数
}
// 交易列表
mapping(uint256 => Transaction) public transactions;
// 交易确认记录:txId -> owner -> bool
mapping(uint256 => mapping(address => bool)) public confirmations;
// ============ 事件 ============
event SubmitTransaction(
address indexed owner,
uint256 indexed txIndex,
address indexed to,
uint256 value,
bytes data
);
event ConfirmTransaction(address indexed owner, uint256 indexed txIndex);
event RevokeConfirmation(address indexed owner, uint256 indexed txIndex);
event ExecuteTransaction(address indexed owner, uint256 indexed txIndex);
event OwnerAdded(address indexed newOwner);
event OwnerRemoved(address indexed removedOwner);
event RequirementChanged(uint256 newRequirement);
// ============ 修饰符 ============
modifier onlyOwner() {
require(isOwner[msg.sender], "Not an owner");
_;
}
modifier txExists(uint256 _txIndex) {
require(_txIndex < transactionCount, "Transaction does not exist");
_;
}
modifier notExecuted(uint256 _txIndex) {
require(!transactions[_txIndex].executed, "Already executed");
_;
}
modifier notConfirmed(uint256 _txIndex) {
require(!confirmations[_txIndex][msg.sender], "Already confirmed");
_;
}
// ============ 构造函数 ============
constructor(address[] memory _owners, uint256 _required) {
require(_owners.length > 0, "Owners required");
require(
_required > 0 && _required <= _owners.length,
"Invalid required number"
);
for (uint256 i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "Invalid owner");
require(!isOwner[owner], "Owner not unique");
isOwner[owner] = true;
owners.push(owner);
}
required = _required;
}
// ============ 核心函数 ============
/**
* @notice 提交新交易
*/
function submitTransaction(address _to, uint256 _value, bytes memory _data)
public
onlyOwner
returns (uint256 txIndex)
{
txIndex = transactionCount;
transactions[txIndex] = Transaction({
to: _to,
value: _value,
data: _data,
executed: false,
numConfirmations: 0
});
transactionCount++;
emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data);
// 自动确认自己的交易
confirmTransaction(txIndex);
}
/**
* @notice 确认交易
*/
function confirmTransaction(uint256 _txIndex)
public
onlyOwner
txExists(_txIndex)
notExecuted(_txIndex)
notConfirmed(_txIndex)
{
confirmations[_txIndex][msg.sender] = true;
transactions[_txIndex].numConfirmations++;
emit ConfirmTransaction(msg.sender, _txIndex);
// 检查是否达到执行阈值
if (transactions[_txIndex].numConfirmations >= required) {
executeTransaction(_txIndex);
}
}
/**
* @notice 执行交易
*/
function executeTransaction(uint256 _txIndex)
public
onlyOwner
txExists(_txIndex)
notExecuted(_txIndex)
{
Transaction storage transaction = transactions[_txIndex];
require(
transaction.numConfirmations >= required,
"Not enough confirmations"
);
transaction.executed = true;
(bool success, ) = transaction.to.call{value: transaction.value}(
transaction.data
);
require(success, "Transaction execution failed");
emit ExecuteTransaction(msg.sender, _txIndex);
}
/**
* @notice 撤销确认
*/
function revokeConfirmation(uint256 _txIndex)
public
onlyOwner
txExists(_txIndex)
notExecuted(_txIndex)
{
require(confirmations[_txIndex][msg.sender], "Not confirmed");
confirmations[_txIndex][msg.sender] = false;
transactions[_txIndex].numConfirmations--;
emit RevokeConfirmation(msg.sender, _txIndex);
}
// ============ 所有者管理 ============
/**
* @notice 添加所有者
*/
function addOwner(address _newOwner) public onlyOwner {
require(_newOwner != address(0), "Invalid address");
require(!isOwner[_newOwner], "Already an owner");
isOwner[_newOwner] = true;
owners.push(_newOwner);
// 如果新的所有者数量超过阈值,自动调整阈值
if (required < owners.length) {
required++;
}
emit OwnerAdded(_newOwner);
}
/**
* @notice 移除所有者
*/
function removeOwner(address _owner) public onlyOwner {
require(owners.length > 1, "Cannot remove last owner");
require(isOwner[_owner], "Not an owner");
require(required <= owners.length - 1, "Invalid required value");
isOwner[_owner] = false;
// 从数组中移除
address[] memory newOwners = new address[](owners.length - 1);
uint256 index = 0;
for (uint256 i = 0; i < owners.length; i++) {
if (owners[i] != _owner) {
newOwners[index] = owners[i];
index++;
}
}
owners = newOwners;
emit OwnerRemoved(_owner);
}
/**
* @notice 修改阈值
*/
function changeRequirement(uint256 _newRequired) public onlyOwner {
require(
_newRequired > 0 && _newRequired <= owners.length,
"Invalid required value"
);
required = _newRequired;
emit RequirementChanged(_newRequired);
}
// ============ 查询函数 ============
function getOwners() public view returns (address[] memory) {
return owners;
}
function getTransactionCount() public view returns (uint256) {
return transactionCount;
}
function getConfirmations(uint256 _txIndex)
public
view
returns (address[] memory)
{
address[] memory confirmers = new address[](
transactions[_txIndex].numConfirmations
);
uint256 index = 0;
for (uint256 i = 0; i < owners.length; i++) {
if (confirmations[_txIndex][owners[i]]) {
confirmers[index] = owners[i];
index++;
}
}
return confirmers;
}
function getPendingTransactions()
public
view
returns (Transaction[] memory)
{
uint256 count = 0;
for (uint256 i = 0; i < transactionCount; i++) {
if (!transactions[i].executed) {
count++;
}
}
Transaction[] memory pending = new Transaction[](count);
uint256 index = 0;
for (uint256 i = 0; i < transactionCount; i++) {
if (!transactions[i].executed) {
pending[index] = transactions[i];
index++;
}
}
return pending;
}
// ============ 接收ETH ============
receive() external payable {}
}
增强版本:带时间锁的多签钱包
时间锁机制
基础多签钱包的一个缺陷是:一旦交易达到M个确认,可以立即执行。这意味着恶意签名者可以在”观察者”发现异常前完成攻击。
时间锁机制通过引入延迟期来解决这个问题——交易提交后必须等待一定时间才能执行,给了各方撤销和干预的机会。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title TimelockedMultiSig
* @notice 带时间锁的多签钱包
*/
contract TimelockedMultiSig {
// ============ 时间锁配置 ============
// 时间锁延迟(秒)
uint256 public timelockDelay;
// 每个操作类型可配置的延迟
mapping(bytes32 => uint256) public operationDelays;
// 交易状态
enum TransactionState {
None,
Submitted,
Confirmed,
AwaitingExecution,
Executed,
Cancelled
}
struct TimelockedTransaction {
address to;
uint256 value;
bytes data;
bytes32 operationId;
TransactionState state;
uint256 submissionTime;
uint256 requiredAfterTime;
uint256 numConfirmations;
address submitter;
address[] confirmers;
}
// ============ 交易存储 ============
uint256 public transactionCount;
mapping(uint256 => TimelockedTransaction) public transactions;
mapping(uint256 => mapping(address => bool)) public confirmations;
mapping(uint256 => mapping(address => bool)) public rejections;
// ============ 事件 ============
event TransactionSubmitted(
uint256 indexed txId,
address indexed submitter,
address to,
uint256 value,
bytes32 operationId
);
event TransactionConfirmed(
uint256 indexed txId,
address indexed confirmer
);
event TransactionRejected(
uint256 indexed txId,
address indexed rejecter
);
event TransactionReadyForExecution(
uint256 indexed txId,
uint256 executableAfterTime
);
event TransactionExecuted(uint256 indexed txId);
event TransactionCancelled(uint256 indexed txId);
event TimelockDelayChanged(uint256 oldDelay, uint256 newDelay);
// ============ 修饰符 ============
modifier onlyOwner() {
require(isOwner[msg.sender], "Not owner");
_;
}
modifier txExists(uint256 txId) {
require(txId < transactionCount, "Tx not found");
_;
}
modifier onlyPending(uint256 txId) {
require(
transactions[txId].state == TransactionState.Submitted ||
transactions[txId].state == TransactionState.Confirmed,
"Tx not pending"
);
_;
}
// 所有者相关
address[] public owners;
mapping(address => bool) public isOwner;
uint256 public required;
// ============ 构造函数 ============
constructor(
address[] memory _owners,
uint256 _required,
uint256 _timelockDelay
) {
require(_owners.length > 0, "No owners");
require(
_required > 0 && _required <= _owners.length,
"Invalid required"
);
for (uint256 i = 0; i < _owners.length; i++) {
require(!isOwner[_owners[i]], "Duplicate owner");
isOwner[_owners[i]] = true;
owners.push(_owners[i]);
}
required = _required;
timelockDelay = _timelockDelay;
// 设置默认操作延迟
operationDelays[bytes32(0)] = _timelockDelay; // 默认
}
// ============ 交易生命周期 ============
/**
* @notice 提交交易
*/
function submitTransaction(
address _to,
uint256 _value,
bytes calldata _data,
bytes32 _operationId
) external onlyOwner returns (uint256 txId) {
txId = transactionCount++;
uint256 delay = operationDelays[_operationId];
if (delay == 0) {
delay = timelockDelay;
}
transactions[txId] = TimelockedTransaction({
to: _to,
value: _value,
data: _data,
operationId: _operationId,
state: TransactionState.Submitted,
submissionTime: block.timestamp,
requiredAfterTime: block.timestamp + delay,
numConfirmations: 1,
submitter: msg.sender,
confirmers: new address[](0)
});
confirmations[txId][msg.sender] = true;
emit TransactionSubmitted(txId, msg.sender, _to, _value, _operationId);
}
/**
* @notice 确认交易
*/
function confirmTransaction(uint256 txId)
external
onlyOwner
txExists(txId)
onlyPending(txId)
{
require(!confirmations[txId][msg.sender], "Already confirmed");
require(!rejections[txId][msg.sender], "Already rejected");
confirmations[txId][msg.sender] = true;
transactions[txId].confirmers.push(msg.sender);
transactions[txId].numConfirmations++;
emit TransactionConfirmed(txId, msg.sender);
// 检查是否达到确认阈值
if (transactions[txId].numConfirmations >= required) {
_transitionToAwaitingExecution(txId);
}
}
/**
* @notice 否决交易
*/
function rejectTransaction(uint256 txId)
external
onlyOwner
txExists(txId)
onlyPending(txId)
{
require(!rejections[txId][msg.sender], "Already rejected");
rejections[txId][msg.sender] = true;
// 如果有足够多的否决,可以直接取消
uint256 rejectionCount = 0;
for (uint256 i = 0; i < owners.length; i++) {
if (rejections[txId][owners[i]]) {
rejectionCount++;
}
}
if (rejectionCount >= owners.length - required + 1) {
transactions[txId].state = TransactionState.Cancelled;
emit TransactionCancelled(txId);
}
emit TransactionRejected(txId, msg.sender);
}
/**
* @notice 过渡到可执行状态
*/
function _transitionToAwaitingExecution(uint256 txId) internal {
transactions[txId].state = TransactionState.AwaitingExecution;
emit TransactionReadyForExecution(
txId,
transactions[txId].requiredAfterTime
);
}
/**
* @notice 执行交易(需等待时间锁)
*/
function executeTransaction(uint256 txId)
external
onlyOwner
txExists(txId)
{
TimelockedTransaction storage tx = transactions[txId];
require(
tx.state == TransactionState.AwaitingExecution,
"Not ready for execution"
);
require(
block.timestamp >= tx.requiredAfterTime,
"Time lock not expired"
);
tx.state = TransactionState.Executed;
(bool success, ) = tx.to.call{value: tx.value}(tx.data);
require(success, "Execution failed");
emit TransactionExecuted(txId);
}
/**
* @notice 取消交易
*/
function cancelTransaction(uint256 txId)
external
onlyOwner
txExists(txId)
{
TimelockedTransaction storage tx = transactions[txId];
require(
tx.state == TransactionState.Submitted ||
tx.state == TransactionState.Confirmed ||
tx.state == TransactionState.AwaitingExecution,
"Cannot cancel"
);
tx.state = TransactionState.Cancelled;
emit TransactionCancelled(txId);
}
// ============ 管理函数 ============
/**
* @notice 设置时间锁延迟
*/
function setTimelockDelay(uint256 _newDelay) external onlyOwner {
require(_newDelay >= 1 days, "Delay too short");
uint256 oldDelay = timelockDelay;
timelockDelay = _newDelay;
emit TimelockDelayChanged(oldDelay, _newDelay);
}
/**
* @notice 设置特定操作的延迟
*/
function setOperationDelay(bytes32 operationId, uint256 delay)
external
onlyOwner
{
operationDelays[operationId] = delay;
}
/**
* @notice 紧急执行(绕过时间锁,仅紧急情况使用)
*/
function emergencyExecute(
address _to,
uint256 _value,
bytes calldata _data
) external onlyOwner {
require(owners.length >= 3, "Need at least 3 owners for emergency");
// 紧急执行需要所有所有者的确认
uint256 emergencyConfirmations = 0;
for (uint256 i = 0; i < owners.length; i++) {
if (isOwner[owners[i]]) {
emergencyConfirmations++;
}
}
require(emergencyConfirmations == owners.length, "Need all confirmations");
(bool success, ) = _to.call{value: _value}(_data);
require(success, "Emergency execution failed");
}
// ============ 查询函数 ============
function getOwners() external view returns (address[] memory) {
return owners;
}
function getTransaction(uint256 txId)
external
view
returns (TimelockedTransaction memory)
{
return transactions[txId];
}
function getPendingTransactions() external view returns (uint256[] memory) {
uint256 count = 0;
for (uint256 i = 0; i < transactionCount; i++) {
if (transactions[i].state != TransactionState.Executed &&
transactions[i].state != TransactionState.Cancelled) {
count++;
}
}
uint256[] memory pending = new uint256[](count);
uint256 index = 0;
for (uint256 i = 0; i < transactionCount; i++) {
if (transactions[i].state != TransactionState.Executed &&
transactions[i].state != TransactionState.Cancelled) {
pending[index] = i;
index++;
}
}
return pending;
}
// ============ 接收ETH ============
receive() external payable {}
}
模块化扩展:DAO治理集成
模块化设计的重要性
Gnosis Safe的核心设计哲学是模块化。Safe本身只处理多签逻辑,所有额外功能(如角色管理、ERC20代币支持、DAO集成)都通过模块实现。
让我们实现一个DAO治理模块,允许代币持有者参与多签决策:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title GovernanceModule
* @notice DAO治理模块:允许代币持有者投票决定多签操作
*/
contract GovernanceModule {
// ============ 状态 ============
// 主钱包合约引用
address public multiSigWallet;
// 治理代币
address public governanceToken;
// 提案结构
struct Proposal {
address proposer;
address to;
uint256 value;
bytes data;
string description;
uint256 startTime;
uint256 duration;
uint256 forVotes;
uint256 againstVotes;
bool executed;
bool cancelled;
mapping(address => bool) hasVoted;
mapping(address => uint256) voteWeight;
}
// 提案列表
mapping(uint256 => Proposal) public proposals;
uint256 public proposalCount;
// 提案状态
enum ProposalState {
Pending,
Active,
Succeeded,
Defeated,
Executed,
Cancelled
}
// 投票门槛
uint256 public votingThreshold;
uint256 public votingDuration;
// ============ 事件 ============
event ProposalCreated(
uint256 indexed proposalId,
address indexed proposer,
address to,
uint256 value,
string description
);
event VoteCast(
uint256 indexed proposalId,
address indexed voter,
bool support,
uint256 weight
);
event ProposalExecuted(uint256 indexed proposalId);
event ProposalCancelled(uint256 indexed proposalId);
// ============ 修饰符 ============
modifier onlyGovernance() {
require(msg.sender == multiSigWallet, "Only wallet");
_;
}
// ============ 构造函数 ============
constructor(
address _multiSigWallet,
address _governanceToken,
uint256 _votingThreshold,
uint256 _votingDuration
) {
multiSigWallet = _multiSigWallet;
governanceToken = _governanceToken;
votingThreshold = _votingThreshold;
votingDuration = _votingDuration;
}
// ============ 核心函数 ============
/**
* @notice 创建治理提案
*/
function createProposal(
address _to,
uint256 _value,
bytes calldata _data,
string calldata _description
) external returns (uint256 proposalId) {
require(bytes(_description).length > 0, "Empty description");
proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.proposer = msg.sender;
proposal.to = _to;
proposal.value = _value;
proposal.data = _data;
proposal.description = _description;
proposal.startTime = block.timestamp;
proposal.duration = votingDuration;
emit ProposalCreated(proposalId, msg.sender, _to, _value, _description);
}
/**
* @notice 投票
*/
function castVote(uint256 proposalId, bool support) external {
Proposal storage proposal = proposals[proposalId];
require(
block.timestamp >= proposal.startTime &&
block.timestamp < proposal.startTime + proposal.duration,
"Voting not active"
);
require(!proposal.hasVoted[msg.sender], "Already voted");
// 获取投票权重(持有代币数量)
uint256 weight = IERC20(governanceToken).balanceOf(msg.sender);
require(weight >= votingThreshold, "Below voting threshold");
proposal.hasVoted[msg.sender] = true;
proposal.voteWeight[msg.sender] = weight;
if (support) {
proposal.forVotes += weight;
} else {
proposal.againstVotes += weight;
}
emit VoteCast(proposalId, msg.sender, support, weight);
}
/**
* @notice 查询提案状态
*/
function getProposalState(uint256 proposalId)
public
view
returns (ProposalState)
{
Proposal storage proposal = proposals[proposalId];
if (proposal.executed) {
return ProposalState.Executed;
}
if (proposal.cancelled) {
return ProposalState.Cancelled;
}
if (block.timestamp < proposal.startTime) {
return ProposalState.Pending;
}
if (block.timestamp < proposal.startTime + proposal.duration) {
return ProposalState.Active;
}
if (proposal.forVotes > proposal.againstVotes) {
return ProposalState.Succeeded;
}
return ProposalState.Defeated;
}
/**
* @notice 执行通过的提案
*/
function executeProposal(uint256 proposalId) external onlyGovernance {
require(
getProposalState(proposalId) == ProposalState.Succeeded,
"Proposal not succeeded"
);
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
(bool success, ) = proposal.to.call{value: proposal.value}(proposal.data);
require(success, "Execution failed");
emit ProposalExecuted(proposalId);
}
/**
* @notice 取消提案
*/
function cancelProposal(uint256 proposalId) external {
Proposal storage proposal = proposals[proposalId];
require(msg.sender == proposal.proposer, "Not proposer");
require(!proposal.executed, "Already executed");
proposal.cancelled = true;
emit ProposalCancelled(proposalId);
}
}
// IERC20接口
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
}
安全最佳实践
重入攻击防护
多签钱包经常处理ETH和代币转账,必须防范重入攻击:
solidity
// 安全转账模式
function safeTransfer(address token, address to, uint256 amount) internal {
(bool success, bytes memory data) = token.call(
abi.encodeWithSelector(
IERC20.transfer.selector,
to,
amount
)
);
require(success && (data.length == 0 || abi.decode(data, (bool))),
"Transfer failed");
}
// 遵循Checks-Effects-Interactions模式
function executeTransactionSafe(uint256 txId) internal {
Transaction storage tx = transactions[txId];
// Checks
require(tx.numConfirmations >= required, "Not enough confirmations");
require(!tx.executed, "Already executed");
// Effects
tx.executed = true;
// Interactions(最后执行外部调用)
(bool success, ) = tx.to.call{value: tx.value}(tx.data);
require(success, "Execution failed");
}
交易队列管理
对于高频多签操作,建议实现交易队列:
solidity
// 交易队列:避免交易丢失或冲突
contract TransactionQueue {
struct QueuedTransaction {
address to;
uint256 value;
bytes data;
uint256 nonce; // 唯一标识,防止重放
uint256 expiresAt;
bytes32 txHash;
}
mapping(bytes32 => QueuedTransaction) public queue;
mapping(address => uint256) public nonces;
function queueTransaction(
address _to,
uint256 _value,
bytes calldata _data,
uint256 _expiration
) internal returns (bytes32 txHash) {
uint256 nonce = nonces[msg.sender]++;
txHash = keccak256(abi.encodePacked(
msg.sender,
nonce,
_to,
_value,
_data
));
queue[txHash] = QueuedTransaction({
to: _to,
value: _value,
data: _data,
nonce: nonce,
expiresAt: block.timestamp + _expiration,
txHash: txHash
});
}
function executeFromQueue(bytes32 txHash) internal {
QueuedTransaction storage qt = queue[txHash];
require(qt.expiresAt > block.timestamp, "Transaction expired");
delete queue[txHash];
(bool success, ) = qt.to.call{value: qt.value}(qt.data);
require(success, "Execution failed");
}
}
测试与验证
Hardhat测试套件
javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-network-helpers");
describe("MultiSigWallet", function () {
let wallet;
let owners;
let required = 2;
beforeEach(async () => {
[owner1, owner2, owner3, recipient] = await ethers.getSigners();
owners = [owner1.address, owner2.address, owner3.address];
const MultiSig = await ethers.getContractFactory("MultiSigWallet");
wallet = await MultiSig.deploy(owners, required);
await wallet.deployed();
// 预存一些ETH用于测试
await owner1.sendTransaction({
to: wallet.address,
value: ethers.utils.parseEther("10")
});
});
it("Should accept deposits", async () => {
const balance = await ethers.provider.getBalance(wallet.address);
expect(balance).to.equal(ethers.utils.parseEther("10"));
});
it("Should submit and auto-confirm transaction", async () => {
const txIndex = 0;
await wallet.connect(owner1).submitTransaction(
recipient.address,
ethers.utils.parseEther("1"),
"0x"
);
const tx = await wallet.transactions(txIndex);
expect(tx.to).to.equal(recipient.address);
expect(tx.numConfirmations).to.equal(1);
});
it("Should execute transaction when threshold met", async () => {
await wallet.connect(owner1).submitTransaction(
recipient.address,
ethers.utils.parseEther("1"),
"0x"
);
await wallet.connect(owner2).confirmTransaction(0);
const recipientBalance = await ethers.provider.getBalance(recipient.address);
expect(recipientBalance).to.equal(ethers.utils.parseEther("1"));
});
it("Should prevent non-owner from confirming", async () => {
await wallet.connect(owner1).submitTransaction(
recipient.address,
ethers.utils.parseEther("1"),
"0x"
);
await expect(
wallet.connect(recipient).confirmTransaction(0)
).to.be.revertedWith("Not an owner");
});
it("Should allow owner removal", async () => {
await wallet.connect(owner1).removeOwner(owner3.address);
const isOwner3 = await wallet.isOwner(owner3.address);
expect(isOwner3).to.equal(false);
});
});
总结
多签钱包是区块链开发者的必修课题。本文从基础实现出发,逐步扩展到时间锁机制和DAO治理模块,展示了多签合约的核心设计模式。
关键要点回顾:
- M-of-N机制:至少M个签名者确认才能执行,是多签的核心逻辑
- 事件驱动:所有状态变更必须发出事件,便于前端追踪和链下索引
- 修饰符复用:通过修饰符组合实现权限控制和状态验证
- 时间锁:为高风险操作引入延迟,提供安全保障和干预窗口
- 模块化:将功能分离到独立模块,便于扩展和维护
在实际生产环境中,建议直接使用经过审计的Gnosis Safe合约代码,而不是从零实现。但理解其内部原理对于安全审计和定制开发至关重要。
相关阅读:

发表回复