ethereum/gas-optimization

Solidity Gas Optimization Guide

ethereumguide๐Ÿ‘ฅ Communityconfidence highhealth 0%
v1.0.0ยทUpdated 3/20/2026

Storage Optimization (Maximum Gains)

Variable Packing

// โŒ Bad: 3 slots (each slot is 32 bytes)
uint256 a;  // slot 0
uint128 b;  // slot 1 (wastes 128 bits)
uint128 c;  // slot 2 (wastes 128 bits)

// โœ… Good: 2 slots
uint256 a;  // slot 0
uint128 b;  // first half of slot 1
uint128 c;  // second half of slot 1 (automatically packed)

Rule: Adjacent small-type variables are packed into the same slot in declaration order.

Use bytes32 Instead of string (Fixed Length)

// โŒ string stores dynamic length, at least 1 slot + content
string public name;

// โœ… bytes32 is fixed at 1 slot, suitable for short strings
bytes32 public name;

Reduce SSTORE/SLOAD (Most Expensive Operations)

OperationGas
SSTORE (cold storage, first write)20,000
SSTORE (warm storage, already written)2,900
SLOAD (cold read)2,100
SLOAD (warm read)100
// โŒ Reading the same storage variable multiple times
function bad() external {
  for (uint i = 0; i < arr.length; i++) {  // arr.length reads from storage each time
    total += arr[i];
  }
}

// โœ… Cache to memory
function good() external {
  uint len = arr.length;  // read once and store in memory
  for (uint i = 0; i < len; i++) {
    total += arr[i];
  }
}

Calldata vs Memory

// โŒ memory copies data (unnecessary memory operations)
function process(uint[] memory data) external { ... }

// โœ… calldata reads directly without copying (use calldata for read-only external function parameters)
function process(uint[] calldata data) external { ... }

unchecked Arithmetic (Solidity 0.8+)

// โŒ checked arithmetic (adds overflow check to each operation, +20 gas)
for (uint i = 0; i < 100; i++) { ... }

// โœ… Use unchecked when overflow is guaranteed not to occur
for (uint i = 0; i < 100;) {
  // ... loop body
  unchecked { i++; }  // saves ~30 gas per iteration
}

Custom Errors vs require string

// โŒ require string is more expensive for both deployment and calls
require(amount > 0, "Amount must be positive");

// โœ… Custom errors (Solidity 0.8.4+) save gas
error InvalidAmount();
if (amount == 0) revert InvalidAmount();

Events Instead of Storage (When Only Historical Records Are Needed)

// โŒ Storing all historical records (extremely expensive)
Transfer[] public history;

// โœ… emit events (375 gas vs 20000 gas)
event Transfer(address indexed from, address indexed to, uint256 amount);
emit Transfer(from, to, amount);

Function Visibility Optimization

// public generates two entry points (internal + external), external saves gas
// โœ… Use external when only called externally
function transfer(address to, uint256 amount) external { ... }

Constants and Immutables

// โœ… constant: replaced at compile time, 0 storage gas
uint256 public constant MAX_SUPPLY = 10000;

// โœ… immutable: written to bytecode at deployment, reading costs only 3 gas
address public immutable owner;
constructor() { owner = msg.sender; }

Bitmap Instead of bool Array

// โŒ bool[] each bool occupies 1 slot (20000 gas each)
mapping(uint => bool) public claimed;

// โœ… bitmap: each uint256 stores 256 bools
mapping(uint => uint256) private _claimedBitmap;
function isClaimed(uint tokenId) public view returns (bool) {
  return _claimedBitmap[tokenId / 256] & (1 << (tokenId % 256)) != 0;
}

Gas Analysis Tools

  • forge snapshot โ€” Foundry gas snapshot comparison
  • hardhat-gas-reporter โ€” Hardhat test gas reports
  • Tenderly Debugger โ€” Per-operation gas analysis
  • ETH Gas Station โ€” Real-time gas price reference