Gas Optimization Race

The same contract, deployed twice on Base Sepolia. One written the way most people write it; one with four optimizations applied. Their function ABI is identical and compiler-enforced, and a single test suite runs against both to prove they behave the same. Race an operation against both in one transaction and read the two gas figures straight out of the receipt.

BaseBase Sepolia · testnet only

Everything below renders without a wallet. Connect one only to run a race — it costs testnet gas and signs a single transaction.

What is being compared

Both contracts implement the same interface, and neither declares an external function outside it, so a drift in any signature fails the build rather than quietly producing two different programs. The runner calls both in one transaction, so they face the same block, the same base fee and the same caller.

The runner's two targets are constructor-set immutables — no setter, no owner, no upgrade path — so the addresses above cannot be repointed after you have read a result. Source, tests and recorded measurements: github.com/pigfox/gas-optimization.

The four operations

Two of these four barely save anything, and they are listed anyway. A gas demo where every technique wins is a curated one. The figures below are execution gas from a previously recorded run — see the note under the table — and each links to the transaction it came from.

OperationTechniqueNaiveOptimizedSaving
writeRecord Packed struct
Four separate mappings become one struct whose fields sum to 232 bits, so a record occupies one storage slot instead of four.
92195 25976 −71.8%
−66219 gas
writeBatch Calldata instead of memory
The array parameter stays in calldata rather than being copied into memory. Real, but the eight cold storage writes in the same call dwarf it.
207006 206640 −0.1%
−366 gas
validate Custom error instead of a revert string
Raced on the REVERTING path, which is the only place this saves anything — a revert string costs nothing until it reverts.
3390 3324 −1.9%
−66 gas
setFlagRange Bitmap instead of a bool mapping
A mapping spends a whole 256-bit slot per flag; packing them as bits puts 256 flags in one slot, so setting eight touches one slot instead of eight.
181783 26121 −85.6%
−155662 gas

Every figure on this page is execution gas — what the contract code costs, measured as a gasleft() delta inside the runner. It excludes the 21,000 gas base transaction cost and the calldata cost that your wallet also pays, and it includes about 2,600 gas of cold-account access which both contracts pay identically, so it cancels in the difference. A standalone transaction to either contract costs roughly 19,000 gas more than the numbers here. Neither measurement is the “true” one: the standalone figure is what a wallet is charged, execution gas is what the code costs.

The counterpoint: deployment

Optimizing is not free. Packing logic and the bitmap word loop are code, and the optimized contract is the more expensive one to deploy — 399594 gas against 362886, or 10.1% more. You pay once at deployment to save on every call afterwards, which is the right trade for a contract that gets used and the wrong one for a contract that does not.

Run a race

Not connected.

Each race uses a fresh random run id, so both contracts start from cold storage. That is what makes the comparison fair.

How the measurement stays honest

  • A fresh run id every race. Every function keys all of its storage under a 32-byte id generated in your browser. A cold storage write costs 20,000 gas and a warm one 2,900 — reusing an id would measure a warm write against a cold one and report the difference as an optimization.
  • One transaction, both contracts. Same block, same base fee, same caller. Two separate transactions could differ in any of those.
  • Read from the receipt, never recomputed. The figures come out of the event log your transaction emitted. If the log cannot be read, this page shows an error rather than a number.
  • Both contracts pay the same overheads. Each is called exactly once per race, so the cold-account charge is identical on both sides and cancels in the difference.