ERC-7521: 智能合约钱包的通用意图
智能合约钱包的通用意图规范,允许在签名时授权当前和未来的意图结构
Authors | Stephen Monn (@pixelcircuits), Bikem Bengisu (@supiket) |
---|---|
Created | 2023-09-19 |
Discussion Link | https://ethereum-magicians.org/t/erc-7521-generalized-intents-for-smart-contract-wallets/15840 |
Table of Contents
摘要
一个通用的意图规范入口点合约,它支持随着时间推移而演变的多种意图标准。智能合约钱包无需不断升级以支持不断涌现的新意图标准,而是信任一个单一的入口点合约来处理签名验证,然后将低级别的意图数据处理和定义传递给用户在意图签名时指定的其他合约。这些签名的消息,称为 UserIntent
,在任何主机上被八卦传播,MEV 搜索者可以在这些主机上查找并将其与自己的 UserIntent
组合成一个名为 IntentSolution
的对象。然后,MEV 搜索者将他们构建的 IntentSolution
对象打包到一个事务中,该事务对特殊的合约入口点合约进行 handleIntents
调用。然后,此事务将通过典型的 MEV 渠道,最终包含在一个区块中。
动机
另请参阅 “ERC-4337:通过入口点合约规范实现账户抽象” 及其中链接的历史工作和动机。
本提案使用相同的入口点合约思想,使智能合约钱包现在可以支持一个单一接口,以解锁对不断发展的意图环境的面向未来访问。它旨在实现以下目标:
- 实现为用户启用意图的关键目标:允许用户使用包含任意验证逻辑的智能合约钱包来指定由各种其他意图标准合约描述和处理的意图执行。
- 去中心化
- 允许任何 MEV 搜索者参与解决已签名意图的过程
- 允许任何开发人员添加他们自己的意图标准定义,供用户在签名时选择加入
- 对未来意图标准兼容性具有前瞻性:定义一个意图标准接口,使未来的意图标准定义合约能够访问尽可能多的关于当前
handleIntents
执行上下文的信息。 - 尽可能降低 Gas 成本:将诸如意图段执行顺序之类的关键意图处理逻辑包含在入口点合约本身中,以便优化最常见用例的 Gas 效率。
- 实现良好的用户体验
- 当用户想要使用新开发的意图标准时,避免智能合约钱包升级的需要。
- 实现只需要单个签名的复杂意图组合。
规范
用户将他们希望其钱包参与的意图打包到一个名为 UserIntent
的 ABI 编码结构中:
字段 | 类型 | 描述 |
---|---|---|
sender |
address |
发出意图的钱包 |
segments |
bytes[] |
由不同意图标准的多个段定义的数据 |
signature |
bytes |
在验证步骤期间传递到钱包的数据 |
segments
参数是一个任意字节数组,其用途由意图标准定义。此数组中的每个项目都称为意图段。每个段的前 32 个字节用于指定该段数据所属的意图标准 ID。用户将 UserIntent
对象发送到最适合所使用意图标准的任何 mempool 策略。一类称为 solver 的特殊 MEV 搜索者寻找这些意图以及它们与其他意图(包括他们自己的意图)组合的方式,以创建一个名为 IntentSolution
的 ABI 编码结构:
字段 | 类型 | 描述 |
---|---|---|
timestamp |
uint256 |
应该评估意图的时间 |
intents |
UserIntent[] |
要执行的意图列表 |
order |
uint256[] |
包含意图的执行顺序 |
然后,solver 创建一个解决方案交易,该交易将 IntentSolution
对象打包到对预先发布的全局入口点合约的单个 handleIntents
调用中。
入口点合约的核心接口如下:
function handleIntents
(IntentSolution calldata solution)
external;
function validateIntent
(UserIntent calldata intent)
external;
function registerIntentStandard
(IIntentStandard intentStandard)
external returns (bytes32);
function verifyExecutingIntentSegmentForStandard
(IIntentStandard intentStandard)
external view returns (bool);
意图标准必须具备的核心接口是:
function validateIntentSegment
(bytes calldata segmentData)
external pure;
function executeIntentSegment
(IntentSolution calldata solution, uint256 executionIndex, uint256 segmentIndex, bytes calldata context)
external returns (bytes memory);
钱包必须具备的核心接口是:
function validateUserIntent
(UserIntent calldata intent, bytes32 intentHash)
external;
function generalizedIntentDelegateCall
(bytes memory data)
external;
必需的入口点合约功能
入口点的 handleIntents
函数必须执行以下步骤。它必须进行两个循环,即验证循环和执行循环。
在验证循环中,handleIntents
调用必须为每个 UserIntent
执行以下步骤:
- 验证
IntentSolution
上的timestamp
值,确保它在block.timestamp
或它之前某个时间的可接受范围内。 - 调用钱包上的
validateUserIntent
,传入UserIntent
和意图的哈希值。钱包应验证意图的签名。如果任何validateUserIntent
调用失败,handleIntents
必须至少跳过该意图的执行,并且可能会完全恢复。
在执行循环中,handleIntents
调用必须为每个 UserIntent
上的 segments
字节数组参数上的所有段执行以下步骤:
- 调用意图标准上的
executeIntentSegment
,该意图标准由segments
的前 32 个字节(意图标准 ID)指定。此调用传入整个IntentSolution
以及当前executionIndex
(此函数之前为任何标准或意图调用过的次数)、segmentIndex
(要在segments
数组中执行的索引)和context
数据。executeIntentSegment
函数返回每个意图的任意字节,这些字节必须被记住并传递到下一次相同的意图的executeIntentSegment
调用中。
意图标准可以自行选择如何解析 segments
字节,并利用在整个意图执行过程中持续存在的 context
数据 blob。
segments
数组中 UserIntent
段的执行顺序始终遵循 segments
参数上定义的相同顺序。但是,UserIntent
对象之间段的执行顺序可以通过 IntentSolution
对象的 order
参数指定。例如,[1,1,0,1]
的 order
数组将导致第二个意图被执行两次(意图 2 上的段 1 和 2),然后第一个意图将被执行(意图 1 上的段 1),最后是第二个意图被第三次执行(意图 2 上的段 3)。如果在解决方案中未指定排序,或者在到达排序数组的末尾后所有意图的所有段尚未被处理,则将使用默认排序。此默认排序会根据需要多次从第一个意图循环到最后一个意图,直到所有意图的所有段都已执行完毕。如果排序要求在一个意图的所有段都已执行后执行该意图,则只需跳过 executeIntentSegment
调用,并继续跨所有意图的执行。
在接受 UserIntent
之前,solver 必须使用 RPC 方法在本地调用入口点的 validateIntent
函数,该函数验证签名和数据格式是否正确;有关详细信息,请参见下面的意图验证部分。
注册新的入口点意图标准
入口点的 registerIntentStandard
函数必须允许免许可地注册新的意图标准合约。在注册过程中,入口点为其提供一个标准 ID,该 ID 对于意图标准合约、入口点合约和链 ID 是唯一的。
执行意图的意图标准行为
意图标准的 executeIntentSegment
函数可以访问广泛的数据集,包括整个 IntentSolution
,以便允许它实现任何将来可能被认为有用的逻辑。每个意图标准合约都应解析 UserIntent
对象的 segments
参数,并使用它来验证任何约束或执行任何与标准相关的操作。意图标准还可以利用可以在 executeIntentSegment
函数末尾返回的 context
数据。此数据由入口点保存,并在下次为意图调用它时作为参数传递给 executeIntentSegment
函数。这使意图标准可以访问持久性数据存储,因为其他意图在其他意图之间执行。一个用例是意图标准在意图执行期间寻找状态变化(例如释放令牌并期望获得其他令牌)。
执行意图的智能合约钱包行为
入口点在验证之后和意图执行期间,不对智能合约钱包有任何期望。但是,意图标准可能希望智能合约钱包在执行期间执行某些操作。智能合约钱包的 generalizedIntentDelegateCall
函数必须使用给定的 calldata 在调用意图标准处执行委托调用。为了使钱包信任执行委托调用,它必须调用入口点合约上的 verifyExecutingIntentSegmentForStandard
函数,以验证以下两项:
- 钱包上
generalizedIntentDelegateCall
的msg.sender
是入口点当前在其上调用executeIntentSegment
的意图标准合约。 - 智能合约钱包是入口点当前为其调用
executeIntentSegment
的UserIntent
上的sender
。
验证意图的智能合约钱包行为
入口点在每个 UserIntent
的 sender
字段中指定的智能合约钱包上为每个意图调用 validateUserIntent
。此函数提供整个 UserIntent
对象以及意图的预计算哈希。然后,应期望智能合约钱包分析此数据,以确保它实际上是从指定的 sender
发送的。如果意图无效,智能合约钱包应在 validateUserIntent
函数中引发错误。应注意的是,尽管 validateUserIntent
不像 view
那样受到限制,但对于诸如 nonce 管理之类的事物的状态更新应在意图本身的单个段中完成。这允许以最大程度地自定义用户定义其意图的方式,同时仅在入口点内嵌入最低限度的验证,以确保意图无法被伪造。
Solver 意图验证
为了验证 UserIntent
,solver 对入口点进行 validateIntent
的 view 调用。此函数检查签名是否通过验证,以及意图上的段是否已正确格式化。如果调用因任何错误而恢复,则 solver 应拒绝 UserIntent
。
模拟
预计 Solver 将在典型的 MEV 工作流程中处理模拟。这很可能意味着在当前区块高度下干运行其解决方案,以确定结果是否符合预期。然后,可以将成功的解决方案作为捆绑包提交给区块构建者,以包含在下一个区块中。
扩展
入口点合约可以启用其他功能,以减少常见情况的 Gas 成本。
扩展:嵌入式意图标准
我们扩展了入口点逻辑,以包括几个已识别的通用意图标准的逻辑。这些标准在入口点合约创建时使用其自己的标准 ID 进行注册。这些标准的 validateUserIntent
和 executeIntentSegment
函数包含在入口点合约代码中,以减少外部调用并节省 Gas。
扩展:处理多重
我们在入口点合约中添加了附加函数 handleIntentsMulti(IntentSolution[] calldata solutions)
。这允许在单个事务中执行多个解决方案,从而可以在接触类似存储区域的意图中节省 Gas。
扩展:nonce 管理
我们在入口点合约中添加了函数 getNonce(address sender, uint256 key)
和 setNonce(uint256 key, uint256 nonce)
。这些函数允许将 nonce 数据存储在入口点合约存储中。Nonce 存储在每个发送者级别,并且可以由任何人读取。但是,入口点合约强制执行只能由当前正在执行的意图标准为用户设置 nonce,并且只能针对当前正在执行的意图上的 sender
。
扩展:数据 blobs
我们使入口点合约能够跳过对 UserIntent
对象的验证,这些对象的 sender
字段为 address(0)
或为空的 segments
字段(而不是验证失败)。同样,在执行期间会跳过它们。然后,可以自由地将 segments
字段或 sender
字段视为将任何任意数据注入到意图执行中的一种方式。此数据可能有助于解决具有需要已知并证明给它的秘密的意图标准的意图,或者其行为可以根据周围的其他意图而变化的意图。例如,一种意图标准,它向智能合约钱包发出信号,以将一些令牌转移到在执行过程中接下来排队的意图的发送者。
理由
通用意图标准的主要挑战是能够适应不断发展的意图世界。用户需要有一种无缝的方式来表达他们的意图,而无需对其智能合约钱包进行持续更新。
在本提案中,我们希望钱包具有一个 validateUserIntent
函数,该函数将 UserIntent
作为输入,并验证签名。受信任的入口点合约使用此函数来验证签名,并将意图处理逻辑转发到 UserIntent
上 segments
数组字段中每个段的前 32 个字节中指定的意图标准合约。然后,应期望钱包具有一个 generalizedIntentDelegateCall
函数,该函数允许它从意图标准合约执行与意图相关的操作,并使用入口点上的 verifyExecutingIntentSegmentForStandard
函数来保证安全性。
基于入口点的方法允许在验证和意图执行之间进行清晰的分离,并防止钱包必须不断更新以支持用户想要使用的最新意图标准组合。另一种方法是,新的意图标准的开发人员必须说服钱包软件开发人员来支持他们的新意图标准。该提案在签名时将意图的核心定义移交给用户。
Solver
Solver 促进用户意图的实现,以寻找他们自己的 MEV。它们还充当链上执行意图的交易发起者,包括必须预付任何 Gas 费用,从而减轻了典型用户的负担。
Solver 将依赖于由意图本身的性质和所使用的各个意图标准决定的八卦网络和解决方案算法。
入口点升级
鼓励钱包成为 DELEGATECALL 转发合约,以提高 Gas 效率并允许钱包可升级性。预期钱包代码会将入口点硬编码到其代码中以提高 Gas 效率。如果引入了新的入口点(无论是添加新功能、提高 Gas 效率还是修复关键安全漏洞),用户可以自行调用以将其钱包的代码地址替换为包含指向新入口点的代码的新代码地址。在升级过程中,预期意图标准合约也将必须重新注册到新的入口点。
另一种选择是钱包不硬编码入口点,而是验证来自任何入口点的签名。验证签名后,钱包可以在瞬态存储中记录入口点,然后使用它来确保接受 generalizedIntentDelegateCall
函数调用时的安全性。在参考实现中有一个示例说明如何实现此目的。
意图标准升级
由于意图标准未硬编码到钱包中,因此用户无需执行任何操作即可使用任何新注册的意图标准。用户只需使用新的意图标准签名一个意图即可。
签名聚合
签名聚合应由智能合约钱包直接在签名验证过程中处理。这消除了入口点的复杂性,并允许开发人员创造性地提出解决方案。参考实现包括一个示例,说明如何通过使用钱包信任聚合合约来实现此目的,该合约使用瞬态存储来报告给各个钱包,即已在该交易调用堆栈中较早地通过聚合签名验证了一个意图。
向后兼容性
此 ERC 不会更改共识层,因此对于整个 Ethereum 而言,不存在向后兼容性问题。尝试与现有的智能合约钱包集成时,会遇到一些困难。如果钱包已经支持 ERC-4337,则实现 validateUserIntent
函数应与 validateUserOp
函数非常相似,但需要用户进行升级。
参考实现
请参阅 https://github.com/essential-contributions/ERC7521
安全注意事项
入口点合约需要经过非常严格的审核和形式验证,因为它将充当 所有 支持 ERC-7521 的钱包的中心信任点。总而言之,此架构降低了生态系统的审核和形式验证负担,因为单个 钱包 必须做的工作量变得更小(它们只需要验证 validateUserIntent
函数及其“检查签名”逻辑)并通过使用 verifyExecutingIntentSegmentForStandard
函数检查入口点来限制对 generalizedIntentDelegateCall
的任何调用。但是,入口点合约中集中的安全风险需要验证为非常强大,因为它非常集中。
验证需要涵盖一个主要声明(不包括保护 Solver 和意图标准相关基础设施所需的声明):
- 防范任意劫持的安全性:仅当入口点已成功验证
UserIntent
的签名并且当前正在对UserIntent
的segments
字段中指定的standard
调用executeIntentSegment
时,入口点才对verifyExecutingIntentSegmentForStandard
返回 true,该UserIntent
也具有与调用该函数的msg.sender
钱包相同的sender
。
对于用户决定与之交互的任何意图标准合约,还需要进行额外的大量审核和形式验证。
版权
通过 CC0 放弃版权及相关权利。
Citation
Please cite this document as:
Stephen Monn (@pixelcircuits), Bikem Bengisu (@supiket), "ERC-7521: 智能合约钱包的通用意图 [DRAFT]," Ethereum Improvement Proposals, no. 7521, September 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7521.