Solidity 练习:多重签名钱包

2022-12-29 Web3 Solidity

# 一、题目说明

实现一个多重签名钱包。

# 二、任务列表

  1. 使合约能够接收 Ether,发送 Deposit 事件。
  2. 编写函数来提交交易,以供其他 owner 批准。
    • 函数 submit(address _to, uint _value, bytes calldata _data) external 把输入存储到 transactions 数组。
    • 提交 Submit 事件,其中 txId 等于存储在 transactions 中的下标。
    • 只有 owners 可以调用该函数。
  3. 编写函数去批准一个待处理交易。
    • 函数 approve(uint _txId) 批准一个 id 为 _txId 的待处理交易。
    • 该函数是 external 的。
    • 只有 owners 可以调用该函数。
    • id 为 _txId 的交易一定存在。
    • 交易一定还没有被执行。
    • 交易一定还没有被 msg.sender 批准。
    • 提交 Approve 事件。
  4. 编写函数去执行一个批准数量大于等于 required 的待处理交易。
    • 函数 execute(uint _txId) 执行交易 transcations[_txId]。
    • 该函数是 external 的。
    • 只有 owners 可以调用该函数。
    • id 为 _txId 的交易一定存在。
    • 交易一定还没有被执行。
    • 该交易的批准数量大于等于 required。
    • 提交 Execute 事件。
  5. 编写函数去撤销一个批准。
    • 函数 revoke(uint _txId) 撤销 msg.sender 对 _txId 交易的批准。
    • 该函数是 external 的。
    • 只有 owners 可以调用该函数。
    • id 为 _txId 的交易一定存在。
    • 交易一定还没有被执行。
    • 检查交易是否被 msg.sender 批准。
    • 提交 Revoke 事件。

# 三、解答代码

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

contract MultiSigWallet {
    event Deposit(address indexed sender, uint amount);
    event Submit(uint indexed txId);
    event Approve(address indexed owner, uint indexed txId);
    event Revoke(address indexed owner, uint indexed txId);
    event Execute(uint indexed txId);

    struct Transaction {
        address to;
        uint value;
        bytes data;
        bool executed;
    }

    address[] public owners;
    mapping(address => bool) public isOwner;
    uint public required;

    Transaction[] public transactions;
    // mapping from tx id => owner => bool
    mapping(uint => mapping(address => bool)) public approved;

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

    modifier txExists(uint _txId) {
        require(_txId < transactions.length, "tx does not exist");
        _;
    }

    modifier notApproved(uint _txId) {
        require(!approved[_txId][msg.sender], "tx already approved");
        _;
    }

    modifier notExecuted(uint _txId) {
        require(!transactions[_txId].executed, "tx already executed");
        _;
    }

    constructor(address[] memory _owners, uint _required) {
        require(_owners.length > 0, "owners required");
        require(
            _required > 0 && _required <= _owners.length,
            "invalid required number of owners"
        );

        for (uint i; i < _owners.length; i++) {
            address owner = _owners[i];

            require(owner != address(0), "invalid owner");
            require(!isOwner[owner], "owner is not unique");

            isOwner[owner] = true;
            owners.push(owner);
        }

        required = _required;
    }

    receive() external payable {
        emit Deposit(msg.sender, msg.value);
    }

    function submit(
        address _to,
        uint _value,
        bytes calldata _data
    ) external onlyOwner {
        transactions.push(
            Transaction({to: _to, value: _value, data: _data, executed: false})
        );
        emit Submit(transactions.length - 1);
    }

    function approve(uint _txId)
        external
        onlyOwner
        txExists(_txId)
        notApproved(_txId)
        notExecuted(_txId)
    {
        approved[_txId][msg.sender] = true;
        emit Approve(msg.sender, _txId);
    }

    function _getApprovalCount(uint _txId) private view returns (uint count) {
        for (uint i; i < owners.length; i++) {
            if (approved[_txId][owners[i]]) {
                count += 1;
            }
        }
    }

    function execute(uint _txId)
        external
        onlyOwner
        txExists(_txId)
        notExecuted(_txId)
    {
        require(_getApprovalCount(_txId) >= required, "approvals < required");
        Transaction storage transaction = transactions[_txId];

        transaction.executed = true;

        (bool success, ) = transaction.to.call{value: transaction.value}(
            transaction.data
        );
        require(success, "tx failed");

        emit Execute(_txId);
    }

    function revoke(uint _txId)
        external
        onlyOwner
        txExists(_txId)
        notExecuted(_txId)
    {
        require(approved[_txId][msg.sender], "tx not approved");
        approved[_txId][msg.sender] = false;
        emit Revoke(msg.sender, _txId);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

# 四、参考资料

Last Updated: 2023-01-28 4:31:25