---
title: "Cómo funciona la estimación de gas de ERC-4337"
description: "Aprende cómo los ingenieros de Alchemy desarrollaron un bundler de ERC-4337 en Rust que proporciona estimaciones de gas precisas para user operations, y conoce los cambios propuestos al contrato entry point para simplificar el proceso de estimación de gas."
---

# Cómo funciona la estimación de gas de ERC-4337

A medida que Alchemy construía su Bundler de ERC-4337, llamado "Rundler", el componente más difícil de implementar correctamente ha sido la estimación de gas de las user operations. Este artículo explica los obstáculos que encontramos al intentar proporcionar a los usuarios estimaciones de gas precisas, y las soluciones que empleamos actualmente.

Esta es una descripción técnica dirigida a desarrolladores de ERC-4337. Si recién estás empezando con [account abstraction estilo ERC-4337](https://www.alchemy.com/overviews/what-is-account-abstraction) te sugerimos comenzar leyendo nuestra [serie de introducción a Account Abstraction](https://www.alchemy.com/overviews/what-is-account-abstraction).

## ¿Por qué es importante la estimación de gas para las user operations de ERC-4337?

Proporcionar estimaciones de gas precisas para las user operations es importante para la experiencia de usuario de ERC-4337. Si una estimación de gas es demasiado baja, una user operation puede revertir durante la simulación, o peor, revertir onchain durante la fase de ejecución, dejando al usuario pagando el gas de una operación revertida. Si la estimación de gas es demasiado alta, un usuario puede verse disuadido de enviar su operación, o no poder hacerlo, debido a los costos.

Si bien es importante ser preciso, la estimación de gas no necesita ser 100% correcta, siempre que los errores sean siempre por sobreestimación \(pero no demasiado\). En ERC-4337 los campos de gas se representan como límites, y al usuario se le reembolsa todo el gas que no consuma onchain. Por lo tanto, la estimación de gas no impacta el costo real de la operación.

<CalloutBlock>

💡 \*Excepto por `preVerificationGas`, más sobre esto luego.

</CalloutBlock>

### Definiciones de los campos de gas de ERC-4337

Los campos de gas en una user operation, y sus definiciones según la [especificación de ERC-4337](https://eips.ethereum.org/EIPS/eip-4337#definitions), son:

- `preVerificationGas`: La cantidad de gas a pagar para compensar al bundler por la ejecución previa a la verificación y el calldata.
- `verificationGasLimit`: La cantidad de gas a asignar para el paso de verificación.
- `callGasLimit`: La cantidad de gas a asignar para la llamada de ejecución principal.
- `maxFeePerGas` : Fee máximo por gas \(similar a `max\_fee\_per\_gas` de [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)\).
- `maxPriorityFeePerGas`: Fee de prioridad máximo por gas \(similar a `max\_priority\_fee\_per\_gas` de EIP-1559\).

## Medición de gas en ERC-4337

Un [bundler de ERC-4337](https://www.alchemy.com/overviews/what-is-a-bundler) paga el costo por adelantado para enviar una transacción en bundle al contrato de entry point. El entry point medirá el gas usado por cada user operation, lo multiplicará por el fee calculado, y compensará al bundler por este valor una vez que la user operation se complete.

El cálculo efectivo se ve así:\*

<CalloutBlock>

💡 \*Por simplicidad esto solo aplica para redes que soportan EIP-1559.

</CalloutBlock>

`preVerificationGas` se agrega tal cual, mientras que el gas de verificación y de llamada son "medidos". Es decir, su uso de gas es medido por el entry point onchain y se les cobra la cantidad exacta que usan, hasta su límite. Si se alcanza el límite en cualquiera de las fases de verificación o de llamada, la operación revertirá.

El entry point no tiene forma de procesar [reembolsos de gas](https://www.evm.codes/about#gasrefunds), ya que mide el gas como parte de una transacción, mientras que los reembolsos de gas se emiten después de que una transacción se completa y por lo tanto se devuelven directamente al Bundler. Esto es una fuente adicional de costo para los usuarios, y una potencial fuente de ingresos para los bundlers.

## Flujo de una user operation

El flujo típico para [enviar una user operation](http://www.alchemy.com/overviews/user-operations) consiste en lo siguiente:

1. Construir una user operation parcial con `sender`, `nonce`, `initCode`, y `callData` poblados.
   a. Además, poblar `signature` y `paymasterAndData` con [valores "dummy"](https://www.alchemy.com/blog/dummy-signatures-and-gas-token-transfers).
1. Estimar el gas para esta user operation parcial mediante un bundler RPC con [`eth\_estimateUserOperationGas`](https://www.alchemy.com/docs/wallets/api-reference/bundler-api/bundler-api-endpoints/eth-estimate-user-operation-gas).
   a. Poblar `preVerificationGas`, `verificationGasLimit`, `callGasLimit` a partir del valor de retorno.
1. [Estimar los fees de gas requeridos](https://www.alchemy.com/blog/user-operation-fee-estimation) para la operación y poblar `maxFeePerGas` y `maxPriorityFeePerGas`.
   a. Este paso no depende de los pasos 1 o 2, puede ejecutarse en cualquier momento, o en paralelo.
1. \(Opcional\) Enviar su user operation a un paymaster patrocinador para su firma
   a. Poblar `paymasterAndData` a partir del valor de retorno.
1. Firmar la user op anterior, poblar `signature`, y enviarla a un bundler mediante [`eth\_sendUserOperation`](https://www.alchemy.com/blog/erc-4337-gas-estimation#).

Este artículo se centrará en el paso 2 y en el método RPC del bundler `eth\_estimateUserOperationGas`.

### Estimación de gas de la user operation

`eth\_estimateUserOperationGas` es un método RPC que los bundlers deben soportar según la [especificación de ERC-4337](https://eips.ethereum.org/EIPS/eip-4337#-eth_estimateuseroperationgas).

**Su definición:**

_Estimar los valores de gas para una UserOperation. Dada una UserOperation opcionalmente sin límites de gas ni precios de gas, devolver los límites de gas necesarios. El campo signature es ignorado por la wallet, de modo que la operación no requerirá la aprobación del usuario. Aun así, podría requerir poner una firma "semi-válida" \(por ejemplo, una firma con la longitud correcta\)._

**\*Parámetros****: los mismos que en *`eth_sendUserOperation`*; los parámetros de límites de gas \(y precios\) son opcionales, pero se usan si se especifican. *`maxFeePerGas`* y *`maxPriorityFeePerGas`* son cero por defecto, por lo que no se requiere pago ni de la cuenta ni del paymaster.\*

**_Valores de retorno:_**

- **\*preVerificationGas**** overhead de gas de esta UserOperation\*
- **\*verificationGasLimit**** gas real usado por la validación de esta UserOperation\*
- **\*callGasLimit**** valor usado por la ejecución interna de la cuenta\*

### Cálculo de PreVerificationGas

`PreVerificationGas` se usa para capturar cualquier uso de gas que el entry point no mide, compensando al bundler por este gas.

**En el caso simple esto se puede descomponer en 3 cálculos separados:**

1. La parte proporcional de la operación en el [gas intrínseco](https://ethereum.org/en/developers/docs/gas/#what-is-gas-limit) de la transacción en bundle.
   a. Nótese que el bundler debe asumir un tamaño de bundle para determinar esto por adelantado.
1. La parte proporcional de la operación en el costo de gas del calldata.
   a. Esto es directamente atribuible al tamaño y la composición en bytes de la operación.
1. La parte proporcional de la operación en cualquier overhead de gas de ejecución en el que incurra el entry point fuera de lo que se mide.
   a. Esto se determina offchain mediante un análisis del uso de gas del contrato de entry point, atribuyéndolo por user operation.

Nótese que `preVerificationGas` no es un límite. Es decir, el valor fijado en este campo siempre se paga al bundler en su totalidad. Hay que tener cuidado con este campo, ¡ya que un valor incorrecto podría significar enviarle al bundler más fees de los necesarios!

Revisa este [análisis detallado](https://www.stackup.sh/blog/an-analysis-of-preverificationgas) de `preVerificationGas` escrito por el equipo de StackUp para más detalle.

Los bundlers típicamente ejecutan su cálculo de `preVerificationGas` durante la fase de "pre-check" \(es decir, las verificaciones que se ejecutan antes de la simulación completa basada en trazas\).

Si la `preVerificationGas` de una operación es menor que el valor calculado por el bundler, este la rechazará. Esto puede convertirse en una fuente de incompatibilidad entre implementaciones de bundler.

Si una implementación particular de bundler, L, está estimando un valor más bajo para `preVerificationGas`, y una implementación diferente, H, está estimando \(y requiriendo\) un valor más alto, H rechazará las operaciones que usaron L para su estimación.

Aún está por verse cómo esta incompatibilidad impactará el mempool P2P, pero es probable que los usuarios estén incentivados a estimar un `preVerificationGas` ligeramente más alto para maximizar sus posibilidades de ser incluidos en un bundle.

## Cómo el bundler de ERC-4337 de Alchemy estima los límites de gas

Proporcionar estimaciones precisas para los campos de límite de gas, `verificationGasLimit` y `callGasLimit`, es importante. El objetivo puede formularse así: proporcionar una función que estime el gas usado durante estas fases asegurándose de solo sobreestimar, pero no tanto como para disuadir al usuario de enviar su operación.

Veamos los distintos intentos que hemos hecho para proporcionar esta función en [Rundler](https://www.alchemy.com/account-abstraction).

### Intento 1: `eth\_estimateGas`

El JSON-RPC estándar de Ethereum proporciona un método adecuado para estimar el gas de las transacciones, ¡usemos simplemente [`eth\_estimateGas`](https://www.alchemy.com/docs/chains/ethereum/ethereum-api-endpoints/eth-estimate-gas)!

**El método funciona así:**

- `verificationGasLimit `: llamar a la función `simulateValidation` en el entry point y pasarla a `eth\_estimateGas`
- `callGasLimit`: llamar a la función `innerHandleOp` en el entry point y pasarla a `eth\_estimateGas`

Esto parece funcionar bien. Si bien tanto `simulateValidation` como `innerHandleOp` no capturan exactamente las partes medidas de cada fase, son estrictamente superconjuntos y sobreestimarán ligeramente.

Nótese que estas llamadas son completamente independientes, pero onchain se ejecutan en una única transacción. En este método el resultado de `simulateValidation` no se persiste para impactar a `innerHandleOp`.

#### ¿Qué pasa si `innerHandleOp` depende de algo hecho durante `simulateValidation`?

Este es el caso del despliegue inicial de una cuenta, y posiblemente de algunos esquemas de validación avanzados. El método de la factory se llama durante la fase de validación para desplegar el contrato de la cuenta. La fase de ejecución requiere que este contrato esté desplegado y `innerHandleOp` revertirá si no lo está, haciendo fallar cualquier intento de estimación.

Necesitamos alguna forma de estimar ambos campos de límite de gas asegurándonos de que la estimación para la fase de ejecución se ejecute en la misma llamada que la fase de validación.

Pasemos al siguiente intento…

### Intento 2: simular y medir

La implementación v0.6 de ERC-4337 proporciona un método [`simulateHandleOp` ](https://github.com/eth-infinitism/account-abstraction/blob/033b4be2a606defd5cd5226bdde3afcea21db5fa/contracts/core/EntryPoint.sol#L184)que combina tanto la fase de validación como la de ejecución en una única llamada de función. Su valor de retorno contiene [`preOpGas`](https://www.alchemy.com/blog/erc-4337-gas-estimation#), que es la suma del gas usado durante la validación y el `preVerificationGas`. También contiene el monto total pagado por la user op, `paid`.

Para determinar el `callGasLimit`, `paid` puede convertirse en gas usado dividiendo por el fee de gas. El bundler puede determinar el fee de gas fijando la simulación a una altura de bloque \(reciente\) con un base fee conocido y estableciendo el priority fee en 0. Mediante algunas conversiones simples, se pueden calcular todos los campos de gas. Este método también puede sobreestimar el gas usado, ya que parte de la lógica no medida del entry point se atribuye al gas usado.

Otro método razonable para medir el gas usado durante la fase de llamada es medir el gas usado durante toda la llamada `simulateHandleOp` y luego restar el gas usado por la fase de validación.

Podemos desplegar un contrato auxiliar que mida el gas usado por `simulateHandleOp` y lo agregue al valor de retorno. Este método tiene el mismo problema de sobreestimación que el anterior.

Para quienes son exigentes con las sobreestimaciones, podemos refinar esto aún más.

#### ¿Podemos trazar el método `simulateHandleOp` del entry point para calcular el gas?

Los bundlers deben tener la capacidad de ejecutar una llamada de traza para realizar las verificaciones de simulación necesarias para protegerse de invalidaciones onchain. ¿Podemos usar esta funcionalidad para estimar gas?

**El método funciona así:**

1. Trazar el método `simulateHandleOp` del entry point, que convenientemente usa un esquema de "marcadores de número" para que el tracer pueda saber exactamente en qué fase de ejecución se encuentra.
1. En el tracer, tomar nota de la cantidad de gas restante al comienzo de cada fase, y al final de todo.
1. A partir de estos valores, calcular la cantidad de gas usado por cada fase.

¡Genial! Hemos usado trazas ligeramente más costosas para calcular exactamente la cantidad de gas usado.

No tan rápido, usar cualquiera de los métodos anteriores puede hacer que las operaciones se queden sin gas para ciertas llamadas a contrato.

¿Por qué?

Para responder eso podemos tomar como referencia [cómo Reth estima el gas](https://github.com/paradigmxyz/reth/blob/547911ac19c68b750f7920a6cd613fa691328ce7/crates/rpc/rpc/src/eth/api/call.rs#L72). ¿Notaste la búsqueda binaria? ¿Qué está pasando ahí? ¿Por qué Reth no usa simplemente un método de trazado similar al de arriba, ya que tiene acceso a la misma información?

Bueno, resulta que en una llamada de función del EVM: **gas usado ≠ gas requerido**.

Se puede encontrar una explicación de por qué esto es así en el artículo de Sergio Lerner "[The Dark Side of Ethereum 1/64th CALL Gas Reduction](https://medium.com/iovlabs-innovation-stories/the-dark-side-of-ethereum-1-64th-call-gas-reduction-ba661778568c)". La más importante de estas razones se llama la ["Regla del 63/64"](https://eips.ethereum.org/EIPS/eip-150) \(o la "Regla del 1/64" dependiendo de con quién hables\).

Esta regla establece que el EVM solo reenviará 63/64 del gas restante a cada llamada de función. El resultado de esto es que se necesita ligeramente más gas por adelantado que el que eventualmente se consume. Esto ocurre en cada llamada, por lo que una fase de validación/ejecución con una pila de llamadas profunda podría necesitar reservar una gran cantidad de gas por adelantado para manejar el gas reservado.

Queremos una solución que funcione para TODAS las user operations potenciales, así que esto invalida cualquier intento que use mediciones de gas usado para la estimación del límite de gas. Para nuestro próximo intento, tomemos inspiración de los clientes del EVM.

<CalloutBlock>

💡 Podría ser posible realizar un análisis detallado de una función de trazado y retroceder desde el gas usado hasta el límite de gas teniendo en cuenta la regla del 63/64 \(y cualquier otra particularidad del EVM\). Aún no hemos explorado este enfoque.

</CalloutBlock>

### Intento 3: búsqueda binaria

No reinventemos la rueda aquí. Para manejar la complejidad descrita anteriormente, los clientes de nodo usan búsquedas binarias durante la estimación de gas, encontrando el valor de gas más bajo que lleva a una transacción exitosa. ¡Podemos hacer algo similar para las user operations!

El enfoque funciona así:

Primero, nótese que dado que la búsqueda binaria requiere muchas llamadas a la misma función, queremos asegurarnos de que la función tenga un buen rendimiento. Esto elimina el uso de trazado como un enfoque razonable aquí, y necesitaremos depender de `eth\_call`.

**Para estimar** `verificationGasLimit`**:**

1. Ejecutar un intento de `simulateHandleOp` en `MAX\_VERIFICATION\_GAS` \([una configuración del bundler](https://eips.ethereum.org/EIPS/eip-4337#client-behavior-upon-receiving-a-useroperation) que define el gas máximo que se puede usar en la fase de validación\) para asegurarse de que el éxito sea posible en primer lugar.
   a. Durante esta ejecución, establecer `callGasLimit` en 0 para ahorrar cómputo.
1. Ejecutar la búsqueda binaria sobre `simulateHandleOp` para acotar hasta el valor más bajo de `verificationGasLimit` que no se quede sin gas.
   a. Si la fase de validación se queda sin gas la llamada revertirá. Dado que ya hemos verificado que la llamada puede tener éxito con un límite de gas más alto, cualquier reversión puede resultar en mover el límite inferior de la búsqueda binaria hacia arriba.

Una optimización útil aquí es notar que no necesitamos una estimación 100% precisa. El bundler puede elegir cuán cerca del valor exacto quiere estar, y terminar la búsqueda binaria una vez que esté dentro de ese rango, siempre errando hacia el lado alto. Esto puede ahorrar muchas rondas de llamadas. Por ejemplo, Rundler establece este rango de error en 1K de gas, ahorrando 10 iteraciones.

La segunda optimización para ahorrar algunas iteraciones es determinar un mejor punto de partida para la búsqueda. Multiplicar el gas usado en \(1\) por un escalar \(por ejemplo, Reth usa 3x\), y establecer eso como la suposición inicial para el algoritmo de búsqueda binaria.

**Para estimar** `callGasLimit`**:**

1. Establecer `verificationGasLimit` en `MAX\_VERIFICATION\_GAS`
1. Ejecutar un intento de `MAX\_EXECUTION\_GAS` \(no forma parte de la especificación, pero es requerido para que un bundler se proteja contra DOS\) y verificar si el éxito de la ejecución es posible.

Aquí es donde este método de estimación se rompe. Las reversiones son [capturadas por el entry point y emitidas como logs](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol#L250) y `eth\_call` no proporciona una forma de inspeccionar ningún log emitido. También desperdicia cómputo al ejecutar la validación completa durante cada intento.

¿Podemos proporcionar una forma de ejecutar esta búsqueda binaria de `callGasLimit`?

<CalloutBlock>

💡 Es posible detectar esta reversión si se usa una traza, pero preferimos evitarlo por razones de costo. Podría valer la pena reconsiderar esta decisión si un bundler tiene control total sobre el cliente de nodo y puede usar un tracer nativo.

</CalloutBlock>

#### Solución: búsqueda binaria en Solidity

Basándonos en el procedimiento para la estimación de `verificationGasLimit`, la idea central detrás de este método es ejecutar la búsqueda binaria para `callGasLimit` en [Solidity](https://www.alchemy.com/overviews/solidity)**,** codificando lógica que pueda capturar si la parte de ejecución falla.

**Definamos las restricciones:**

1. La búsqueda binaria debe ocurrir después de que se ejecute la validación para asegurar que cualquier contrato de cuenta esté desplegado
   a. Una restricción deseable, pero no requerida, sería ejecutar la validación una sola vez \(o limitar la cantidad de veces\) para reducir el cómputo desperdiciado.
1. Las llamadas de ejecución deben iniciarse desde el entry point. Muchas implementaciones de cuenta solo permiten que se llame a sus funciones de ejecución cuando `msg.sender` es igual a una dirección de entry point codificada de forma fija.
1. La búsqueda binaria debe devolver su resultado final.
1. Todas las funciones del entry point deben comportarse exactamente como lo harían normalmente.

La solución que encontramos es usar un contrato proxy que utiliza `DELEGATECALL` para reenviar al contrato de entry point, pero agregando lógica adicional. Durante la estimación usamos overrides de `eth\_call` para mover el contrato de entry point original a una dirección aleatoria, y reemplazarlo con el contrato proxy. Esto satisface las restricciones \(2\) y \(4\).

Como se señaló anteriormente, el entry point oculta el resultado de la fase de ejecución. En su lugar, omitimos la llamada de ejecución estándar \(estableciendo `callGasLimit` en 0\), y utilizamos los argumentos `target/targetCallData` de `simulateHandleOp`. Esto se ejecuta después de la validación y puede devolver información, satisfaciendo las restricciones \(1\) y \(3\). Ver el código del entry point abajo \(se agregaron comentarios\):

Para nuestra llamada de target personalizada llamamos a un método, `estimateCallGas`, en el contrato proxy con datos de llamada que codifican:

1. La cuenta y su calldata \(de la user operation original\)
1. Los parámetros de la búsqueda binaria

Dado que el proxy está ubicado en la dirección del entry point original, `estimateCallGas` puede llamar a la función de ejecución de la cuenta, determinar si la llamada tuvo éxito o se quedó sin gas, y ejecutar el algoritmo de búsqueda binaria hasta lograr el éxito.

¡Listo! La búsqueda binaria ahora puede ocurrir en una `eth\_call` para obtener una estimación precisa de `callGasLimit`.

En la práctica, Rundler divide esta única `eth\_call` en múltiples llamadas, cada una con un límite de gas global máximo, para sortear los límites máximos de gas por llamada de los nodos. Cada llamada ejecutará iteraciones de búsqueda binaria hasta que la siguiente iteración haría que la llamada se quedara sin gas.

<CalloutBlock>

💡 Este método no captura una pequeña cantidad de overhead impuesto por el entry point durante `innerHandleOp`. Este overhead es estático y se agrega al resultado de la estimación.

</CalloutBlock>

## Posible cambio en el entry point

Hemos estado pensando en posibles cambios al entry point que podrían hacer que el proceso de estimación de gas descrito arriba sea más fácil de implementar y más performante para los usuarios. Este no es un problema fácil de resolver, ¡así que nos encantaría saber de otros bundlers o miembros de la comunidad de ERC-4337 si tienen ideas!

Queremos asegurarnos de que la fase de validación se ejecute antes que la fase de ejecución durante la estimación de gas. La fase de ejecución puede depender de estado establecido durante la fase de validación \(es decir, una transferencia de USDC de un paymaster y una fase de ejecución que usa USDC\) y por lo tanto es más preciso estimar justo después.

Una posible mejora sería que el entry point devuelva un booleano que represente si la parte de ejecución revirtió o no durante `simulateHandleOp`. El bundler podría entonces ejecutar la búsqueda binaria offchain sin el contrato proxy ni los overrides de estado. Sin embargo, esta búsqueda binaria será ineficiente ya que ejecutará la fase de validación durante cada iteración.

Esto nos lleva a tomar la búsqueda binaria realizada por el contrato proxy en el intento 3 anterior y agregar su funcionalidad directamente al contrato de entry point \(pseudocódigo\):

Nuestra implementación de lo anterior \(como contrato proxy\) será open source pronto. Creemos que podría ser beneficioso para los usuarios de ERC-4337 que la estimación mediante búsqueda binaria forme parte del propio contrato de entry point.

¡Estate atento a nuestra implementación open source de bundler, que llega pronto! 🦀

## Continúa leyendo

Los siguientes artículos de esta serie de 4 partes exploran los desafíos de estimación de gas que encontramos y las soluciones que implementó nuestro equipo de ingeniería.

- [Dummy Signatures and Gas Token Transfers](https://www.alchemy.com/blog/dummy-signatures-and-gas-token-transfers)
- [Solving The L2 and Aggregator Problem](https://www.alchemy.com/blog/l2-gas-and-signature-aggregators)
- [User Operation Fee Estimation](https://www.alchemy.com/blog/user-operation-fee-estimation)
