Gas Optimization
Overview
Remediation Guide: Gas Optimization Remediation
The gas optimization detector reports one specific, high-value inefficiency: a function that loads the same storage slot more than once. Each SLOAD after the first costs 100 gas warm — and the first in a transaction costs 2,100 cold — so a loop that re-reads a storage variable on every iteration pays that cost over and over for a value that has not changed. Caching the slot in a local variable pays the read once.
Findings are informational: they never indicate a vulnerability, only avoidable cost. The detector is deliberately narrow — repeated SLOAD of one slot is the pattern that is both cheap to detect from bytecode and consistently worth fixing.
Why This Is an Issue
Storage reads are the dominant recurring cost in most hot paths. The canonical offender is reading array.length or a config variable inside a loop condition:
- 10 iterations re-reading one slot: ~1,000 gas wasted (warm reads).
- The same loop over a cold slot in a busy function called thousands of times a day compounds into real money for users.
Because the value does not change between reads (no intervening write), the fix is semantically free.
How to Resolve
Cache the storage value in a local variable and use the copy:
// Before: rate is SLOADed on every iteration
uint256 public rate;
function total(uint256[] calldata amounts) external view returns (uint256 sum) {
for (uint256 i = 0; i < amounts.length; i++) {
sum += amounts[i] * rate; // one SLOAD per iteration
}
}
// After: one SLOAD, then memory reads
function total(uint256[] calldata amounts) external view returns (uint256 sum) {
uint256 cachedRate = rate; // single SLOAD
for (uint256 i = 0; i < amounts.length; i++) {
sum += amounts[i] * cachedRate;
}
}
Do not cache across external calls if the callee can change the value — re-read after any call that may write the slot.
Examples
Sample Sigvex Output
{
"detector_id": "gas-optimization",
"severity": "info",
"confidence": 0.45,
"description": "Multiple SLOAD from same slot in process(): storage slot '0x03' is loaded multiple times. Cache in memory to save gas.",
"location": { "function": "process()", "offset": 82 }
}
Detection Methodology
The detector walks each function’s basic blocks in the decompiled IR, counting SLOAD instructions per storage key. The second and subsequent loads of a key within one function produce a finding at that instruction. Confidence starts at 0.45 — repeated loads can be intentional when they sit on mutually exclusive branches — and is discounted further (to roughly 0.14) for recognized audited library code (OpenZeppelin, Solmate, Solady), whose layouts are deliberate. Proxy and implementation contracts are annotated with the detected mitigation context.
Limitations
- The per-function count does not model control flow: two loads on mutually exclusive branches execute at most once each at runtime, yet are still counted. This is the main false-positive source and the reason for the conservative confidence.
- Loads separated by an intervening write to the same slot are legitimate re-reads; the current pattern does not track interleaved
SSTOREs and may flag them. - Other optimization classes — storage packing, calldata-vs-memory parameters, loop-invariant hoisting — are outside this detector’s scope.
Related Detectors
- Loop Gas Exhaustion — loops whose gas grows without bound
- Bytecode Size Limit — the EIP-170 deployment ceiling
- Dead Code — unreachable code inflating deploy cost