O que é Gas e por que as taxas mudam no EVM

1. Visão geral

Se você já enviou uma transação na Ethereum e viu as taxas variar 10 vezes de uma hora para outra, você está tocando no conceito de gas. Este artigo explica o que é gas, como a EVM calcula as taxas antes e depois do EIP-1559, e por que o baseFee muda a cada bloco. Depois de ler, você conseguirá calcular a taxa de transação via código e saberá definir maxFeePerGas adequadamente para evitar que a transação fique presa ou você pague acima da conta.

2. O que é Gas

Gas é a unidade que mede o trabalho computacional que a EVM (Ethereum Virtual Machine) precisa realizar para executar uma transação. Cada opcode (instrução de máquina virtual) tem um custo de gas fixo — uma soma inteira (ADD) é barata, mas escrever dados no storage (SSTORE) custa centenas de vezes mais.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Counter {
    uint256 public value; // slot de armazenamento

    function increment() public {
        value = value + 1; // ADD (~3 gas) + SSTORE (~5000-20000 gas)
    }
}

A linha value = value + 1 na verdade é dois passos: cálculo aritmético (barato) e escrita no storage (caro). Por isso otimizações em smart contracts costumam focar em reduzir o número de leitura/escrita no storage, não em reduzir operações aritméticas.

Nota: Gas não é dinheiro. Gas é “quantidade de trabalho”. Taxa real = gas usado × preço por gás (em ETH).

3. Gas limit, gas price e gas used

Três conceitos frequentemente confundidos:

  • Gas limit: o máximo de gas que você permite a transação gastar. Se definir muito baixo → a transação sofre revert com erro out of gas.
  • Gas used: o gas realmente gasto, sempre ≤ gas limit.
  • Gas price: o preço por unidade de gas, medido em gwei (1 gwei = 10⁻⁹ ETH).
Taxa de transação (antes do EIP-1559) = gasUsed × gasPrice

Por exemplo, uma transferência simples de ETH custa 21.000 gas, se gasPrice = 30 gwei:

Taxa = 21.000 × 30 gwei = 630.000 gwei = 0.00063 ETH

4. Mecanismo antigo: legacy gas price

Antes do EIP-1559 (antes do fork Londres em 2021), o usuário definia gasPrice. Preço mais alto significava que miners priorizavam colocar a transação no bloco antes. Esse mecanismo era como um leilão às cegas: você não sabia quanto os concorrentes pagavam, era fácil pagar demais ou de menos e ter que esperar.

5. EIP-1559: base fee + priority fee

EIP-1559 divide a taxa em duas partes:

  • baseFee: calculada automaticamente pela rede baseada no preenchimento do bloco anterior, é queimada (não vai para o validador).
  • priorityFee (tip): você paga extra ao validador para ter prioridade.

O usuário define dois valores:

  • maxFeePerGas: o preço máximo por gás que você aceita pagar.
  • maxPriorityFeePerGas: o tip máximo que você aceita pagar.

5.1. Fórmula para calcular a taxa real

effectiveGasPrice = min(maxFeePerGas, baseFee + maxPriorityFeePerGas)
taxa real          = gasUsed × effectiveGasPrice

5.2. Calcular taxa em Java

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.26.3</version>
    <scope>test</scope>
</dependency>
package dev.nonla.evm;

import java.math.BigInteger;

public class FeeCalculator {

    public BigInteger effectiveGasPrice(
            BigInteger baseFee,
            BigInteger maxPriorityFeePerGas,
            BigInteger maxFeePerGas) {

        BigInteger desired = baseFee.add(maxPriorityFeePerGas);
        return desired.min(maxFeePerGas); // nunca vai exceder maxFeePerGas
    }

    public BigInteger totalFee(BigInteger gasUsed, BigInteger effectiveGasPrice) {
        return gasUsed.multiply(effectiveGasPrice);
    }
}
  • effectiveGasPrice pega o menor valor entre (baseFee + tip desejado) e o limite maxFeePerGas que você definiu.
  • totalFee multiplica pelo gas realmente usado para calcular o ETH a pagar.
package dev.nonla.evm;

import org.junit.jupiter.api.Test;
import java.math.BigInteger;

import static org.assertj.core.api.Assertions.assertThat;

class FeeCalculatorTest {

    private final FeeCalculator calculator = new FeeCalculator();

    @Test
    void quandoBaseFeeComTipMenorQueMax_entaoUsaTipCompleto() {
        BigInteger baseFee = BigInteger.valueOf(20); // gwei
        BigInteger tip = BigInteger.valueOf(2);
        BigInteger maxFee = BigInteger.valueOf(50);

        BigInteger price = calculator.effectiveGasPrice(baseFee, tip, maxFee);

        assertThat(price).isEqualTo(BigInteger.valueOf(22));
    }

    @Test
    void quandoBaseFeeExcedeMax_entaoFixaEmMaxFeePerGas() {
        BigInteger baseFee = BigInteger.valueOf(60); // sobe bastante
        BigInteger tip = BigInteger.valueOf(2);
        BigInteger maxFee = BigInteger.valueOf(50);

        BigInteger price = calculator.effectiveGasPrice(baseFee, tip, maxFee);

        assertThat(price).isEqualTo(maxFee); // fixado em 50, não paga além
    }
}

O segundo teste ilustra exatamente por que sua transação fica presa (pending) quando a rede está congestionada: o baseFee sobe acima de maxFeePerGas que você definiu, e o validador não tem incentivo para aceitar porque o tip real fica muito baixo.

