---
title: "Como funciona a estimativa de gas no ERC-4337"
description: "Saiba como os engenheiros da Alchemy desenvolveram um bundler ERC-4337 em Rust que fornece estimativas precisas de gas para user operations, e conheça as mudanças sugeridas no entry point contract para simplificar o processo de estimativa de gas."
---

# Como funciona a estimativa de gas no ERC-4337

Quando a Alchemy construiu seu Bundler ERC-4337, chamado “Rundler”, o componente mais desafiador de acertar foi a estimativa de gas de user operation. Este post explicará os obstáculos que encontramos ao tentar fornecer aos usuários estimativas de gas precisas, e as soluções que atualmente empregamos.

Esta é uma visão técnica voltada para desenvolvedores de ERC-4337. Se você é novo em [ERC-4337 style account abstraction](https://www.alchemy.com/overviews/what-is-account-abstraction), sugerimos começar lendo nossa [intro to Account Abstraction series](https://www.alchemy.com/overviews/what-is-account-abstraction).

## Por que a estimativa de gas é importante para user operations do ERC-4337?

Fornecer estimativas de gas precisas para user operations é importante para a experiência do usuário no ERC-4337. Se uma estimativa de gas for baixa demais, uma user operation pode reverter durante a simulação, ou pior, reverter onchain durante a fase de execução, deixando o usuário pagando pelo gas de uma operação revertida. Se a estimativa de gas for alta demais, um usuário pode ser desencorajado, ou impossibilitado, de enviar sua operação devido aos custos.

Embora seja importante ser preciso, a estimativa de gas não precisa ser 100% correta, desde que os erros sempre superestimem \(mas não demais\). No ERC-4337 os campos de gas são representados como limites, e o usuário recebe reembolso de qualquer gas que não consumir onchain. Assim, a estimativa de gas não afeta o custo real da operação.

<CalloutBlock>

💡 \*Exceto por `preVerificationGas`, mais sobre isso adiante.

</CalloutBlock>

### Definições dos campos de gas do ERC-4337

Os campos de gas em uma user operation, e suas definições conforme a [ERC-4337 spec](https://eips.ethereum.org/EIPS/eip-4337#definitions), são:

- `preVerificationGas`: A quantidade de gas a pagar para compensar o bundler pela execução de pré-verificação e pelo calldata.
- `verificationGasLimit`: A quantidade de gas a alocar para a etapa de verificação.
- `callGasLimit`: A quantidade de gas a alocar para a chamada de execução principal.
- `maxFeePerGas` : Taxa máxima por gas \(similar ao `max\_fee\_per\_gas` do [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)\).
- `maxPriorityFeePerGas`: Taxa de prioridade máxima por gas \(similar ao `max\_priority\_fee\_per\_gas` do EIP-1559\).

## Medição de gas no ERC-4337

Um [ERC-4337 bundler](https://www.alchemy.com/overviews/what-is-a-bundler) paga o custo antecipadamente para enviar uma transação de bundle ao contrato entry point. O entry point vai medir o gas usado por cada user operation, multiplicar isso pela fee calculada, e compensar o bundler por esse valor após a conclusão da user operation.

O cálculo efetivo é assim:\*

<CalloutBlock>

💡 \*Para simplificar, isso vale apenas para redes que suportam EIP-1559.

</CalloutBlock>

`preVerificationGas` é adicionado como está, enquanto o gas de verificação e de call são “medidos”. Ou seja, seu uso de gas é medido pelo entry point onchain e são cobrados pela quantidade exata que usam, até seu limite. Se o limite for atingido tanto na fase de verificação quanto na de call, a operação reverterá.

O entry point não tem como processar [gas refunds](https://www.evm.codes/about#gasrefunds), pois ele mede o gas como parte de uma transação, enquanto os gas refunds são emitidos após a conclusão de uma transação e, portanto, são retornados diretamente ao Bundler. Essa é uma fonte extra de custo para os usuários, e uma fonte potencial de receita para bundlers.

## Fluxo da user operation

O fluxo típico para [enviar uma user operation](http://www.alchemy.com/overviews/user-operations) consiste no seguinte:

1. Construir uma user operation parcial com `sender`, `nonce`, `initCode`, e `callData` preenchidos.
   a. Também preencher `signature` e `paymasterAndData` com [valores "dummy"](https://www.alchemy.com/blog/dummy-signatures-and-gas-token-transfers).
1. Estimar o gas para essa user operation parcial via um bundler RPC com [`eth\_estimateUserOperationGas`](https://www.alchemy.com/docs/wallets/api-reference/bundler-api/bundler-api-endpoints/eth-estimate-user-operation-gas).
   a. Preencher `preVerificationGas`, `verificationGasLimit`, `callGasLimit` a partir do valor de retorno.
1. [Estimar as fees de gas necessárias](https://www.alchemy.com/blog/user-operation-fee-estimation) para a operação e preencher `maxFeePerGas` e `maxPriorityFeePerGas`.
   a. Esta etapa não depende das etapas 1 ou 2, pode ser executada a qualquer momento, ou em paralelo.
1. \(Opcional\) Enviar sua user operation para um paymaster patrocinador para assinatura
   a. Preencher `paymasterAndData` a partir do valor de retorno.
1. Assinar a user op acima, preencher `signature`, e enviar a um bundler via [`eth\_sendUserOperation`](https://www.alchemy.com/blog/erc-4337-gas-estimation#).

Este post vai focar na etapa 2 e no método RPC do bundler `eth\_estimateUserOperationGas`.

### Estimativa de gas da user operation

`eth\_estimateUserOperationGas` é um método RPC que bundlers devem suportar conforme a [ERC-4337 specification](https://eips.ethereum.org/EIPS/eip-4337#-eth_estimateuseroperationgas).

**Sua definição:**

_Estimar os valores de gas para uma UserOperation. Dada uma UserOperation opcionalmente sem limites de gas e preços de gas, retornar os limites de gas necessários. O campo signature é ignorado pela wallet, de modo que a operação não exigirá aprovação do usuário. Ainda assim, pode ser necessário colocar uma assinatura "semi-válida" \(por exemplo, uma assinatura com o tamanho correto\)._

**\*Parâmetros****: iguais aos de *`eth_sendUserOperation`*; os parâmetros de limites de gas \(e preços\) são opcionais, mas são usados se especificados. *`maxFeePerGas`* e *`maxPriorityFeePerGas`* têm valor padrão zero, de modo que nenhum pagamento é exigido nem da account nem do paymaster.\*

**_Valores de retorno:_**

- **\*preVerificationGas**** overhead de gas desta UserOperation\*
- **\*verificationGasLimit**** gas real usado pela validação desta UserOperation\*
- **\*callGasLimit**** valor usado pela execução interna da account\*

### Cálculo do PreVerificationGas

`PreVerificationGas` é usado para capturar qualquer uso de gas que o entry point não mede, compensando o bundler por esse gas.

**No caso simples isso pode ser decomposto em 3 cálculos separados:**

1. A parcela da operação no [intrinsic gas](https://ethereum.org/en/developers/docs/gas/#what-is-gas-limit) da transação de bundle.
   a. Note que o bundler deve assumir um tamanho de bundle para determinar isso previamente.
1. A parcela da operação no custo de gas do calldata.
   a. Isso é diretamente atribuível ao tamanho e à composição de bytes da operação.
1. A parcela da operação em qualquer overhead de gas de execução que o entry point incorre fora do que é medido.
   a. Isso é determinado offchain fazendo uma análise de uso de gas do contrato entry point e atribuindo-o por user operation.

Note que `preVerificationGas` não é um limite. Ou seja, o valor definido nesse campo é sempre pago integralmente ao bundler. Tome cuidado com esse campo, pois um valor incorreto pode significar enviar ao bundler mais fees do que o necessário!

Confira esta [análise aprofundada](https://www.stackup.sh/blog/an-analysis-of-preverificationgas) de `preVerificationGas` escrita pela equipe da StackUp para mais detalhes.

Bundlers normalmente executam seu cálculo de `preVerificationGas` durante a fase de "pre-check" \(ou seja, verificações executadas antes da simulação completa baseada em trace\).

Se o `preVerificationGas` de uma operação for menor que o valor calculado pelo bundler, ele a rejeitará. Isso pode se tornar uma fonte de incompatibilidade entre implementações de bundler.

Se uma implementação de bundler específica, L, está estimando um valor menor para `preVerificationGas` , e uma implementação diferente, H, está estimando \(e exigindo\) um valor maior, H rejeitará operações que usaram L para a estimativa.

Ainda não se sabe como essa incompatibilidade vai impactar o mempool P2P, mas é provável que os usuários sejam incentivados a estimar um `preVerificationGas` ligeiramente maior para maximizar suas chances de serem incluídos em um bundle.

## Como o bundler ERC-4337 da Alchemy estima os limites de gas

Fornecer estimativas precisas para os campos de limite de gas, `verificationGasLimit` e `callGasLimit`, é importante. O objetivo pode ser descrito como: fornecer uma função que estime o gas usado durante essas fases, garantindo que sempre superestime, mas não tanto a ponto de desencorajar o usuário a enviar sua operação.

Vamos discutir as várias tentativas que fizemos para fornecer essa função no [Rundler](https://www.alchemy.com/account-abstraction).

### Tentativa 1: `eth\_estimateGas`

O JSON-RPC padrão do Ethereum fornece um método conveniente para estimar gas de transações, vamos apenas usar [`eth\_estimateGas`](https://www.alchemy.com/docs/chains/ethereum/ethereum-api-endpoints/eth-estimate-gas)!

**O método funciona assim:**

- `verificationGasLimit `: chamar a função `simulateValidation` no entry point e passar para `eth\_estimateGas`
- `callGasLimit`: chamar a função `innerHandleOp` no entry point e passar para `eth\_estimateGas`

Isso parece funcionar bem. Embora tanto `simulateValidation` quanto `innerHandleOp` não capturem exatamente as partes medidas de cada fase, elas são estritamente supersets e vão superestimar levemente.

Note que essas chamadas são completamente independentes, mas onchain são executadas em uma única transação. Nesse método, o resultado de `simulateValidation` não é persistido de forma a afetar `innerHandleOp`.

#### E se `innerHandleOp` depender de algo feito durante `simulateValidation`?

Esse é o caso da implantação inicial de uma account, e possivelmente de alguns esquemas de validação avançados. O método da factory é chamado durante a fase de validação para implantar o contrato da account. A fase de execução requer que esse contrato esteja implantado e `innerHandleOp` reverterá se não estiver, fazendo com que qualquer tentativa de estimativa falhe.

Precisamos de alguma forma de estimar ambos os campos de limite de gas garantindo que a estimativa para a fase de execução seja executada na mesma chamada que a fase de validação.

Vamos à próxima tentativa…

### Tentativa 2: simular e medir

A implementação do ERC-4337 v0.6 fornece um método [`simulateHandleOp` ](https://github.com/eth-infinitism/account-abstraction/blob/033b4be2a606defd5cd5226bdde3afcea21db5fa/contracts/core/EntryPoint.sol#L184)que combina tanto a fase de validação quanto a de execução em uma única chamada de função. Seu valor de retorno contém [`preOpGas`](https://www.alchemy.com/blog/erc-4337-gas-estimation#), que é a soma do gas usado durante a validação e o `preVerificationGas`. Ele também contém o valor total pago pela user op, `paid`.

Para determinar o `callGasLimit`, `paid` pode ser convertido em gas usado dividindo pela fee de gas. O bundler pode determinar a fee de gas fixando a simulação em uma altura de bloco \(recente\) com uma base fee conhecida e definindo a priority fee como 0. Através de algumas conversões simples, todos os campos de gas podem ser calculados. Esse método também pode superestimar o gas usado, já que parte da lógica não medida do entry point é atribuída ao gas usado.

Outro método razoável para medir o gas usado durante a fase de call é medir o gas usado durante toda a chamada `simulateHandleOp` e então subtrair o gas usado pela fase de validação.

Podemos implantar um contrato auxiliar que mede o gas usado por `simulateHandleOp` e anexa isso ao valor de retorno. Esse método tem o mesmo problema de superestimativa que o anterior.

Para quem é exigente com relação a superestimativas, podemos refinar isso ainda mais.

#### Podemos fazer trace do método `simulateHandleOp` do entry point para calcular o gas?

Bundlers devem ter a capacidade de executar uma chamada de trace para realizar as verificações de simulação necessárias para se proteger de invalidação onchain. Podemos usar essa funcionalidade para estimar gas?

**O método funciona assim:**

1. Fazer trace do método `simulateHandleOp` do entry point, que convenientemente usa um esquema de "number marker" para que o tracer saiba exatamente em qual fase da execução está.
1. No tracer, anotar a quantidade de gas restante no início de cada fase, e no final.
1. A partir desses valores, calcular a quantidade de gas usada por cada fase.

Ótimo! Usamos um trace ligeiramente mais caro para calcular exatamente a quantidade de gas usada.

Não tão rápido assim, usar qualquer um dos métodos acima pode fazer com que operações fiquem sem gas para determinadas chamadas de contrato.

Por quê?

Para responder a isso podemos nos inspirar em [como o Reth estima gas](https://github.com/paradigmxyz/reth/blob/547911ac19c68b750f7920a6cd613fa691328ce7/crates/rpc/rpc/src/eth/api/call.rs#L72). Notou a busca binária? O que está acontecendo ali? Por que o Reth simplesmente não usa um método de trace semelhante ao de cima, já que tem acesso à mesma informação?

Bem, acontece que em uma chamada de função do EVM: **gas usado ≠ gas necessário**.

Uma visão geral do motivo disso pode ser encontrada no artigo 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)". A mais importante dessas razões é chamada de ["63/64th Rule"](https://eips.ethereum.org/EIPS/eip-150) \(ou "1/64th Rule" dependendo de com quem você está falando\).

Essa regra afirma que o EVM só encaminha 63/64 do gas restante para cada chamada de função. O resultado disso é que você precisa de um pouco mais de gas antecipadamente do que o que é eventualmente consumido. Isso acontece a cada chamada, então uma fase de validação/execução com uma pilha de chamadas profunda pode precisar reservar uma grande quantidade de gas antecipadamente para lidar com o gas reservado.

Queremos uma solução que funcione para TODAS as user operations possíveis, então isso invalida quaisquer tentativas que usem medições de gas usado para estimativa de limite de gas. Para nossa próxima tentativa, vamos nos inspirar nos clientes EVM.

<CalloutBlock>

💡 Pode ser possível fazer uma análise detalhada de uma função de trace e retroceder o gas usado para gas limit levando em conta a regra dos 63/64 \(e quaisquer outras particularidades do EVM\). Ainda não exploramos esse ângulo.

</CalloutBlock>

### Tentativa 3: busca binária

Vamos não reinventar a roda aqui. Para lidar com a complexidade descrita acima, os clientes de nó usam buscas binárias durante a estimativa de gas, encontrando o menor valor de gas que leva a uma transação bem-sucedida. Podemos fazer algo semelhante para user operations!

A abordagem é a seguinte:

Primeiro, note que, como a busca binária requer várias chamadas à mesma função, queremos garantir que a função tenha alto desempenho. Isso elimina o uso de trace como uma abordagem razoável aqui, e precisaremos depender de `eth\_call`.

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

1. Executar uma tentativa de `simulateHandleOp` em `MAX\_VERIFICATION\_GAS` \([uma configuração do bundler](https://eips.ethereum.org/EIPS/eip-4337#client-behavior-upon-receiving-a-useroperation) que define o gas máximo que pode ser usado na fase de validação\) para garantir que o sucesso seja pelo menos possível.
   a. Durante essa execução, definir `callGasLimit` como 0 para economizar computação.
1. Executar a busca binária em `simulateHandleOp` para chegar ao menor valor de `verificationGasLimit` que não fica sem gas.
   a. Se a fase de validação ficar sem gas, a chamada reverterá. Já que verificamos que a chamada pode ter sucesso com um limite de gas maior, quaisquer reversões podem fazer com que a borda inferior da busca binária suba.

Uma boa otimização aqui é notar que não precisamos de uma estimativa 100% precisa. O bundler pode escolher o quão próximo do exato quer ser, e encerrar a busca binária uma vez que esteja dentro dessa margem, sempre errando para mais. Isso pode economizar várias rodadas de chamadas. Por exemplo, o Rundler define essa margem de erro em 1K de gas, economizando 10 iterações.

A segunda otimização para economizar algumas iterações é determinar um ponto de partida melhor para a busca. Multiplique o gas usado em \(1\) por um escalar \(por exemplo, o Reth usa 3x\), e use isso como estimativa inicial para o algoritmo de busca binária.

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

1. Definir `verificationGasLimit` como `MAX\_VERIFICATION\_GAS`
1. Executar uma tentativa de `MAX\_EXECUTION\_GAS` \(não faz parte da spec, mas é necessário para que um bundler se proteja contra DOS\) e verificar se o sucesso da execução é possível.

É aqui que esse método de estimativa falha. Reversões são [capturadas pelo entry point e emitidas como logs](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol#L250) e `eth\_call` não fornece uma forma de inspecionar quaisquer logs emitidos. Também desperdiça computação ao executar a validação por completo em cada tentativa.

Podemos fornecer uma forma de executar essa busca binária de `callGasLimit`?

<CalloutBlock>

💡 É possível detectar essa reversão usando um trace, mas gostaríamos de evitar isso por questões de custo. Pode valer a pena revisitar essa decisão se um bundler tiver controle total sobre o node client e puder usar um tracer nativo.

</CalloutBlock>

#### Solução: busca binária em Solidity

Construindo sobre o procedimento para a estimativa de `verificationGasLimit`, a ideia central desse método é executar a busca binária para `callGasLimit` em [Solidity](https://www.alchemy.com/overviews/solidity)**,** codificando lógica que consiga capturar se a parte de execução falha.

**Vamos definir as restrições:**

1. A busca binária deve ocorrer após a validação ser executada para garantir que quaisquer contratos de account estejam implantados
   a. Uma restrição desejável, mas não obrigatória, seria executar a validação apenas uma vez \(ou limitar a quantidade de vezes\) para reduzir computação desperdiçada.
1. As chamadas de execute devem ser iniciadas a partir do entry point. Muitas implementações de account só permitem que suas funções execute sejam chamadas quando `msg.sender` for igual a um endereço de entry point fixo.
1. A busca binária deve retornar seu resultado final.
1. Todas as funções do entry point devem se comportar exatamente como se comportariam normalmente.

A solução que encontramos foi usar um contrato proxy que usa `DELEGATECALL` para encaminhar para o contrato entry point, mas adiciona lógica adicional. Durante a estimativa usamos overrides de `eth\_call` para mover o contrato entry point original para um endereço aleatório, e substituí-lo pelo contrato proxy. Isso satisfaz as restrições \(2\) e \(4\).

Como observado acima, o entry point oculta o resultado da fase de execução. Em vez disso, pulamos a chamada de execução padrão \(definindo `callGasLimit` como 0\), e utilizamos os argumentos `target/targetCallData` de `simulateHandleOp`. Isso é executado após a validação e pode retornar informações, satisfazendo as restrições \(1\) e \(3\). Veja o código do entry point abaixo \(comentários adicionados\):

Para nossa custom target call chamamos um método, `estimateCallGas`, no contrato proxy com dados de call que codificam:

1. A account e seu call data \(da user operation original\)
1. Os parâmetros da busca binária

Como o proxy está localizado no endereço do entry point original, `estimateCallGas` é capaz de chamar a função execute da account, determinar se a chamada teve sucesso ou ficou sem gas, e executar o algoritmo de busca binária até o sucesso.

Voilà! A busca binária agora pode ocorrer em uma `eth\_call` para obter uma estimativa precisa de `callGasLimit`!

Na prática, o Rundler divide esse único `eth\_call` em múltiplas chamadas, cada uma com um limite máximo de gas global, para contornar os limites máximos de gas de chamada dos nós. Cada chamada executará iterações de busca binária até que a próxima iteração faça a chamada ficar sem gas.

<CalloutBlock>

💡 Esse método deixa de fora uma pequena quantidade de overhead imposto pelo entry point durante `innerHandleOp`. Esse overhead é estático e é somado ao resultado da estimativa.

</CalloutBlock>

## Possível mudança no entry point

Temos pensado em possíveis mudanças no entry point que poderiam tornar o processo de estimativa de gas descrito acima mais simples de implementar e mais performático para os usuários. Esse não é um problema fácil de resolver, então adoraríamos ouvir outros bundlers ou membros da comunidade ERC-4337 se você tiver ideias!

Queremos garantir que a fase de validação seja executada antes da fase de execução durante a estimativa de gas. A fase de execução pode depender de estado definido durante a fase de validação \(ou seja, uma transferência de USDC por um paymaster e uma fase de execução que usa USDC\) e, portanto, é mais preciso estimar logo em seguida.

Uma possível melhoria seria fazer com que o entry point retornasse um booleano representando se a parte de execução reverteu ou não durante `simulateHandleOp`. O bundler poderia então executar a busca binária offchain sem o contrato proxy e os overrides de estado. No entanto, essa busca binária seria ineficiente, pois executaria a fase de validação em cada iteração.

Isso nos leva a pegar a busca binária feita pelo contrato proxy na tentativa 3 acima e adicionar sua funcionalidade diretamente ao contrato entry point \(pseudocódigo\):

Nossa implementação do acima \(como um contrato proxy\) será open source em breve. Achamos que pode ser benéfico para os usuários de ERC-4337 ter a estimativa via busca binária como parte do próprio contrato entry point.

Fique de olho na nossa implementação open source de bundler, que vem por aí! 🦀

## Continue lendo

Os próximos artigos nesta série de 4 partes exploram os desafios de estimativa de gas que encontramos e as soluções que nossa equipe de engenharia implementou.

- [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)
