4. 提交交易提案
任务说明
本案例实现多签钱包的交易提案提交功能。当任意一个已注册的签名人调用submitTransaction时,合约将记录目标地址、转账金额、调用数据以及提案的初始执行状态(未执行),并返回唯一的提案ID。该提案后续可被其他签名人确认,并在达到阈值后执行。
合约实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract MultiSigWallet {
// ---- 数据结构 ----
struct Transaction {
address destination; // 目标地址
uint256 value; // 转账金额(wei)
bytes data; // 调用数据
bool executed; // 执行状态
uint256 confirmations; // 已确认数
}
// ---- 状态变量 ----
address[] public owners; // 签名人列表
mapping(address => bool) public isOwner; // 地址是否为签名人
uint256 public required; // 所需确认阈值
Transaction[] public transactions; // 交易提案列表
// ---- 事件 ----
event SubmitTransaction(
address indexed owner,
uint256 indexed txIndex,
address indexed destination,
uint256 value,
bytes data
);
// ---- 修饰器 ----
modifier onlyOwner() {
require(isOwner[msg.sender], "not owner");
_;
}
// ---- 构造函数 ----
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], "owner not unique");
isOwner[owner] = true;
owners.push(owner);
}
required = _required;
}
// ---- 核心功能:提交交易提案 ----
function submitTransaction(
address _destination,
uint256 _value,
bytes calldata _data
) external onlyOwner returns (uint256 txIndex) {
require(_destination != address(0), "invalid destination");
txIndex = transactions.length;
transactions.push(Transaction({
destination: _destination,
value: _value,
data: _data,
executed: false,
confirmations: 0
}));
emit SubmitTransaction(msg.sender, txIndex, _destination, _value, _data);
}
// ---- 查询辅助 ----
function getTransactionCount() external view returns (uint256) {
return transactions.length;
}
function getTransaction(uint256 _txIndex)
external
view
returns (address destination, uint256 value, bytes memory data, bool executed, uint256 confirmations)
{
require(_txIndex < transactions.length, "tx index out of bounds");
Transaction storage txItem = transactions[_txIndex];
return (txItem.destination, txItem.value, txItem.data, txItem.executed, txItem.confirmations);
}
}
关键设计说明
合约使用动态数组transactions存储所有提案,每个提案包含目标地址、金额、调用数据、执行状态和当前确认数。提交时自动将executed置为false,confirmations置为0,表示尚未被其他签名人确认。提案ID即数组下标,从0开始递增,保证唯一性。构造函数中完成签名人列表和阈值的初始化,确保只有合法签名人才能提交提案。
SVG:交易提案数据结构与提交流程
部署与验证
部署参数:构造函数需传入签名人地址数组和所需确认阈值。例如使用三个测试地址作为签名人,阈值设为2。
// 部署示例(使用 Hardhat 或 Remix)
address[] memory owners = new address[](3);
owners[0] = address(0x1111...);
owners[1] = address(0x2222...);
owners[2] = address(0x3333...);
MultiSigWallet wallet = new MultiSigWallet(owners, 2);
功能验证步骤:
- 使用任一签名人地址调用
submitTransaction,传入目标地址(如0x4444...)、金额(如1 ether)和空数据0x; - 调用返回提案ID(首次提交返回0);
- 调用
getTransactionCount(),确认返回1; - 调用
getTransaction(0),确认返回的destination、value与提交参数一致,executed为false,confirmations为0; - 使用非签名人地址调用
submitTransaction,应抛出异常"not owner"; - 传入零地址作为目标地址,应抛出异常
"invalid destination"。
判定标准:以上步骤中返回值、状态字段和异常行为均符合预期,即证明交易提案提交功能正确实现。