智能合约多签钱包开发实战:从零构建安全的多签机制

多签钱包智能合约开发实战,展示M-of-N签名机制与交易审批流程

在加密货币世界里,资产安全始终是首要命题。对于个人投资者而言,助记词丢失是最大的风险来源;对于项目方、DAO金库或机构投资者而言,单点故障更是不可接受的安全隐患。多签钱包(Multi-Signature Wallet)正是为解决这些问题而生的核心基础设施。

Gnosis Safe作为以太坊生态中最成功的多签钱包协议,管理着价值数百亿美元的资产。其设计理念和技术实现值得所有区块链开发者深入学习。本文将从零开始,完整实现一个功能完备的多签钱包合约。

多签交易工作流程图,展示提交、确认、执行三阶段的完整生命周期

多签钱包的核心设计原则

为什么需要多签?

传统单签钱包存在两个核心风险:

  1. 单点故障:私钥丢失或泄露意味着资产永久损失
  2. 权力集中:单一私钥持有者拥有完全控制权,无法实现权限分离

多签钱包通过”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治理模块,展示了多签合约的核心设计模式。

关键要点回顾:

  1. M-of-N机制:至少M个签名者确认才能执行,是多签的核心逻辑
  2. 事件驱动:所有状态变更必须发出事件,便于前端追踪和链下索引
  3. 修饰符复用:通过修饰符组合实现权限控制和状态验证
  4. 时间锁:为高风险操作引入延迟,提供安全保障和干预窗口
  5. 模块化:将功能分离到独立模块,便于扩展和维护

在实际生产环境中,建议直接使用经过审计的Gnosis Safe合约代码,而不是从零实现。但理解其内部原理对于安全审计和定制开发至关重要。

相关阅读

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注