ERC-7821: 最小批量执行器接口
用于委托的最小批量执行器接口
Authors | Vectorized (@Vectorized), Jake Moxey (@jxom), Hadrien Croubois (@Amxx) |
---|---|
Created | 2024-11-21 |
Discussion Link | https://ethereum-magicians.org/t/erc-7821-minimal-batch-executor-interface/21776 |
Requires | EIP-7579 |
Table of Contents
摘要
本提案定义了一个用于委托的最小批量执行器接口。委托是一个智能合约,它实现了其他智能合约可以委托给它的逻辑。这允许以标准化的方式准备原子批量执行。
动机
随着 EIP-7702 的出现,外部所有账户 (EOA) 可以执行原子批量执行。
我们预计将会有多个主要供应商提供多个 EIP-7702 委托。执行接口的标准将实现更好的互操作性。EIP-7702 委托是一个有风险的过程,应该谨慎进行——不应在用户每次切换网站时执行。此外,EIP-7702 委托是需要 gas 费用的交易,这使得频繁切换委托变得不经济。标准化的执行接口将减少切换委托的需求。
该标准补充了 EIP-5792 中的 wallet_sendCalls
API。它支持检测 EOA 上的原子批量执行能力,并为 EOA 上的原子批量执行准备 calldata。
使用原子批量执行可以减少总延迟和总交易成本,因此对于 EOA 来说,它比顺序交易发送更可取。
因此,本提案的最大动机是,它经过精心设计,以实现最大的简单性、可扩展性、性能和兼容性。
规范
本文档中的关键词“必须”、“不得”、“需要”、“应”、“不应”、“推荐”、“不推荐”、“可以”和“可选”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
概述
最小批量执行器接口定义如下:
/// @dev 最小批量执行器的接口。
interface IERC7821 {
/// @dev `execute` 函数的调用结构体。
struct Call {
address to; // 如果是 `address(0)`,则替换为 `address(this)`。
uint256 value; // 要发送的原生货币(即以太币)的数量。
bytes data; // 要随调用一起发送的 Calldata。
}
/// @dev 执行 `executionData` 中的调用。
/// 如果任何调用失败,则恢复并冒泡错误。
///
/// 如果 `address(0)`,MAY 将 `Call.to` 替换为 `address(this)`。
///
/// `executionData` 编码(单个批次):
/// - 如果 `opData` 为空,则 `executionData` 仅仅是 `abi.encode(calls)`。
/// - 否则,`executionData` 是 `abi.encode(calls, opData)`。
/// 参见:https://eips.ethereum.org/EIPS/eip-7579
///
/// `executionData` 编码(批次集合):
/// - `executionData` 是 `abi.encode(bytes[])`,其中 `bytes[]` 中的每个元素
/// 都是单个批次的 `executionData`。
///
/// 支持的模式:
/// - `0x01000000000000000000...`: 单个批次。不支持可选的 `opData`。
/// - `0x01000000000078210001...`: 单个批次。支持可选的 `opData`。
/// - `0x01000000000078210002...`: 批次集合。该模式是可选的。
///
/// 对于“批次集合”模式,每个批次将以 `0x01000000000078210001...` 模式在内部递归地传递到
/// `execute` 中。
/// 适用于传入由不同签名者签名的批次。
///
/// 授权检查:
/// - 如果 `opData` 为空,则实现应要求 `msg.sender == address(this)`。
/// - 如果 `opData` 不为空,则实现应使用在 `opData` 中编码的签名来确定
/// 调用者是否可以执行该操作。
/// - 如果 `msg.sender` 是授权的入口点,则 `execute` 可以接受来自入口点的调用,并且可以使用
/// `opData` 进行专门的逻辑。
///
/// `opData` 可以用于存储用于身份验证、
/// 付款人数据、gas 限制等额外数据。
///
/// 为了 calldata 压缩效率,如果 Call.to 是 `address(0)`,
/// 它将被替换为 `address(this)`。
function execute(bytes32 mode, bytes calldata executionData)
external
payable;
/// @dev 提供用于执行模式支持检测。
/// 只返回真值:
/// - `0x01000000000000000000...`: 单个批次。不支持可选的 `opData`。
/// - `0x01000000000078210001...`: 单个批次。支持可选的 `opData`。
/// - `0x01000000000078210002...`: 批次集合。该模式是可选的。
function supportsExecutionMode(bytes32 mode) external view returns (bool);
}
OPTIONAL 支持批量模式的批量处理。如果不支持,合约必须在 supportsExecutionMode
中返回 false,对于任何以 0x01000000000078210002
开头的模式。
建议
为了支持 EOA 上具有委托的批准 + 交换工作流程,前端应:
-
查询
supportsExecutionMode(bytes32(0x0100000000000000000000000000000000000000000000000000000000000000))
,确保它返回 true。 -
执行
execute(bytes32(0x0100000000000000000000000000000000000000000000000000000000000000), abi.encode(calls))
。
理由
我们的目标是实现最大程度的极简主义,以使标准尽可能的左倾。简单是采用的关键。我们的北极星是尽快让每个去中心化交易所都支持 EOA 与委托的批准 + 交换工作流程。
execute
和 supportsExecutionMode
我们选择在 ERC-7579 中使用 execute
和 supportsExecutionMode
函数,以实现与现有智能账户生态系统的更好兼容性。
虽然极简主义是目标,但在追求更好采用的过程中必须做出一些妥协。
为了极简主义,此标准不需要在 ERC-7579 中实现 executeFromExecutor
函数。
executionData
中 opData
的可选编码
opData
字节参数可以选择性地包含在 executionData
中,可以通过执行 abi.encode(calls)
或 abi.encode(calls, opData)
来实现。
将 address(0)
替换为 address(this)
用于 calldata 压缩优化。
可选的批次集合模式
opData
可用于为单个批次提供身份验证数据。拥有批次集合模式将使单个交易能够提交由不同签名者签名的批次,而无需授权入口点。此模式是可选的,因为使用授权的入口点仍然可以实现相同的功能。它包含在内是为了开发人员的体验。
向后兼容性
没有向后兼容性问题。
参考实现
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.4;
/// @notice 最小批量执行器混合合约。
abstract contract ERC7821 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 结构体 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `execute` 函数的调用结构体。
struct Call {
address to; // 如果是 `address(0)`,则替换为 `address(this)`。
uint256 value; // 要发送的原生货币(即以太币)的数量。
bytes data; // 要随调用一起发送的 Calldata。
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 错误 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev 不支持执行模式。
error UnsupportedExecutionMode();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 执行操作 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev 执行 `executionData` 中的调用。
/// 如果任何调用失败,则恢复并冒泡错误。
///
/// `executionData` 编码(单个批次):
/// - 如果 `opData` 为空,则 `executionData` 仅仅是 `abi.encode(calls)`。
/// - 否则,`executionData` 是 `abi.encode(calls, opData)`。
/// 参见:https://eips.ethereum.org/EIPS/eip-7579
///
/// `executionData` 编码(批次集合):
/// - `executionData` 是 `abi.encode(bytes[])`,其中 `bytes[]` 中的每个元素
/// 都是单个批次的 `executionData`。
///
/// 支持的模式:
/// - `0x01000000000000000000...`: 单个批次。不支持可选的 `opData`。
/// - `0x01000000000078210001...`: 单个批次。支持可选的 `opData`。
/// - `0x01000000000078210002...`: 批次集合。该模式是可选的。
///
/// 对于“批次集合”模式,每个批次将以 `0x01000000000078210001...` 模式在内部递归地传递到
/// `execute` 中。
/// 适用于传入由不同签名者签名的批次。
///
/// 授权检查:
/// - 如果 `opData` 为空,则实现应要求 `msg.sender == address(this)`。
/// - 如果 `opData` 不为空,则实现应使用在 `opData` 中编码的签名来确定
/// 调用者是否可以执行该操作。
/// - 如果 `msg.sender` 是授权的入口点,则 `execute` 可以接受来自入口点的调用,并且可以使用
/// `opData` 进行专门的逻辑。
///
/// `opData` 可以用于存储用于身份验证、
/// 付款人数据、gas 限制等额外数据。
///
/// 为了 calldata 压缩效率,如果 Call.to 是 `address(0)`,
/// 它将被替换为 `address(this)`。
function execute(bytes32 mode, bytes memory executionData)
public
payable
virtual
{
uint256 id = _executionModeId(mode);
if (id == 3) {
mode ^= bytes32(uint256(3 << (22 * 8)));
bytes[] memory batches = abi.decode(executionData, (bytes[]));
for (uint256 i; i < batches.length; ++i) {
execute(mode, batches[i]);
}
return;
}
if (id == uint256(0)) revert UnsupportedExecutionMode();
bool tryWithOpData;
/// @solidity memory-safe-assembly
assembly {
let t := gt(mload(add(executionData, 0x20)), 0x3f)
let executionDataLength := mload(executionData)
tryWithOpData := and(eq(id, 2), and(gt(executionDataLength, 0x3f), t))
}
Call[] memory calls;
bytes memory opData;
if (tryWithOpData) {
(calls, opData) = abi.decode(executionData, (Call[], bytes));
} else {
calls = abi.decode(executionData, (Call[]));
}
_execute(calls, opData);
}
/// @dev 提供用于执行模式支持检测。
/// 只返回真值:
/// - `0x01000000000000000000...`: 单个批次。不支持可选的 `opData`。
/// - `0x01000000000078210001...`: 单个批次。支持可选的 `opData`。
/// - `0x01000000000078210002...`: 批次集合。该模式是可选的。
function supportsExecutionMode(bytes32 mode) public view virtual returns (bool result) {
return _executionModeId(mode) != 0;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 内部帮助函数 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev 0:无效模式,1:不支持 `opData`,2:支持 `opData`,3:批次集合。
function _executionModeId(bytes32 mode) internal view virtual returns (uint256 id) {
// 仅支持原子批量执行。
// 有关编码方案,请参见:https://eips.ethereum.org/EIPS/eip-7579
// 字节布局:
// - [0] ( 1 字节 ) `0x01` 用于批量调用。
// - [1] ( 1 字节 ) `0x00` 用于在任何失败时恢复。
// - [2..5] ( 4 字节) 由 ERC7579 保留用于未来的标准化。
// - [6..9] ( 4 字节) `0x00000000` 或 `0x78210001` 或 `0x78210002`。
// - [10..31] (22 字节) 未使用。免费使用。
uint256 m = (uint256(mode) >> (22 * 8)) & 0xffff00000000ffffffff;
if (m == 0x01000000000078210002) id = 3;
if (m == 0x01000000000078210001) id = 2;
if (m == 0x01000000000000000000) id = 1;
}
/// @dev 执行调用并返回结果。
/// 如果任何调用失败,则恢复并冒泡错误。
function _execute(Call[] memory calls, bytes memory opData)
internal
virtual
{
// 非常基本的身份验证,仅允许此合约由自身调用。
// 覆盖此函数以使用 `opData` 执行更复杂的身份验证。
if (opData.length == uint256(0)) {
require(msg.sender == address(this));
// 当您覆盖此函数时,请记住返回 `_execute(calls)`。
return _execute(calls);
}
revert(); // 在您的覆盖中,将此替换为对 `opData` 进行操作的逻辑。
}
/// @dev 执行调用。
/// 如果任何调用失败,则恢复并冒泡错误。
function _execute(Call[] memory calls) internal virtual {
for (uint256 i; i < calls.length; ++i) {
Call memory c = calls[i];
address to = c.to == address(0) ? address(this) : c.to;
_execute(to, c.value, c.data);
}
}
/// @dev 执行调用。
/// 如果调用失败,则恢复并冒泡错误。
function _execute(address to, uint256 value, bytes memory data)
internal
virtual
{
(bool success, bytes memory result) = to.call{value: value}(data);
if (success) return;
/// @solidity memory-safe-assembly
assembly {
// 如果调用恢复,则冒泡恢复。
revert(add(result, 0x20), mload(result))
}
}
}
安全考虑
execute
的访问控制
实现应确保 execute
具有适当的访问控制。
版权
版权和相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Vectorized (@Vectorized), Jake Moxey (@jxom), Hadrien Croubois (@Amxx), "ERC-7821: 最小批量执行器接口 [DRAFT]," Ethereum Improvement Proposals, no. 7821, November 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7821.