6. Por que base fee muda a cada bloco

baseFee não é fixo — ajusta-se automaticamente conforme o preenchimento do bloco anterior, baseado na fórmula aproximada:

baseFee_próximo = baseFee_atual × (1 + 1/8 × (gasUsed - gasTarget) / gasTarget)

Onde gasTarget é 50% do gasLimit do bloco (cada bloco Ethereum tem um gas limit em torno de 30 milhões, com target em torno de 15 milhões).

  • Bloco mais cheio que o target → baseFee sobe no máximo 12,5% por bloco.
  • Bloco mais vazio que o target → baseFee cai no máximo 12,5% por bloco.
package dev.nonla.evm;

import java.math.BigInteger;

public class BaseFeeAdjuster {

    private static final BigInteger EIGHT = BigInteger.valueOf(8);

    public BigInteger nextBaseFee(BigInteger currentBaseFee, long gasUsed, long gasTarget) {
        long delta = gasUsed - gasTarget;
        BigInteger change = currentBaseFee
                .multiply(BigInteger.valueOf(delta))
                .divide(BigInteger.valueOf(gasTarget))
                .divide(EIGHT);

        BigInteger next = currentBaseFee.add(change);
        // baseFee nunca fica negativo
        return next.max(BigInteger.ONE);
    }
}
@Test
void quandoBlocoMaisCheioqueMetas_entaoBaseFeeAumenta() {
    BaseFeeAdjuster adjuster = new BaseFeeAdjuster();
    BigInteger current = BigInteger.valueOf(100); // gwei

    BigInteger next = adjuster.nextBaseFee(current, 30_000_000L, 15_000_000L);

    // gasUsed = 2x target -> sobe perto de 12.5%
    assertThat(next).isGreaterThan(current);
}

Esse mecanismo de auto-ajuste explica por que você vê baseFee pulando continuamente a cada ~12 segundos (o tempo de um bloco): a rede está balanceando automaticamente a oferta e demanda de espaço em bloco, não é o miner/validador definindo preço arbitrariamente.

7. Observar base fee e gas price via JSON-RPC

Você pode obter baseFeePerGas diretamente de um nó Ethereum:

curl -s -X POST https://rpc.ankr.com/eth \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false],"id":1}' \
  | jq '.result.baseFeePerGas'
"0x9502f9000"

O valor retornado está em hex (wei), você converte para gwei dividindo por 10⁹.

8. Estimar gas antes de enviar transação com Web3j

Em vez de adivinhar gasLimit, deixe o nó calcular antecipadamente com eth_estimateGas — evita definir muito baixo (revert) ou muito alto (bloqueia ETH em excesso temporariamente).

<dependency>
    <groupId>org.web3j</groupId>
    <artifactId>core</artifactId>
    <version>4.12.2</version>
</dependency>
package dev.nonla.evm;

import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
import org.web3j.protocol.core.methods.request.Transaction;
import java.math.BigInteger;

public class GasEstimator {

    private final Web3j web3j;

    public GasEstimator(Web3j web3j) {
        this.web3j = web3j;
    }

    public BigInteger estimate(String from, String to, String data) throws Exception {
        Transaction tx = Transaction.createFunctionCallTransaction(
                from, null, null, null, to, data);

        EthEstimateGas response = web3j.ethEstimateGas(tx).send();
        if (response.hasError()) {
            throw new IllegalStateException("Estimativa falhou: " + response.getError().getMessage());
        }
        return response.getAmountUsed();
    }
}
  • ethEstimateGas simula a transação no estado atual e retorna o gas necessário.
  • Na prática, você deve adicionar um buffer de 10–20% a esse resultado antes de definir gasLimit, porque o estado pode mudar entre o momento da estimativa e a execução real da transação.

9. Erros comuns

  • Definir gasLimit exatamente igual ao resultado de estimate: se o layout de storage mudar levemente (por exemplo, slot de 0 → non-zero custa gas diferente), a transação pode sofrer revert por out of gas. Sempre adicione um buffer.
  • Definir maxFeePerGas muito baixo quando a rede está congestionada: a transação fica no mempool indefinidamente, sem timeout automático — você precisa cancelá-la manualmente enviando uma transação com o mesmo nonce mas fee mais alta (replace-by-fee).
  • Confundir gasPrice (legacy) com maxFeePerGas (EIP-1559): enviar uma transação do tipo antigo em uma rede que já suporta 1559 ainda funciona, mas você perde o controle sobre o tip separado — a carteira pode pagar acima do baseFee real.
  • Não considerar priorityFee muito baixo: se a taxa de prioridade está perto de 0 enquanto o bloco está cheio, o validador não tem incentivo para incluir a transação logo mesmo que maxFeePerGas seja alto.

Nota: Não faça hard-code de um gasPrice fixo no código de produção. Sempre chame eth_feeHistory ou eth_gasPrice para obter valores de mercado no momento, depois aplique a fórmula da seção 5.

10. Conclusão

Gas é a unidade que mede o trabalho computacional na EVM; a taxa real = gas usado × preço por gás. Com EIP-1559, esse preço se divide em baseFee que se auto-ajusta conforme o preenchimento do bloco e priorityFee que você escolhe para prioridade. Entender bem essa fórmula ajuda você a definir gasLimit/maxFeePerGas adequadamente, evitando transações presas ou pagamento excessivo — e se necessário, você pode completamente escrever sua própria lógica de cálculo de taxa em Java como nos exemplos acima para integrar no backend de monitoramento de carteira ou painel de transações.

evmblockchainethereumgaseip-1559