moonbeam icon indicating copy to clipboard operation
moonbeam copied to clipboard

Moonbeam RPC Significant Gas Underestimation in UniswapInterfaceMulticall Static Calls

Open mshakeg opened this issue 4 months ago • 2 comments

Problem Description

When using UniswapInterfaceMulticall.callStatic.multicall() for gas estimation on Moonbeam mainnet, RPC nodes consistently underestimate the actual gas requirements, causing transactions to fail with out-of-gas errors.

Technical Details

Chain Affected: Moonbeam Mainnet (Chain ID: 1284)

Root Cause:

  • callStatic.multicall() returns gasUsed values that are lower than actual execution requirements
  • The internal gas accounting in UniswapInterfaceMulticall appears to be simulated incorrectly by Moonbeam RPC nodes
  • Other EVM chains simulate this correctly

Reproduction Steps

Method 1: Direct Contract Interaction

  1. Deploy or use existing UniswapInterfaceMulticall contract on Moonbeam
  2. Create multiple (permissionless/open) calls that consume significant gas (e.g., complex DeFi operations, state changes)
  3. Use callStatic.multicall(calls) to get gas estimates
  4. Execute actual multicall(calls) transaction using the estimated gas
  5. Observe transaction failure due to insufficient gas

Method 2: Synthetic Test Case

// Deploy a gas-consuming test contract
contract GasConsumer {
    mapping(uint256 => uint256) public data;

    function consumeGas(uint256 iterations) external {
        for(uint256 i = 0; i < iterations; i++) {
            data[i] = i * 2;
        }
    }
}

Then test with multicall:

// assuming the actual gas limit is guaranteed to be < 500000 we allocate at most that for each call
const calls = [
  { target: gasConsumer.address, gasLimit: 500000, callData: gasConsumer.interface.encodeFunctionData('consumeGas', [100]) },
  { target: gasConsumer.address, gasLimit: 500000, callData: gasConsumer.interface.encodeFunctionData('consumeGas', [150]) }
];

// This will underestimate
const staticResult = await multicall.callStatic.multicall(calls);
console.log('Estimated gas:', staticResult.returnData.map(r => r.gasUsed));

// This may fail
const tx = await multicall.multicall(calls);

Error Pattern

Transaction Status: 0 (reverted) Gas Used: [Full gas limit] (indicating out-of-gas) Error: CALL_EXCEPTION

Impact

  • Applications relying on accurate gas estimation face transaction failures
  • Gas fees are wasted on failed transactions
  • Automated systems (keepers, bots) cannot operate reliably

Current Workarounds

  1. Disable gas estimation and use static gas limits
  2. Apply significant gas buffers (50%+ markup)
  3. Use alternative gas estimation methods

Chain Comparison

  • Working chains: Ethereum, Polygon, Arbitrum, Base, and many other EVM chains
  • Problematic: Moonbeam mainnet specifically
  • Note: This issue is unique to Moonbeam's RPC gas simulation, the issue is on a few Moonbeam RPCs such as https://1rpc.io/glmr and https://moonbeam.unitedbloc.com

UniswapInterfaceMulticall Contract

The issue occurs in the internal gas accounting:

uint256 gasLeftBefore = gasleft();
(bool success, bytes memory ret) = target.call{gas: gasLimit}(callData);
uint256 gasUsed = gasLeftBefore - gasleft();

mshakeg avatar Aug 19 '25 05:08 mshakeg

Can you try using eth_estimateGas RPC?

Moonbeam has 3 gas factors, the standard gasmeter, the PoV gas and Storage growth gas. This is most likely happens only when PoV gas or Storage growth gas are the winning factors. We will try to have a look into this, and possibly improve it for future releases.

For now, you should use eth_estimateGas RPC, it is the standard way for estimating gas.

RomarQ avatar Aug 19 '25 08:08 RomarQ

@RomarQ eth_estimateGas would only return the gas estimation for the entire tx, I would like to get an estimate for each call within the multicall() which requires eth_call(as this returns the multicall result).

mshakeg avatar Aug 20 '25 04:08 mshakeg