# 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_ ![Mock Graph](https://notes.ethereum.org/_uploads/BJKTmLO7ll.png)