Skip to content

Conversation

Oxbobby
Copy link
Member

@Oxbobby Oxbobby commented Aug 21, 2025

Change log

  • Modify Estimation.sol so that it first calculates a gasLimit and then tries to simulate the calls with the set limit. Upon failure, it makes the gasLimit 3 times bigger and tries again
  • Remove outdated accountOpExecuteBefore logic

Problem

Estimation.sol correctly estimates the gas cost of a transaction but when firing a request to Estimation.sol, the request has an inifinite gasLimit set. This of course is normal - the goal for Estimation.sol to return the correct gasLimit of the transaction and it wouldn't make sense for it to revert because of insufficient gas.

This is working brilliantly in 99% of cases unless one - where an UI artificially requests more gas than actually needed.

Let's take the sample contract below as an example:

contract GasGuard {
  uint256 lastGas;

  error InsufficientGas(uint256 gasLeft, uint256 required);

  function guardedCall(uint256 minGasRequired) external returns (uint256) {
    uint256 g = gasleft();
    lastGas = g;

    if (g < minGasRequired) {
      revert InsufficientGas(g, minGasRequired);
    }

    return g;
  }
}

We see that guardedCall has a minGasRequired parameter. So a dapp can take advantage of this and send this call to the wallet:

{
  to: '0x70D8fDf010b0be407273DAB41614574E9f22A3Ae',
  value: 0n,
  data: gasGuardInterface.encodeFunctionData('guardedCall', [60000])
}

This means the dapp is requesting a minimum of 60k gas for the execution of its contract call. When we're doing an estimation with Estimation.sol, we've set the gas parameter to infinite so the above passes without problems. However, after estimation has concluded, Estimation.sol will return the calculated gasLimit for running the above code. Guess what? It's below 60k as the execution cost of the above method doesn't actually require 60k regardless that the dapp requests it.

Another interesting part of this problem is the fact that estimateGas won't actually return below 60k because estimateGas works on a binary search principle. This means that estimateGas does estimates by starting at a higher set gasLimit and reduces it in half after each successful estimation until it hits a failure. So the moment it goes below 60k, estimateGas will revert and it will return the last successful estimation before going below 60k, making it a winning bet.

Solution

We're changing how Estimation.sol works to incorporate the above problem and make it work, to a reasonable extent, with "dapp-requested" gasLimit. Here's how:

  • We start off with a normal simulation with infinite gas as until now. However, we revert at the end and send the calculated gas back
  • We make a second simulation by using the calculated gas limit
  • If that simulation succeeds, we return the result to the UI
  • If it doesn't, we once again revert it, increase the calculated gas limit 3 times, and make a final, third simulation
  • Regardless whether the final simulation succeeds or not, we return the result
    The above algorithm allows us to make successful estimations on "dapp-requested" gas limits that are up to 3 times larger than what would originally cost. Any more than 3 times at this point in time we consider unreasonable and will not allow the execution of, returning an estimation error to the user.

Here are real world examples of such transactions:

We see clearly that 325k is more than enough to execute the first transaction that had an unreasonably large amount of 850k set. However, the dapp has included a minimum gas limit in its contract logic, forcing the 325k transaction to fail

There's a reason for such minimum gas limits. In the above particular case, here it is:

        // Trigger the call to the target contract. We use a custom low level method
        // SafeCall.callWithMinGas to ensure two key properties
        //   1. Target contracts cannot force this call to run out of gas by returning a very large
        //      amount of data (and this is OK because we don't care about the returndata here).
        //   2. The amount of gas provided to the execution context of the target is at least the
        //      gas limit specified by the user. If there is not enough gas in the current context
        //      to accomplish this, `callWithMinGas` will revert.

For the curious: here's the full contract, search for callWithMinGas

… tries to simulate the calls with the set limit; upon failure, it makes the gasLimit 3 times bigger and tries again; finally, remove outdated accountOpExecuteBefore logic
@Oxbobby Oxbobby requested a review from Ivshti August 21, 2025 01:41
@Oxbobby Oxbobby self-assigned this Aug 21, 2025
@Oxbobby Oxbobby added bug Something isn't working enhancement New feature or request labels Aug 21, 2025
…y when an internal asks for a bigger gas limit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant