Gas Optimization

Gas optimization is the discipline of minimizing the Ethereum gas consumption of smart contracts and transactions through deliberate code structure choices, storage management techniques, assembly-level optimizations, architectural patterns, and transaction timing — with the goals of reducing per-transaction fees for end users, lowering smart contract deployment costs for developers, enabling more complex logic within block gas limits, and improving protocol competitiveness in a market where high gas costs routinely deter user activity. Every EVM (Ethereum Virtual Machine) opcode has a defined gas cost, and gas optimization involves selecting lower-cost opcodes, minimizing expensive storage operations, batching transactions, and exploiting calldata pricing differences to achieve the same functional result at lower total gas consumption.


EVM Gas Cost Fundamentals

How Gas Is Calculated

“`

Total Fee = (Base Fee + Priority Fee) × Gas Used

“`

  • Base fee: Network-determined, burned (EIP-1559)
  • Priority fee: User-set tip to validator
  • Gas used: Sum of gas costs of all opcodes executed

Most Expensive EVM Operations

Operation Gas Cost Notes
SSTORE (new slot) 20,000 Writing new data to storage — most expensive common op
SSTORE (update) 2,900 Updating existing non-zero storage
SLOAD 2,100 Reading from storage (EIP-2929 cold access)
CREATE / CREATE2 32,000+ Deploying a new contract
CALL (external) 700+ Calling another contract
LOG (events) 375 + 8/byte Emitting events
MLOAD / MSTORE 3 Memory operations (cheap)
ADD, MUL, SUB 3–5 Arithmetic (cheap)
SHA3 (keccak256) 30 + 6/word Hashing

Storage Optimization (Biggest Wins)

Avoid Unnecessary SSTOREs

“`solidity

// Bad: 2 separate SSTOREs = 40,000 gas

function update(uint256 a, uint256 b) external {

storedA = a; // 20,000 gas

storedB = b; // 20,000 gas

}

// Better: Read both, compute in memory, write once if changed

“`

Struct Packing

“`solidity

// Bad: 3 separate storage slots (96,000 gas to initialize)

uint256 a; // 32 bytes

uint256 b; // 32 bytes

uint256 c; // 32 bytes

// Good: All fit in 1 storage slot (32,000 gas to initialize)

uint128 a; // 16 bytes

uint64 b; // 8 bytes

uint64 c; // 8 bytes = 32 bytes total = 1 slot

“`

Use uint256 Over Smaller Uints (Counterintuitively)

“`solidity

// In isolation (not in struct): uint256 is cheaper than uint8

uint256 counter; // no masking needed

uint8 counter; // EVM pads to 256-bit, adds masking cost

“`

Constants and Immutables

// Bad: State variable (costs SLOAD every read = 2,100 gas)

uint256 public fee = 100;

// Good: Constant (inlined at compile time, ~3 gas)

uint256 public constant FEE = 100;

// Good: Immutable (set once in constructor, ~3 gas reads)

uint256 public immutable fee;

constructor(uint256 _fee) { fee = _fee; }

“`


Computation Optimization

Short-Circuit Boolean Evaluation

“`solidity

// Put cheapest/most likely to fail check FIRST

require(amount > 0 && token.balanceOf(msg.sender) >= amount);

// If amount == 0, skips the SLOAD for balanceOf

“`

Cache Storage Reads in Memory

// Bad: 3 SLOADs at 2,100 gas each = 6,300 gas

function calculate() external view returns (uint256) {

return storedValue * storedValue + storedValue;

}

// Good: 1 SLOAD + 2 cheap MLOAD = ~2,106 gas

function calculate() external view returns (uint256) {

uint256 val = storedValue; // one SLOAD

return val * val + val;

}

“`

Unchecked Arithmetic (Solidity 0.8+)

“`solidity

// Standard (checked): ~40 gas overhead per operation

for (uint256 i = 0; i < length; i++) { … }

// Unchecked (safe when i < length guaranteed): saves ~30 gas/iteration

for (uint256 i = 0; i < length; ) {

// loop body

unchecked { ++i; } // ++i cheaper than i++ too

}

“`


Calldata and ABI Optimization

Calldata Cost

  • Non-zero bytes: 16 gas per byte

Minimizing calldata (especially non-zero bytes) reduces transaction cost:

“`solidity

// Bad: sends full 32-byte address encoded in calldata

function transfer(address to, uint256 amount) external { … }

// Optimization: encode addresses as shorter custom types when possible

// Use minimal-ABI selectors, avoid unnecessary padding

“`

Batch Operations

“`solidity

// Bad: 3 transactions × 21,000 base = 63,000 base gas

approve(token, amount);

deposit(amount);

stake(amount);

// Good: 1 transaction with all logic

depositAndStake(token, amount);

“`


Architecture-Level Optimizations

Events Over Storage for Historical Data

“`solidity

// Bad: Storing historical data on-chain = 20,000 gas/write

mapping(uint256 => uint256) public priceHistory;

// Good: Emit event = ~375 + data bytes gas, readable off-chain

event PriceUpdated(uint256 indexed timestamp, uint256 price);

“`

Proxy Patterns Reduce Deployment Cost

Gas Tokens (Historical)


Transaction Timing Optimization

Gas Price Timing

Period Typical Base Fee
Weekday 9am–5pm UTC High (peak activity)
Weekday evenings UTC Medium
Weekend late night UTC Lowest
During market crashes Spike (panic transactions)
During bull market NFT drops Extreme spike

Tools: GasNow (defunct), Etherscan Gas Tracker, EIP-1559 fee estimators


History

  • 2015–2020: Gas optimization as black art — small dev community shares Solidity tricks; few comprehensive resources
  • 2020: DeFi Summer drives gas prices to 500+ gwei; optimization becomes economically critical
  • 2021 Aug: EIP-1559 London fork — base fee + priority fee model; eliminates gas token strategies
  • 2021: Solidity 0.8.x adds automatic overflow checks — unchecked block introduced for opt-out
  • 2022: Layer 2 rollups (Optimism, Arbitrage) reduce L2 execution costs dramatically; L1 calldata cost becomes dominant L2 expense
  • 2024 Mar: EIP-4844 (Dencun upgrade) — blob transactions reduce L2 calldata cost by 10–100× on L1
  • 2024–2025: Gas optimization focus shifts from L1 execution to L2 proof costs and blob data efficiency

See Also