Skip to content

Ethereum Gas Exactimation

In Ethereum, estimating gas for a given transaction is a tricky problem to solve (especially when attempting to maintain EIP-114 compliance). Most of the well-known Ethereum implementations like Geth¹ and Parity² use interval halving (binary search) to estimate gas by running transactions through the EVM until an approximate estimation converges.

Ganache Logo

At Truffle, we decided such an approach was an unnecessarily CPU-intensive solution to the problem and set out to find a (theoretically) more performant and perfectly accurate way of estimating gas. The result? Gas exactimation. In this tutorial, we’ll go over gas exactimation at a high level in order to demonstrate its precision.

Complete example available at the following repo.

Prerequisites: You should be familiar with Truffle, Ganache, and Solidity. If you need an introduction please consult the following resources:

EIP-114, or the “1/64ths rule”

EIP-114 mandates that certain stackdepth-creating opcodes withhold 1/64th of remaining gas from the stack they create. In practice this means:

  1. The gas required for a successful transaction can be greater than the actual gas spent (similar to how gas refunds behave).
  2. The extra gas required for a successful transaction varies depending on the transaction’s initial gas amount.

A long-standing issue with Ganache has been the fact that we haven’t returned EIP-114 compliant gas estimations. This has caused our gas estimates to be too low in cases where a transaction executed certain opcodes. Gas exactimation addresses this by considering how the gas withheld at any nested stack depth/frame affects the gas needed outside of its execution context.

Let’s see it in action.

Create a New Truffle Project

We will use truffle init to create a new Truffle project and then wire up an example Solidity smart contract and test script.

// In a new project directory...
$ truffle init

Setup an Example Smart Contract

With our new project initialized, we will create an example file: ContractFactory.sol.

$ touch ./contracts/ContractFactory.sol

Our file will have two contracts, Contract and ContractFactory. ContractFactory will have the method createInstance that we will use to create a new empty Contract.

// ./contracts/ContractFactory.sol
pragma solidity ^0.5.0;

contract ContractFactory {
  function createInstance() public {
    new Contract();
  }
}

contract Contract {
  constructor() public {}
}

Note the new keyword being used to create a new Contract. A valid statement containing the new keyword gets compiled to bytecode containing the CREATE opcode which is subject to the EIP-114 1/64th gas withholding.

Write a Test Case

Next, we will write a ContractFactory test case.

$ touch ./test/ContractFactory.js

This test case will deploy ContractFactory to a Ganache test network and use a gas estimate provided by Ganache to create a new Contract.

// ./test/ContractFactory.js
const ContractFactory = artifacts.require("ContractFactory");

contract("ContractFactory", () => {
  it("...should deploy and successfully call createInstance using the method's provided gas estimate", async () => {
    const contractFactoryInstance = await ContractFactory.new();

    const gasEstimate = await contractFactoryInstance.createInstance.estimateGas();

    const tx = await contractFactoryInstance.createInstance({
      gas: gasEstimate
    });
    assert(tx);
  });
});

A Quick Check

Before we run our test, we’ll download the most recent version of Truffle that uses Ganache before gas exactimation.

// In the project directory...
$ npm i [email protected]

And we’ll make sure we have the latest version of Truffle installed globally that uses Ganache with gas exactimation.

$ npm i -g truffle

Testing Before and After

$ npx truffle test
...
...
Error: Returned error: VM Exception while processing transaction: revert

And when we use gas exactimation...

$ truffle test
...
...
Contract: ContractFactory

✓ ...should deploy and successfully call createInstance using the method's provided gas estimate (130ms)

1 passing (143ms)

!!!!

Testing Exactimation

But is gas exactimation actually exact? 🤔

We’ll open our test file and subtract exactly a single unit of gas from the gasEstimate before sending our test transaction.

// ./test/ContractFactory.js
const ContractFactory = artifacts.require("ContractFactory");

contract("ContractFactory", () => {
  it("...should deploy and successfully call createInstance using the method's provided gas estimate", async () => {
    const contractFactoryInstance = await ContractFactory.new();

const gasEstimate = await contractFactoryInstance.createInstance.estimateGas();

const tx = await contractFactoryInstance.createInstance({
      gas: gasEstimate - 1
    });
    assert(tx);
  });
});

Running our test again against Ganache with gas exactimation...

$ truffle test
...
...
Error: Returned error: VM Exception while processing transaction: revert

Exactimation confirmed 🚀.

Note: Since the initial release of gas exactimation, an even more performant iteration of the algorithm is currently in review here.