---
title: "面向 L2 和签名聚合器的 ERC-4337 Gas 估算"
description: "了解 Alchemy 的 ERC-4337 Bundler 如何为未被入口点合约计量的 Gas 费用进行 Gas 估算，包括签名聚合器以及 Optimism 和 Arbitrum 等 layer 2 交易。"
---

# 面向 L2 和签名聚合器的 ERC-4337 Gas 估算

在[ERC-4337 Gas Estimation](https://www.alchemy.com/blog/erc-4337-gas-estimation)一文中，我们讨论了 ERC-4337 中 gas 的运作方式以及我们的 gas 预估方法。在第二篇[Dummy Signatures and Gas Token Transfers](https://www.alchemy.com/blog/dummy-signatures-and-gas-token-transfers)中，我们发现预估 gas 并不总是那么简单，需要考虑一些边界情况。本文将深入探讨我们遇到的更多边界情况。

## L2 问题

[Ethereum Layer 2 rollup](https://ethereum.org/en/layer-2/)的部分定义是：“让 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 费用计算方式](https://developer.arbitrum.io/arbos/l1-pricing)如下，其中数据大小是指经过 Brotli 压缩后的字节数：

<CalloutBlock>

💡 L1 成本 \(L1C\) = 每字节数据的 L1 价格 \(L1P\) \* 待发布数据的字节大小 \(L1S\)

</CalloutBlock>

<CalloutBlock>

💡 Gas \(G\) = L1 成本 \(L1C\) / L2 Gas 价格 \(P\)

</CalloutBlock>

这部分 gas 在交易开始执行之前收取，并计入交易的 `gasLimit`。因此，必须在[交易 gas 预估](https://developer.arbitrum.io/devs-how-tos/how-to-estimate-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 收回这部分损失的费用。

**计算方式如下：**

1. 预估 L1 费用并转换为 L2 gas
   a. 假设 bundle 大小为 1 个 user operation，使用网络提供的方法（通常以特殊合约调用的形式暴露）来预估 gas。
   b. 这需要假设一个 L1 base fee 和一个 L2 base fee，以便将费用转换为 L2 gas 数值。
1. 预估 `verificationGasLimit`
   a. 由于这个 gas limit 受到严格的模拟规则约束，预估值极有可能非常接近实际值，这与 `callGasLimit` 不同。
1. 设置 `maxPriorityFeePerGasBuffer` = `L1\_fee / verificationGasLimit‍`
1. 将这部分缓冲加到所需的优先费上

这种方式可行，但用户体验很差。

为了保护自身，bundler 必须假设实际使用的 call gas 为 0，并按照验证 gas 是唯一组成部分来向用户收费。这样用户最终会多付出「缓冲优先费部分乘以实际使用的 call gas」这一额外金额。这对用户来说并不理想。

### 尝试 2：操纵 preVerificationGas

这样一来，`preVerificationGas` 就成了唯一可以合理操纵的字段。这也很符合上文对 `preVerificationGas` 的定义——“用于记录 entry point 无法计量的 gas 使用量的字段”。由于这是 entry point 未计量的 gas，我们应该可以用这个字段向用户收费。

**计算方式如下：**

1. 预估 L1 费用并转换为 L2 gas，设为 `preVerificationGas`。
   a. 假设 bundle 大小为 1 个 user operation，使用网络提供的方法（通常以特殊合约调用的形式暴露）来预估 gas。
   b. 这需要我们假设一个 L1 base fee 和一个 L2 base fee，以便将费用转换为 L2 gas 数值。
1. 计算 L2 未计量的 gas，并将其加到上面计算出的值中。
   a. 这与我们在 PreVerificationGas 计算部分中描述的普通 `preVerificationGas` 计算方法相同。
1. 在 `eth\_sendUserOperation` 期间也执行步骤 \(1\) 和 \(2\)，并在“pre-check”阶段拒绝任何 `preVerificationGas` 不够高的 operation。
1. 在打包阶段，在提交前再次执行步骤 \(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 被拒绝」之间做选择。

<CalloutBlock>

💡 Rundler 实现了上述 `preVerificationGas` 计算方式。我们建议这些 L2 的用户在 `eth\_estimateUserOperationGas` 返回的 `preVerificationGas` 基础上增加 25% 的余量，以提高 operation 不被拒绝的概率。

</CalloutBlock>

### 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](https://www.alchemy.com/overviews/account-abstraction-aggregate-signatures) 是 [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337#using-signature-aggregators) 中一个被广泛讨论的功能，因为它能够：

1. 通过签名压缩降低 L2 上的 calldata 成本，带来显著的节省。
1. 将聚合验证检查的 gas 成本分摊到一个 bundle 内的多个 operation 上。
   a. 这种验证检查可以简单到一个 BLS 签名，也可以复杂到一个聚合的 [ZK](https://www.alchemy.com/blog/zero-knowledge-rollups) 证明。

在当前版本的 entry point 中，[对签名聚合器 validate 函数的调用](https://github.com/eth-infinitism/account-abstraction/blob/d1333cf58c17d5cc84e830f3fc3a69081c777979/contracts/core/EntryPoint.sol#L137)是未计量的。这意味着 bundler 需要找到一种方式，向被聚合的 user operation 收取这部分 gas 费用，情况与上文的 L2 问题类似。

与 L2 问题相同，唯一合理的做法就是提高 `preVerificationGas`。

**一种实现方式是：**

1. bundler 假设一个 `target` 的 bundle 大小
1. bundler 在预估之前对收到的每个 operation 调用 `validateUserOp`，以提取所使用的聚合器地址（如果有的话）。
1. 当收到一个聚合的 user operation 时，bundler 需要在该目标 bundle 大小下，预估签名聚合器每个 operation 所消耗的 gas 量。
   a. 一种方法是将收到的 user operation 复制 `target` 次组成一个 bundle，然后对 `aggregator.validateSignatures‍` 使用 `eth\_estimateGas`
   b. 另一种方法是维护一个签名聚合器白名单，预先填充好 gas 度量值（包含一个静态值和一个按 operation 计算的动态值）。
1. 将预估出的 gas 加到上文 `preVerificationGas` 的计算结果中，并返回该值。

**这种方法存在一些问题：**

1. bundler 假设一个 `target` 的 bundle 大小，本身就是一种风险。要么：
   a. bundler 一直等到实际能够打包到目标数量的 operation，这会因延迟损害用户体验
   b. bundler 打包的数量低于目标数量，自行承担这部分成本
1. 用户无法根据希望被纳入的速度来“出价”多付或少付。
   a. 在基于 limit 的方式中，如果用户想确保尽快被打包，可以高估成本。如果最终成本较低，也不会被多收费。而在这种方式中，由于 `preVerificationGas` 是静态的，用户必须始终支付其全部出价。
1. 在 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 计量

<CalloutBlock>

💡 这只是针对这一棘手问题的一个非常粗略的解决方案概述。我们很想听听社区的想法！

</CalloutBlock>

L1 calldata gas 成本可以在链上进行计量，user operation 可以只按其产生的确切成本收费，而不是按包含余量的成本收费。应使用基于 limit 的字段。

一种可能的方案是引入一个新字段，`daCallDataGasLimit`（da 代表 Data Availability，暂定名称）。该字段将原生用于 L2+ 版本的 entry point，用于那些交易 calldata 发布到另一个系统、因而需要以不同方式收费的链上。

**entry point 的逻辑大致如下：**

1. 在部署时，entry point 会关联一个辅助合约 `daGasMeter`，该合约有一个函数 `measureUserOperationDaGas\(UserOperation userOp\)`
   a. 该函数接受一个 user operation，并精确测量所使用的 DA gas 数量。
   b. 例如，在 Arbitrum 上，该计量器可以用一个内存中的 user operation 调用 [`gasEstimateL1Component`](https://github.com/OffchainLabs/nitro-contracts/blob/2ba206505edd15ad1e177392c454e89479959ca5/src/node-interface/NodeInterface.sol#L112)，从而*几乎*精确地确定其使用了多少 L1 gas（它无法计算多个 operation 组成 bundle 时因额外压缩而产生的差异）。
1. entry point 会在验证之前对每个 user operation 调用该函数，并将这部分 gas 归入 `daCalldataGasLimit `。
1. 为避免交易回滚，如果 DA gas 使用量超过 `daCalldataGasLimit`，entry point 不会回滚，而是直接进行封顶处理，超出 limit 的部分由 bundler 自行承担。bundler 可以在链下通过确保被打包的每个 operation 在打包前其 limit 字段留有足够高的余量来解决这个问题。

**这种方法存在一些相当显著的缺点：**

1. 不同链上的 entry point 合约将拥有不同的地址。
1. 度量过程中会消耗 gas。
   a. 这个问题相对较小，因为在 L2+ 上，执行 gas 成本通常远低于数据 gas 成本。

## 签名聚合器 gas 计量

可以修改 entry point 合约，用以测量其调用 [`aggregator.validateSignatures`](https://github.com/eth-infinitism/account-abstraction/blob/5e78f8635552e3c55fbced072056a77c8f1e8b75/contracts/core/EntryPoint.sol#L137) 时消耗的 gas 量，将其除以被聚合的 user operation 数量，然后从 `verificationGasLimit` 中均匀扣除，以此分摊 gas 使用量。

每个白名单中的签名聚合器都应关联一个静态值（验证聚合签名的基础成本）和一个动态值（每增加一个被聚合的 operation 所增加的成本）。例如，一个 BLS 签名聚合器可以将其静态值设为一次性的 [signature verification](https://github.com/eth-infinitism/account-abstraction/blob/5e78f8635552e3c55fbced072056a77c8f1e8b75/contracts/samples/bls/BLSSignatureAggregator.sol#L70) 成本，动态值设为 [per op hashing operations](https://github.com/eth-infinitism/account-abstraction/blob/5e78f8635552e3c55fbced072056a77c8f1e8b75/contracts/samples/bls/BLSSignatureAggregator.sol#L68) 成本。bundler 可以在 `eth\_estimateUserOperationGas` 期间，按静态值除以目标 bundle 大小再加上动态值的结果，来提高 `verificationGasLimit`。

#### 我们如何设定目标 bundle 大小？

一种可能的方案是在 `eth\_estimateUserOperationGas` 的参数中补充一个 `minimumBundleSize`，对应用户希望被纳入其中的最小 bundle 大小（也就是最高的 gas）。愿意支付更多的用户可以降低他们的最小 bundle 大小，从而提高被纳入的速度。

bundler 随后需要相应的逻辑，以确保只将某个 UO 纳入大小不小于该 UO 最小 bundle 大小的 bundle 中。这个最小 bundle 大小可以在模拟阶段计算得出：通过追踪账户验证步骤所消耗的 gas 量，将其从 `verificationGasLimit ` 中减去，再根据剩余值计算出最小大小。bundler 可以将该值与 UO 一起存储在其内存池中，并将其用作构建 bundle 时的提示。

<CalloutBlock>

💡 这种方法的一个缺点是，它假设签名聚合器对每个 operation 的 gas 使用是均匀的。如果情况并非如此，计量工作可以转移到聚合器合约上，由其验证函数返回相应结果。bundler 同样需要一种方式在链下计算这些非均匀的值。

</CalloutBlock>

### 这对签名聚合器开发者意味着什么？

签名聚合器很可能需要通过向 bundler 提供链下签名计算方法及其静态/动态 gas 成本参数，直接被 bundler 加入白名单。冷启动问题将会比较棘手，尤其是考虑到用户无法“出价”来加快其 operation 的处理速度。

### 这对账户客户端开发者意味着什么？

理解上述关于 L2 的区别非常重要，尤其是在预估 gas 费用时。在 Optimism 上，如果你使用网络提供的优先费，可能会为某个 user operation 严重超额支付，因为这个优先费现在也适用于 L1 成本。如果使用 Alchemy 的 bundler 端点，请参阅我们的[文档](https://www.alchemy.com/docs/reference/bundler-faqs#how-do-we-determine-fee-values-to-give-your-userop-the-best-chance-of-landing-on-chain)，了解预估费用的技巧。

🦀

## 继续阅读

本系列深入探讨 ERC-4337 gas 预估的下一篇文章，将逐步讲解 user operation 费用预估的过程。如果你错过了第一篇或第二篇，可以了解 ERC-4337 gas 预估、dummy 值以及 token 转账问题的运作方式。

- [How ERC-4337 Gas Estimation Works](https://www.alchemy.com/blog/erc-4337-gas-estimation)
- [Dummy Signatures and Gas Token Transfers](https://www.alchemy.com/blog/dummy-signatures-and-gas-token-transfers)
- [User Operation Fee Estimation](https://www.alchemy.com/blog/user-operation-fee-estimation)
