单个合约中多个交易的模式

问题描述

早期空头 NFT/ERC20 Token 的话,更多需要用户来做 withdraw。本文章的设计模式是由发行方来发送 NFT/ERC20 Token 给到用户。同样适用于多个资产同时转账的场景。

解决方案

合约接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.8.0;

interface IMulticall {
/// @notice Takes an array of abi-encoded call data, delegatecalls itself with each calldata, and returns the abi-encoded result
/// @dev Reverts if any delegatecall reverts
/// @param data The abi-encoded data
/// @returns results The abi-encoded return values
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results);

/// @notice OPTIONAL. Takes an array of abi-encoded call data, delegatecalls itself with each calldata, and returns the abi-encoded result
/// @dev Reverts if any delegatecall reverts
/// @param data The abi-encoded data
/// @param values The effective msg.values. These must add up to at most msg.value
/// @returns results The abi-encoded return values
function multicallPayable(bytes[] calldata data, uint256[] values) external payable virtual returns (bytes[] memory results);
}

multicallPayable 是可选的,因为由于 msg.value,它并不总是可行的。

以下是最为简陋的实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.8.0;

/// Derived from OpenZeppelin's implementation
abstract contract Multicall is IMulticall {
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory returndata) = address(this).delegatecall(data);
require(success);
results[i] = returndata;
}
return results;
}
}

multicallPayable 只应在合约能够支持时使用。以上实现可能允许攻击者使用相同的以太币多次调用支付函数。

调用方法/测试方法

以下 JavaScript 代码使用 Ethers 库,应自动将 ERC-20 Token 的 amt 单位传输到地址 A 和地址 B。

1
2
3
4
await token.multicall(await Promise.all([
token.interface.encodeFunctionData('transfer', [ addressA, amt ]),
token.interface.encodeFunctionData('transfer', [ addressB, amt ]),
]));

完整工程

如下:

https://github.com/willzhuang/multicall

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-6357.md