3. 修改确认阈值

修改确认阈值 学习卡片

任务说明

本案例为多签钱包增加修改确认阈值功能:允许已批准的多签事务在达到当前阈值后,将所需确认数从旧值变更为新值。核心约束是——只有通过多签批准流程(而非单个所有者直接调用)才能完成修改,以此保证修改行为本身受到多签治理约束。

实现方案

在既有MultiSigWallet合约中新增changeRequirement函数,该函数内部调用_execute执行逻辑。由于_execute仅允许已提交且确认数达阈值的事务执行,因此修改确认数的操作天然被多签流程保护。新阈值必须满足1 <= newRequired <= ownerCount,否则回滚。

所有者 EOA (owner[0]) MultiSigWallet owners[] / required / txCount transactions[] / confirmations Transaction 结构 destination, value, data executed, confirmations newRequired 字段 1. submitTransaction (self, 0, data) 2. 内部生成 txId 确认收集 confirmTransaction(txId) 确认数 ≥ 旧阈值 required 3. 自动执行 _execute(txId) 校验: 未执行 && 确认数≥required 更新 required = newRequired 校验规则 1 ≤ newRequired ≤ ownerCount 仅多签批准可修改 data = abi.encodeWithSignature( "changeRequirement(uint256)", newReq)

合约代码

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract MultiSigWallet {
    struct Transaction {
        address destination;
        uint256 value;
        bytes data;
        bool executed;
        uint256 confirmations;
    }

    address[] public owners;
    mapping(address => bool) public isOwner;
    uint256 public required;
    Transaction[] public transactions;
    mapping(uint256 => mapping(address => bool)) public confirmations;

    event Confirmation(address indexed sender, uint256 indexed txId);
    event Execution(uint256 indexed txId);
    event RequirementChanged(uint256 newRequired);

    modifier onlyOwner() {
        require(isOwner[msg.sender], "not owner");
        _;
    }

    modifier txExists(uint256 txId) {
        require(txId < transactions.length, "tx not exist");
        _;
    }

    modifier notExecuted(uint256 txId) {
        require(!transactions[txId].executed, "tx executed");
        _;
    }

    modifier confirmed(uint256 txId) {
        require(transactions[txId].confirmations >= required, "not enough confirmations");
        _;
    }

    constructor(address[] memory _owners, uint256 _required) {
        require(_owners.length > 0, "owners required");
        require(_required > 0 && _required <= _owners.length, "invalid required");
        for (uint256 i = 0; i < _owners.length; i++) {
            address owner = _owners[i];
            require(owner != address(0), "invalid owner");
            require(!isOwner[owner], "duplicate owner");
            isOwner[owner] = true;
            owners.push(owner);
        }
        required = _required;
    }

    /// @notice 提交修改确认数的多签事务(data 编码 changeRequirement 调用)
    function submitChangeRequirement(uint256 newRequired) external onlyOwner returns (uint256 txId) {
        bytes memory data = abi.encodeWithSignature("changeRequirement(uint256)", newRequired);
        txId = _addTransaction(address(this), 0, data);
    }

    function confirmTransaction(uint256 txId)
        external
        onlyOwner
        txExists(txId)
        notExecuted(txId)
    {
        require(!confirmations[txId][msg.sender], "tx already confirmed");
        confirmations[txId][msg.sender] = true;
        transactions[txId].confirmations++;
        emit Confirmation(msg.sender, txId);

        // 达到阈值后自动执行
        if (transactions[txId].confirmations >= required) {
            _execute(txId);
        }
    }

    /// @notice 内部执行函数,仅由多签流程触发
    function _execute(uint256 txId) internal txExists(txId) notExecuted(txId) confirmed(txId) {
        Transaction storage txn = transactions[txId];
        txn.executed = true;
        (bool success, ) = txn.destination.call{value: txn.value}(txn.data);
        require(success, "tx failed");
        emit Execution(txId);
    }

    /// @notice 修改确认阈值(只能通过多签事务调用)
    function changeRequirement(uint256 newRequired) external {
        require(msg.sender == address(this), "only via multisig");
        require(newRequired > 0 && newRequired <= owners.length, "invalid new required");
        required = newRequired;
        emit RequirementChanged(newRequired);
    }

    function _addTransaction(address destination, uint256 value, bytes memory data)
        internal
        returns (uint256 txId)
    {
        txId = transactions.length;
        transactions.push(Transaction({
            destination: destination,
            value: value,
            data: data,
            executed: false,
            confirmations: 0
        }));
    }

    // 查询辅助
    function getOwners() external view returns (address[] memory) { return owners; }
    function getTransactionCount() external view returns (uint256) { return transactions.length; }
    function getRequired() external view returns (uint256) { return required; }
}

验证方法

部署与测试流程:

  1. 部署合约:owners = [0xA, 0xB, 0xC],required = 2。
  2. 所有者A调用submitChangeRequirement(3),获得txId = 0。
  3. 所有者B调用confirmTransaction(0)——此时确认数=2,达到旧阈值,事务自动执行,required变为3。
  4. 调用getRequired(),预期返回3。

判定标准:

  • 若所有者A直接调用changeRequirement(1),因msg.sender != address(this),交易回滚——证明修改只能通过多签批准。
  • 若提交newRequired = 0或newRequired = 4(超出所有者数),执行时回滚——证明阈值范围校验生效。
  • 确认数未达旧阈值(如仅A一人确认)时,required保持不变——证明修改未被提前执行。

预期输出:上述步骤3执行后,getRequired()返回3;步骤4中直接调用changeRequirement的尝试会触发revert异常。