多签钱包
多签钱包
本页内容正在整理中,欢迎贡献。
概述
多签钱包(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: MITpragma 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);