Audits Beyond Code (2026): Practical Gas Optimization That Still Matters
What modern tactics can we use to optimize our smart contracts?
Security audits usually focus on “can this be hacked?”. But mature audits also ask: will this be too expensive to use? A contract can be perfectly secure and still fail as a product because it’s unaffordable or fragile under load.
Below is a refreshed, 2026-era list of gas optimization tips that are actually worth your time. (Some older “classic tips” have become obsolete due to protocol changes — those are called out explicitly.)
If you’re new to the fee model, Ethereum’s official docs explain gas as the unit pricing computation and storage (ethereum.org gas docs).
1) Delete dead code and unused dependencies
Obvious, but still the #1 real-world win:
- remove unused functions
- remove unused libraries
- avoid importing an entire library if you use one tiny helper
Every byte of deployed code costs money, and larger contracts are harder to reason about.
2) Stop importing SafeMath in Solidity 0.8+
Since Solidity 0.8, arithmetic overflows revert by default. That makes SafeMath generally unnecessary (Solidity 0.8 breaking changes and the Solidity team’s own preview post saying you “will not need SafeMath anymore” in 0.8+).
Use unchecked { ... } for places where overflow is impossible and you want cheaper math (common in tight loops).
3) Prefer custom errors over revert strings
Revert strings are expensive because the string data is baked into bytecode and returned at runtime. Custom errors are cheaper in both deployment and runtime when reverting (Solidity team: Custom Errors).
Good pattern:
- use custom errors for “expected” failures (auth, bounds, invalid input)
- reserve
assert/panic-style failures for “this should never happen”
4) Storage is expensive. Storage access is now context-dependent.
The old “SLOAD is 200 gas” era is over.
Since Berlin, Ethereum distinguishes cold vs warm access. The first access to a storage slot in a transaction is much more expensive than repeated reads in the same transaction (EIP-2929).
Practical implications:
- cache frequently used storage values in memory once per function
- avoid repeatedly reading the same storage keys across internal calls
- be aware that cross-contract reads can also be “cold”
If you want a mental model: “first time you touch a storage slot is a cold cache miss”.
5) Use calldata for external function parameters
For external functions, prefer:
calldatafor read-only arrays/bytes/stringsmemoryonly when you need to modify/copy data
This reduces unnecessary copying and can meaningfully cut costs in hot paths.
(Solidity docs cover data locations and their implications throughout the language reference; start with the compiler + language docs: https://docs.soliditylang.org/)
6) Avoid unbounded loops (or you’re building a DoS)
Unbounded loops aren’t just “more gas” — they’re a failure mode:
- you can run out of gas
- the whole call reverts
- an attacker can make a function unusable by bloating the iterable set
If you must process many items:
- paginate (
start,count) - process in batches
- move iteration off-chain and submit proofs/results on-chain
7) Pack storage where it actually saves gas
Rule of thumb still holds:
- single variables:
uint256often makes sense - inside structs: smaller types can pack into one 32-byte slot and reduce storage writes
Packing is real, but the best time to do it is at design time. Retrofitting it later is painful because it changes storage layout.
8) Short-circuit logic: put the cheap checks first
Most languages (and Solidity) short-circuit && and ||. So:
- do cheap checks first
- do expensive checks (like external calls) last
- reject early
This remains a simple but reliable micro-win.
9) Turn on the optimizer (and understand runs)
Solidity’s optimizer can reduce both code size and execution cost (Solidity Optimizer docs). By default, the compiler optimizes assuming ~200 executions across the contract’s lifetime (Solidity “Using the Compiler” docs).
--optimize-runs is a tradeoff knob:
- low runs → cheaper deployment, slightly more expensive execution
- high runs → more expensive deployment, cheaper execution
10) Consider viaIR (but measure)
Solidity has a modern pipeline using Yul IR. It can enable optimizations and sometimes avoids “stack too deep”. The Solidity team has written about it directly (“A closer look at via-IR”).
But: it can change codegen enough that you must benchmark gas and re-run tests. Treat it like a compiler switch, not magic dust.
11) Gas refunds and “gas tokens”: this is mostly dead now
Old-school gas tricks relied on gas refunds:
- deploy contracts when gas is cheap
- delete them later to get refunds
This has been largely neutralized:
- EIP-3529 removed the
SELFDESTRUCTrefund and reducedSSTORErefunds (EIP-3529) - EIP-6780 changed
SELFDESTRUCTsemantics so it no longer “deletes contracts” in the old way except in the same transaction as creation (see a concise explanation in the Consensys Dencun writeup)
So: don’t build new systems around gas token mechanics. They’re not a 2026 strategy.
12) Opcode-level thinking: use it only when it pays
If you drop into Yul/assembly, you should understand what opcodes cost. A good reference is evm.codes.
But here’s the auditor reality: assembly is a double-edged sword.
- yes, it can shave gas
- it also increases audit complexity and bug risk
- it can break assumptions across upgrades if you rely on tricky behavior
Use it only for hot paths where you can prove the savings matter.
13) Measure, don’t guess
Use tooling that gives you hard numbers:
- Foundry can produce gas snapshots (forge snapshot docs)
- It can also capture per-section gas in tests (Foundry gas section snapshots)
If you can’t measure a change, you’re not optimizing.
Final note: optimize the right layer
Many "gas optimizations" are actually architecture decisions:
- fewer storage writes beats any micro-optimization
- better data layout beats clever assembly
- doing work off-chain beats doing work on-chain
Got more tips to share? Let us know!