面向 L2 和签名聚合器的 ERC-4337 Gas 估算
在ERC-4337 Gas Estimation一文中,我们讨论了 ERC-4337 中 gas 的运作方式以及我们的 gas 预估方法。在第二篇Dummy Signatures and Gas Token Transfers中,我们发现预估 gas 并不总是那么简单,需要考虑一些边界情况。本文将深入探讨我们遇到的更多边界情况。
L2 问题
Ethereum Layer 2 rollup的部分定义是:“让 layer 1 负责安全性、数据可用性和去中心化,而 layer 2 负责扩容。”
为实现这一点,L2 会将大量交易“打包”成一个批次,然后发布到 layer 1 区块链上。这一交易成本并非免费,因为 L2 需要为向 L1 链发布大批量数据时产生的 calldata 成本付费。
L2 需要一种方式向用户收取这些 L1 calldata 成本。不同的 rollup 框架实现方式各不相同。
本文关注两个最大的 EVM rollup:Arbitrum 和 Optimism。
Arbitrum 如何计算覆盖 L1 gas 费用的成本?
在 Arbitrum 上,用于覆盖 L1 gas 成本的 L2 gas 费用计算方式如下,其中数据大小是指经过 Brotli 压缩后的字节数:
💡 L1 成本 (L1C) = 每字节数据的 L1 价格 (L1P) * 待发布数据的字节大小 (L1S)
💡 Gas (G) = L1 成本 (L1C) / L2 Gas 价格 (P)
这部分 gas 在交易开始执行之前收取,并计入交易的 gasLimit。因此,必须在交易 gas 预估中考虑这部分成本。
Optimism 如何计算覆盖 L1 gas 费用的成本?
在 Optimism 上,L1 gas 成本的计算方式类似,即用压缩后的字节大小乘以 L1 费率。但与 Arbitrum 不同,Optimism 不会把这个值转换为 L2 gas,而是直接从发送者账户中扣除所需的 ETH。
发送者在预估 gas 时不需要考虑这个值,但也无法为其支出设置上限。Optimism 会确保这笔费用不会出现骤增。
L2 上的 bundler 如何收取 L1 费用?
在这两种情况下,在 L2 上提交 bundle 交易的 bundler 都需要承担 L1 费用。bundler 需要一种方式,通过提高 L2 gas 来向被打包的 user operation 收取这笔费用。
对 L2 gas 的实际影响可以通过以下方式确定:
L2\_gas = L1\_gas \* L1\_fee / L2\_fee
verificationGasLimit 和 callGasLimit 由 entry point 计量,因此 bundler 无法用它们来收取这笔额外的 gas 费用。bundler 需要依赖其他方法。
尝试 1:设置更高的优先费
要求更高的 maxPriorityFeePerGas 可以让 bundler 收回这部分损失的费用。
计算方式如下:
- 预估 L1 费用并转换为 L2 gas a. 假设 bundle 大小为 1 个 user operation,使用网络提供的方法(通常以特殊合约调用的形式暴露)来预估 gas。 b. 这需要假设一个 L1 base fee 和一个 L2 base fee,以便将费用转换为 L2 gas 数值。
- 预估
verificationGasLimita. 由于这个 gas limit 受到严格的模拟规则约束,预估值极有可能非常接近实际值,这与callGasLimit不同。 - 设置
maxPriorityFeePerGasBuffer=L1\_fee / verificationGasLimit - 将这部分缓冲加到所需的优先费上
这种方式可行,但用户体验很差。
为了保护自身,bundler 必须假设实际使用的 call gas 为 0,并按照验证 gas 是唯一组成部分来向用户收费。这样用户最终会多付出「缓冲优先费部分乘以实际使用的 call gas」这一额外金额。这对用户来说并不理想。
尝试 2:操纵 preVerificationGas
这样一来,preVerificationGas 就成了唯一可以合理操纵的字段。这也很符合上文对 preVerificationGas 的定义——“用于记录 entry point 无法计量的 gas 使用量的字段”。由于这是 entry point 未计量的 gas,我们应该可以用这个字段向用户收费。
计算方式如下:
- 预估 L1 费用并转换为 L2 gas,设为
preVerificationGas。 a. 假设 bundle 大小为 1 个 user operation,使用网络提供的方法(通常以特殊合约调用的形式暴露)来预估 gas。 b. 这需要我们假设一个 L1 base fee 和一个 L2 base fee,以便将费用转换为 L2 gas 数值。 - 计算 L2 未计量的 gas,并将其加到上面计算出的值中。
a. 这与我们在 PreVerificationGas 计算部分中描述的普通
preVerificationGas计算方法相同。 - 在
eth\_sendUserOperation期间也执行步骤 (1) 和 (2),并在“pre-check”阶段拒绝任何preVerificationGas不够高的 operation。 - 在打包阶段,在提交前再次执行步骤 (1) 和 (2)。
a. 拒绝任何
preVerificationGas不够高的 operation。
尽管这一机制可行,但存在一个非常显著的用户体验问题。
在步骤 (2) 中,bundler 必须假设 L1 和 L2 base fee 的值来进行 gas 计算。由于 base fee 是动态变化的,如果在 gas 预估阶段和提交/打包阶段之间 L1\_fee / L2\_fee 的比率上升,就需要更高的 preVerificationGas,而那些以较低比率计算的 user operation 将被拒绝。
用户能做的最好办法是假设该比率在预估与打包之间会上升,并在 preVerificationGas 上预留一定余量以提高成功率。
由于 preVerificationGas 总是全额收取(即它不是一个 limit 字段),无论价格如何变化,用户都不得不为这份余量买单。用户只能在「可能多付」和「operation 被拒绝」之间做选择。
💡 Rundler 实现了上述 preVerificationGas 计算方式。我们建议这些 L2 的用户在 eth\_estimateUserOperationGas 返回的 preVerificationGas 基础上增加 25% 的余量,以提高 operation 不被拒绝的概率。
Optimism 的特殊情况
Optimism 的 base fee 极低,通常远低于 100 wei(没错,就是 wei)。用于收取 L1 gas 费用所需的 preVerificationGas 与 L2 gas fee 成反比,因此需要 preVerificationGas 达到极高的水平(数百万级别)。
Optimism 的优先费往往比其 base fee 高出几个数量级。因此,用户必须非常小心,不要提交与网络优先费成比例的优先费,因为 entry point 要求为 preVerificationGas \(very high\) \* priority fee \(normal\) 付费,这会导致严重的超额支付。因此 Rundler 要求优先费必须是 base fee 的固定百分比,以激励在 Optimism 上的打包行为。
签名聚合器问题
Signature aggregation 是 ERC-4337 中一个被广泛讨论的功能,因为它能够:
- 通过签名压缩降低 L2 上的 calldata 成本,带来显著的节省。
- 将聚合验证检查的 gas 成本分摊到一个 bundle 内的多个 operation 上。 a. 这种验证检查可以简单到一个 BLS 签名,也可以复杂到一个聚合的 ZK 证明。
在当前版本的 entry point 中,对签名聚合器 validate 函数的调用是未计量的。这意味着 bundler 需要找到一种方式,向被聚合的 user operation 收取这部分 gas 费用,情况与上文的 L2 问题类似。
与 L2 问题相同,唯一合理的做法就是提高 preVerificationGas。
一种实现方式是:
- bundler 假设一个
target的 bundle 大小 - bundler 在预估之前对收到的每个 operation 调用
validateUserOp,以提取所使用的聚合器地址(如果有的话)。 - 当收到一个聚合的 user operation 时,bundler 需要在该目标 bundle 大小下,预估签名聚合器每个 operation 所消耗的 gas 量。
a. 一种方法是将收到的 user operation 复制
target次组成一个 bundle,然后对aggregator.validateSignatures使用eth\_estimateGasb. 另一种方法是维护一个签名聚合器白名单,预先填充好 gas 度量值(包含一个静态值和一个按 operation 计算的动态值)。 - 将预估出的 gas 加到上文
preVerificationGas的计算结果中,并返回该值。
这种方法存在一些问题:
- bundler 假设一个
target的 bundle 大小,本身就是一种风险。要么: a. bundler 一直等到实际能够打包到目标数量的 operation,这会因延迟损害用户体验 b. bundler 打包的数量低于目标数量,自行承担这部分成本 - 用户无法根据希望被纳入的速度来“出价”多付或少付。
a. 在基于 limit 的方式中,如果用户想确保尽快被打包,可以高估成本。如果最终成本较低,也不会被多收费。而在这种方式中,由于
preVerificationGas是静态的,用户必须始终支付其全部出价。 - 在 P2P 网络中,不同 bundler 可能有不同的目标 bundle 大小。
a. 这意味着用于预估的 bundler 可能会高估或低估内存池中另一个 bundler 所需的
preVerificationGas。 b. 这些链下假设会损害内存池的互操作性。
由于这些复杂性,Rundler 目前尚不支持签名聚合器。我们很可能会添加对上述方法的支持,并假设一个(较小的,从 1 开始的)bundle 大小,这将使使用签名聚合器的成本变得非常高。
潜在的 entry point 变更
上述问题都源于 entry point 合约对 user operation 产生的大量 gas 使用缺乏计量。依赖 preVerificationGas 来收取这部分 gas 费用存在显著的用户体验问题——用户必须支付超过实际使用量的费用,以提高其 UO 快速上链的概率。
解决这些问题的一种方案是修改 entry point 合约,对这部分额外的 gas 使用进行计量,并将其归入基于 limit 的 gas 字段中。
L2-L1 calldata gas 计量
💡 这只是针对这一棘手问题的一个非常粗略的解决方案概述。我们很想听听社区的想法!
L1 calldata gas 成本可以在链上进行计量,user operation 可以只按其产生的确切成本收费,而不是按包含余量的成本收费。应使用基于 limit 的字段。
一种可能的方案是引入一个新字段,daCallDataGasLimit(da 代表 Data Availability,暂定名称)。该字段将原生用于 L2+ 版本的 entry point,用于那些交易 calldata 发布到另一个系统、因而需要以不同方式收费的链上。
entry point 的逻辑大致如下:
- 在部署时,entry point 会关联一个辅助合约
daGasMeter,该合约有一个函数measureUserOperationDaGas\(UserOperation userOp\)a. 该函数接受一个 user operation,并精确测量所使用的 DA gas 数量。 b. 例如,在 Arbitrum 上,该计量器可以用一个内存中的 user operation 调用gasEstimateL1Component,从而几乎精确地确定其使用了多少 L1 gas(它无法计算多个 operation 组成 bundle 时因额外压缩而产生的差异)。 - entry point 会在验证之前对每个 user operation 调用该函数,并将这部分 gas 归入
daCalldataGasLimit。 - 为避免交易回滚,如果 DA gas 使用量超过
daCalldataGasLimit,entry point 不会回滚,而是直接进行封顶处理,超出 limit 的部分由 bundler 自行承担。bundler 可以在链下通过确保被打包的每个 operation 在打包前其 limit 字段留有足够高的余量来解决这个问题。
这种方法存在一些相当显著的缺点:
- 不同链上的 entry point 合约将拥有不同的地址。
- 度量过程中会消耗 gas。 a. 这个问题相对较小,因为在 L2+ 上,执行 gas 成本通常远低于数据 gas 成本。
签名聚合器 gas 计量
可以修改 entry point 合约,用以测量其调用 aggregator.validateSignatures 时消耗的 gas 量,将其除以被聚合的 user operation 数量,然后从 verificationGasLimit 中均匀扣除,以此分摊 gas 使用量。
每个白名单中的签名聚合器都应关联一个静态值(验证聚合签名的基础成本)和一个动态值(每增加一个被聚合的 operation 所增加的成本)。例如,一个 BLS 签名聚合器可以将其静态值设为一次性的 signature verification 成本,动态值设为 per op hashing operations 成本。bundler 可以在 eth\_estimateUserOperationGas 期间,按静态值除以目标 bundle 大小再加上动态值的结果,来提高 verificationGasLimit。
我们如何设定目标 bundle 大小?
一种可能的方案是在 eth\_estimateUserOperationGas 的参数中补充一个 minimumBundleSize,对应用户希望被纳入其中的最小 bundle 大小(也就是最高的 gas)。愿意支付更多的用户可以降低他们的最小 bundle 大小,从而提高被纳入的速度。
bundler 随后需要相应的逻辑,以确保只将某个 UO 纳入大小不小于该 UO 最小 bundle 大小的 bundle 中。这个最小 bundle 大小可以在模拟阶段计算得出:通过追踪账户验证步骤所消耗的 gas 量,将其从 verificationGasLimit 中减去,再根据剩余值计算出最小大小。bundler 可以将该值与 UO 一起存储在其内存池中,并将其用作构建 bundle 时的提示。
💡 这种方法的一个缺点是,它假设签名聚合器对每个 operation 的 gas 使用是均匀的。如果情况并非如此,计量工作可以转移到聚合器合约上,由其验证函数返回相应结果。bundler 同样需要一种方式在链下计算这些非均匀的值。
这对签名聚合器开发者意味着什么?
签名聚合器很可能需要通过向 bundler 提供链下签名计算方法及其静态/动态 gas 成本参数,直接被 bundler 加入白名单。冷启动问题将会比较棘手,尤其是考虑到用户无法“出价”来加快其 operation 的处理速度。
这对账户客户端开发者意味着什么?
理解上述关于 L2 的区别非常重要,尤其是在预估 gas 费用时。在 Optimism 上,如果你使用网络提供的优先费,可能会为某个 user operation 严重超额支付,因为这个优先费现在也适用于 L1 成本。如果使用 Alchemy 的 bundler 端点,请参阅我们的文档,了解预估费用的技巧。
🦀
继续阅读
本系列深入探讨 ERC-4337 gas 预估的下一篇文章,将逐步讲解 user operation 费用预估的过程。如果你错过了第一篇或第二篇,可以了解 ERC-4337 gas 预估、dummy 值以及 token 转账问题的运作方式。
Alchemy 新闻通讯
第一时间获取发布信息
订阅我们的新闻通讯
获取 Alchemy 的最新产品更新和资源
填写您的电子邮箱地址即表示您同意接收我们的营销通讯和产品更新。您确认 Alchemy 按照我们的隐私声明处理我们收到的信息。您可以随时取消订阅。


