Solidity Gas Optimization
Overview
Comprehensive gas optimization guide for Solidity smart contracts, containing 80+ techniques across 8 categories. Based on the RareSkills Book of Gas Optimization. Rules are prioritized by impact and safety.
When to Apply
Reference these guidelines when:
- •Writing new Solidity smart contracts
- •Reviewing or auditing existing contracts
- •Optimizing gas costs for deployment or execution
- •Refactoring contract storage layouts
- •Implementing cross-contract interactions
- •Choosing between design patterns (ERC721 vs ERC1155, etc.)
Priority-Ordered Categories
| Priority | Category | Impact | Risk |
|---|---|---|---|
| 1 | Storage Optimization | CRITICAL | LOW |
| 2 | Deployment Optimization | HIGH | LOW |
| 3 | Calldata Optimization | HIGH | LOW |
| 4 | Design Patterns | HIGH | MEDIUM |
| 5 | Cross-Contract Calls | MEDIUM-HIGH | MEDIUM |
| 6 | Compiler Optimizations | MEDIUM | LOW |
| 7 | Assembly Tricks | MEDIUM | HIGH |
| 8 | Dangerous Techniques | LOW | CRITICAL |
Quick Reference
Critical: Storage Optimization (Apply First)
Zero-to-One Writes:
- •Avoid zero-to-one storage writes (costs 22,100 gas)
- •Use 1/2 instead of 0/1 for boolean-like values
- •Keep minimum balances in ERC20 contracts
Variable Packing:
// Bad: 3 slots
struct Unpacked {
uint64 time; // slot 1
uint256 amount; // slot 2
address user; // slot 3
}
// Good: 2 slots
struct Packed {
uint64 time; // slot 1 (with address)
address user; // slot 1
uint256 amount; // slot 2
}
Caching:
// Bad: reads storage twice
function increment() public {
require(count < 10);
count = count + 1;
}
// Good: reads storage once
function increment() public {
uint256 _count = count;
require(_count < 10);
count = _count + 1;
}
Constants & Immutables:
uint256 constant MAX = 100; // No storage slot address immutable owner; // Set in constructor, no storage
High: Deployment Optimization
Custom Errors:
// Bad: ~64+ bytes require(amount <= limit, "Amount exceeds limit"); // Good: ~4 bytes error ExceedsLimit(); if (amount > limit) revert ExceedsLimit();
Payable Constructors:
// Saves ~200 gas on deployment
constructor() payable {}
Clone Patterns:
- •Use EIP-1167 minimal proxies for repeated deployments
- •Use UUPS over Transparent Proxy for upgradeable contracts
High: Calldata Optimization
Calldata vs Memory:
// Bad: copies to memory
function process(bytes memory data) external {}
// Good: reads directly from calldata
function process(bytes calldata data) external {}
Avoid Signed Integers:
- •Small negative numbers are expensive (e.g., -1 = 0xffff...)
- •Use unsigned integers in function parameters
High: Design Patterns
Token Standards:
- •Prefer ERC1155 over ERC721 for NFTs (no balanceOf overhead)
- •Consider consolidating multiple ERC20s into one ERC1155
Signature vs Merkle:
- •Prefer ECDSA signatures over Merkle trees for allowlists
- •Implement ERC20Permit for approve + transfer in one tx
Alternative Libraries:
- •Consider Solmate/Solady over OpenZeppelin for gas efficiency
Medium-High: Cross-Contract Calls
Reduce Interactions:
- •Use ERC1363 transferAndCall instead of approve + transferFrom
- •Implement multicall for batching operations
- •Cache external call results (e.g., Chainlink oracles)
Access Lists:
- •Use ERC2930 access list transactions to pre-warm storage
Medium: Compiler Optimizations
Loop Patterns:
// Good: unchecked increment, cached length
uint256 len = arr.length;
for (uint256 i; i < len; ) {
// logic
unchecked { ++i; }
}
Named Returns:
// More efficient bytecode
function calc(uint256 x) pure returns (uint256 result) {
result = x * 2;
}
Bitshifting:
// Cheaper: 3 gas x << 1 // x * 2 x >> 2 // x / 4 // Expensive: 5 gas x * 2 x / 4
Short-Circuit Booleans:
- •Place likely-to-fail conditions first in
&& - •Place likely-to-succeed conditions first in
||
Medium: Assembly (Use Carefully)
Efficient Checks:
// Check address(0) with assembly
assembly {
if iszero(caller()) { revert(0, 0) }
}
// Even/odd check
x & 1 // instead of x % 2
Memory Reuse:
- •Reuse scratch space (0x00-0x40) for small operations
- •Avoid memory expansion in loops
Avoid: Dangerous Techniques
These are unsafe for production:
- •Making all functions payable
- •Ignoring send() return values
- •Using gasleft() for branching
- •Manipulating block.number in tests
Outdated Patterns
These no longer apply in modern Solidity:
- •"external is cheaper than public" - No longer true
- •"!= 0 is cheaper than > 0" - Changed around 0.8.12
References
Full documentation with code examples:
- •
references/solidity-gas-guidelines.md- Complete guide - •
references/rules/- Individual patterns by category
To look up specific patterns:
grep -l "storage" references/rules/ grep -l "assembly" references/rules/ grep -l "struct" references/rules/
Rule Categories in references/rules/
- •
storage-*- Storage optimization patterns - •
deploy-*- Deployment gas savings - •
calldata-*- Calldata optimization - •
design-*- Design pattern choices - •
crosscall-*- Cross-contract call optimization - •
compiler-*- Compiler optimization patterns - •
assembly-*- Low-level assembly tricks
Key Principles
- •Always Benchmark - Compiler behavior varies by context and version
- •Balance Readability - Not all optimizations are worth code complexity
- •Test Both Approaches - Counterintuitive optimizations sometimes increase costs
- •Consider
--via-ir- Modern compiler option may obsolete some tricks - •Use Alternative Libraries - Solmate/Solady often beat OpenZeppelin on gas