# Gas Repricing and Gas Limit Increase Documentation
This document outlines our methodology and findings regarding gas repricing and gas limit testing for Ethereum precompiled contracts across various client implementations.
## Test Environment
We conducted all tests on a remote cloud-based Linux server running **Ubuntu 24.10**, selected to comply with [EIP-7870](https://eips.ethereum.org/EIPS/eip-7870). The machine specifications were as follows:
- **CPU**: Intel, 16 Cores / 16 Threads @ 2.7 GHz
- **RAM**: 32 GB
**PassMark Benchmark Results:**
- **Multi-threaded (MT)**: 16,008 < 25,000 (threshold)
- **Single-threaded (ST)**: 2,323 < 3,500 (threshold)
> **Note:**
> MT = Multi-threaded score
> ST = Single-threaded score
This machine was dedicated exclusively to running and benchmarking state tests using different EVM implementations.
## Methodology
To guide the repricing of elliptic curve (EC) precompiled operations, we used BLS performance as a reference target. Our main goal was to align EC operations with BLS execution time, which consistently stayed around **700 milliseconds**, even with 100 million gas (MGas) limits.
### Test Case Generation
- We leveraged [FuzzyVM](https://github.com/MariusVanDerWijden/FuzzyVM/) to generate a range of state tests through fuzzing.
- The slowest generated tests were retained for benchmarking.
- Performance metrics were gathered to identify worst-case execution scenarios.
Our target is to align the gas pricing of `EcAdd`, `EcMul`, and `EcPairing` so their worst-case runtimes approximate **700ms**, in line with BLS operations.
## Focused Clients
Although our main focus is the `go-evm` implementation in **Geth**, we included additional benchmarking across:
- **Nethermind**
- **Reth**
- **Besu**
This allows us to compare performance across different EVM implementations.
## EC Pairing Gas Formula
The cost of the `EcPairing` precompile is calculated using the formula:
```
TotalGas = 45,000 + (34,000 × K)
```
Where:
- `K` = Number of pairing operations
- `45,000` = Base fee
- `34,000` = Cost per pairing
### Test Cases Evaluated
We designed three benchmark cases:
- **2 pairings**
- **10 pairings**
- **Maximum pairings**
### Estimating Maximum Pairings
To calculate the maximum number of pairings under a 100M gas limit:
```
K = (100,000,000 - 45,000) / 34,000 ≈ 2,939
```
Thus, in theory, **2,939 pairings** can fit within a 100M gas transaction. However, in practice, other opcodes such as `PUSH`, `MSTORE`, and `STATICCALL` also consume gas. Accounting for these, the true executable maximum is approximately **2,820 pairings**.
To validate this, we generated state tests with **2,900 pairings**. The execution time was surprisingly short (<1 second). Upon tracing (`--json`), we observed that no actual `STATICCALL` was being triggered — the EVM was only executing setup opcodes (`PUSH`, `MSTORE`), indicating the operation wasn’t fully run.
We reduced the pairing count incrementally until the trace included a full cycle of:
> `PUSH >> JUMP >> JUMPDEST >> PUSH1 >> PUSH1 >> PUSH1 >> PUSH1 >> PUSH1 >> GAS >> STATICCALL`
This confirmed that the state test was valid and actually exercising the pairing logic.
## Benchmark Results
Execution times (in **seconds**) for each precompiled operation across clients were averaged over 5 runs:
> **Note:**
> - Clients like **Besu** and **Nethermind** exhibit a consistent startup/loading overhead (~3.6s), even for empty tests.
> - For these clients, values in parentheses represent adjusted timings **excluding** this loading time.
> - Asterisks (*) indicate values affected by loading delay.
> - Double asterisks (**) suggest likely caching; pending confirmation with client teams.
| Precompile | Geth (evm) | Nethermind (nethtest) | Reth (revme) | Besu (evm) |
|--------------------|------------|------------------------|--------------|---------------------|
| **EcMul** | 1.531 | 3.981* (**0.544**)** | 0.332** | 4.539* |
| **EcAdd** | 4.743 | 6.237* (**2.764**) | 2.249 | 7.215* |
| **EcPairing** | 2.678 | 3.499* (**0.081**)** | 1.340 | 4.453* |
| **2_EcPairing** | 2.510 | 3.502* (**0.067**)** | 1.324 | 4.469* |
| **10_EcPairing** | 2.640 | 3.528* (**0.073**)** | 1.360 | 4.516* |
| **2820_EcPairing** | 2.666 | 5.702* (**2.234**) | 1.344 | 4.513* |
| **BLS_AddG1** | 0.763 | 4.331* (**0.897**) | 0.205** | 5.251* (**1.557**) |
| **BLS_MulG1** | 0.769 | 4.325* (**0.901**) | 0.206** | 5.274* (**1.566**) |
## Repricing Strategy
As noted in the [Methodology](https://notes.ethereum.org/@mushow/H1EwxZvXxg#Methodology), we aim to calibrate EC precompiles to consistently execute in ~700ms.
To reprice, we adjusted the gas values in the [`protocol_params.go`](https://github.com/ethereum/go-ethereum/blob/master/params/protocol_params.go#L149) file in the Geth codebase. After modification, recompile using:
```bash
make all
```
This rebuilds both Geth and its tooling. The updated `evm` binary will be located at:
```
build/bin/evm
```
### Gas Price Comparison
| Precompiled | EcAdd | EcMul | EcPairing Formula |
|--------------------|------:|------:|--------------------------------|
| **Original** | 150 | 6,000 | `34,000 * k + 45,000` |
| **Adjusted** | 1,800 | 13,000 | `125,000 * k + 80,000` |
```diff
- Bn256AddGasIstanbul uint64 = 150 // Gas needed for an elliptic curve addition
+ Bn256AddGasIstanbul uint64 = 1800 // Gas needed for an elliptic curve addition
Bn256ScalarMulGasByzantium uint64 = 40000 // Byzantium gas needed for an elliptic curve scalar multiplication
- Bn256ScalarMulGasIstanbul uint64 = 6000 // Gas needed for an elliptic curve scalar multiplication
+ Bn256ScalarMulGasIstanbul uint64 = 13000 // Gas needed for an elliptic curve scalar multiplication
Bn256PairingBaseGasByzantium uint64 = 100000 // Byzantium base price for an elliptic curve pairing check
- Bn256PairingBaseGasIstanbul uint64 = 45000 // Base price for an elliptic curve pairing check
+ Bn256PairingBaseGasIstanbul uint64 = 80000 // Base price for an elliptic curve pairing check
Bn256PairingPerPointGasByzantium uint64 = 80000 // Byzantium per-point price for an elliptic curve pairing check
- Bn256PairingPerPointGasIstanbul uint64 = 34000 // Per-point price for an elliptic curve pairing check
+ Bn256PairingPerPointGasIstanbul uint64 = 125000 // Per-point price for an elliptic curve pairing check
```
Initially, we assumed that to reduce execution from, say, 3s to 0.7s, we could scale gas linearly:
```
3s / 0.7s ≈ 4.29 × original gas
```
However, benchmarking revealed that **execution time does not scale linearly** with gas. This insight suggests a need for empirical calibration.
### Suggestion
It would be valuable to create a graph plotting:
- **Gas multiplier → Execution time**
- Under different **gas limits** (e.g., 100M, 300M)
Such a visualisation would help refine repricing.
## Mock Visual (Illustrative Only)
> _Not based on actual benchmark data_
