Solidity smart contract gas optimization guidelines based on RareSkills. Use when writing, reviewing, or auditing Solidity code. Triggers on tasks involving smart contracts, EVM development, gas optimization, or Solidity best practices.
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 guidereferences/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 patternsdeploy-*- Deployment gas savingscalldata-*- Calldata optimizationdesign-*- Design pattern choicescrosscall-*- Cross-contract call optimizationcompiler-*- Compiler optimization patternsassembly-*- 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
You Might Also Like
Related Skills

coding-agent
Run Codex CLI, Claude Code, OpenCode, or Pi Coding Agent via background process for programmatic control.
openclaw
add-uint-support
Add unsigned integer (uint) type support to PyTorch operators by updating AT_DISPATCH macros. Use when adding support for uint16, uint32, uint64 types to operators, kernels, or when user mentions enabling unsigned types, barebones unsigned types, or uint support.
pytorch
at-dispatch-v2
Convert PyTorch AT_DISPATCH macros to AT_DISPATCH_V2 format in ATen C++ code. Use when porting AT_DISPATCH_ALL_TYPES_AND*, AT_DISPATCH_FLOATING_TYPES*, or other dispatch macros to the new v2 API. For ATen kernel files, CUDA kernels, and native operator implementations.
pytorch
skill-writer
Guide users through creating Agent Skills for Claude Code. Use when the user wants to create, write, author, or design a new Skill, or needs help with SKILL.md files, frontmatter, or skill structure.
pytorch
implementing-jsc-classes-cpp
Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or constructors.
oven-sh
implementing-jsc-classes-zig
Creates JavaScript classes using Bun's Zig bindings generator (.classes.ts). Use when implementing new JS APIs in Zig with JSC integration.
oven-sh