Files
gh-tenequm-claude-plugins-f…/skills/skill/references/gas-optimization.md
2025-11-30 09:01:14 +08:00

334 lines
7.2 KiB
Markdown

# Solidity Gas Optimization Guide
Comprehensive gas optimization techniques for Solidity 0.8.30 with Foundry profiling.
## Storage Optimization
### Variable Packing
Pack multiple variables into single 32-byte storage slots:
```solidity
// BAD: 3 storage slots (3 SSTORE operations)
contract Unoptimized {
uint256 amount; // slot 0
uint8 status; // slot 1
address owner; // slot 2
}
// GOOD: 2 storage slots
contract Optimized {
uint128 amount; // slot 0 (16 bytes)
uint96 lockTime; // slot 0 (12 bytes)
uint8 status; // slot 0 (1 byte)
address owner; // slot 1 (20 bytes)
}
```
**Savings:** 15,000+ gas per packed write
### Struct Packing
Pack struct fields in declaration order:
```solidity
// BAD: 3 slots
struct BadPacking {
uint256 a; // slot 0
uint8 b; // slot 1
uint256 c; // slot 2
}
// GOOD: 2 slots
struct GoodPacking {
uint256 a; // slot 0
uint128 c; // slot 1 (16 bytes)
uint8 b; // slot 1 (1 byte)
}
```
### Storage vs Memory vs Calldata
**Gas Costs:**
| Operation | Gas |
|-----------|-----|
| SLOAD (cold) | 2,100 |
| SLOAD (warm) | 100 |
| SSTORE (0→non-0) | 20,000 |
| SSTORE (non-0→non-0) | 5,000 |
| MLOAD/MSTORE | 3 per word |
| Calldata read | 3 per byte |
**Cache storage reads:**
```solidity
// BAD: 3 SLOAD operations
function process() external returns (uint256) {
return value * 2 + value * 3 + value * 4;
}
// GOOD: 1 SLOAD + memory operations
function process() external returns (uint256) {
uint256 v = value;
return v * 2 + v * 3 + v * 4;
}
```
**Use calldata for external function arrays:**
```solidity
// BAD: Copies to memory
function batchProcess(uint256[] memory values) external { }
// GOOD: Reads directly from calldata
function batchProcess(uint256[] calldata values) external {
for (uint256 i; i < values.length; ) {
// Process values[i]
unchecked { i++; }
}
}
```
## Arithmetic Optimization
### Unchecked Blocks
Solidity 0.8+ adds overflow checks by default (~50-100 gas per operation):
```solidity
// BAD: Overflow check on every increment
for (uint256 i = 0; i < 100; i++) {
result += i;
}
// GOOD: Unchecked increment (safe when bounded)
for (uint256 i = 0; i < 100; ) {
result += i;
unchecked { i++; }
}
```
**Savings:** 50-100 gas per iteration
### Short-Circuit Logic
Order conditions by likelihood to fail:
```solidity
// Expensive check last (short-circuits if first fails)
if (amount > 0 && expensiveValidation(amount)) {
// ...
}
```
### Bitwise Flag Packing
Combine booleans into single uint256:
```solidity
// BAD: 4 storage slots
bool isActive;
bool isVerified;
bool isPaused;
bool isBlocked;
// GOOD: 1 storage slot, bit manipulation
uint256 flags;
function isActive() view returns (bool) {
return (flags & (1 << 0)) != 0;
}
function setActive(bool _active) {
if (_active) flags |= (1 << 0);
else flags &= ~(1 << 0);
}
```
## Variable Declaration
### Immutable vs Constant vs Storage
```solidity
// Storage variable: 2,100 gas (cold read)
uint256 public maxSupply = 1_000_000;
// Constant: ~3 gas (embedded in bytecode)
uint256 public constant MAX_SUPPLY = 1_000_000;
// Immutable: ~100 gas (set once in constructor)
uint256 public immutable maxSupply;
constructor(uint256 _max) { maxSupply = _max; }
```
**Use constant for compile-time values, immutable for constructor-set values.**
### Function Visibility
```solidity
// Public creates getter (extra dispatch)
uint256 public value;
// External is cheaper for functions not called internally
function getValue() external view returns (uint256) {
return _value;
}
```
## Error Handling
### Custom Errors vs Require Strings
```solidity
// BAD: String stored in bytecode (~50+ gas)
require(msg.sender == owner, "Unauthorized access");
// GOOD: Custom error (~24 gas)
error Unauthorized();
if (msg.sender != owner) revert Unauthorized();
```
**Savings:** 20-50 gas per revert
## Events vs Storage
Store transient data in events instead of contract storage:
```solidity
// BAD: 20,000+ gas per write
uint256[] public transfers;
function recordTransfer(uint256 amount) external {
transfers.push(amount);
}
// GOOD: ~375 gas base + 8 gas per byte
event Transfer(address indexed to, uint256 amount);
function recordTransfer(address to, uint256 amount) external {
emit Transfer(to, amount);
}
```
## Compiler Settings
```toml
# foundry.toml
[profile.default]
optimizer = true
optimizer_runs = 200 # Balance size/runtime
[profile.production]
optimizer = true
optimizer_runs = 1000000 # Optimize for runtime
via_ir = true # IR pipeline (slower compile, better optimization)
```
**Guidance:**
- `200` runs: Smaller deployment, higher runtime cost
- `10000+` runs: Larger deployment, lower runtime cost
- `via_ir = true`: Best optimization, slowest compilation
## EVM Opcode Reference
| Operation | Gas | Notes |
|-----------|-----|-------|
| ADD/SUB/MUL | 3 | Basic arithmetic |
| DIV/MOD | 5 | Division operations |
| SLOAD (cold) | 2,100 | First storage read |
| SLOAD (warm) | 100 | Subsequent reads |
| SSTORE (0→non-0) | 20,000 | New storage slot |
| SSTORE (non-0→non-0) | 5,000 | Update existing |
| SSTORE (→0) | 5,000 | +15,000 refund |
| CALL | 700+ | External call base |
| KECCAK256 | 30 | +6 per word |
| LOG0-LOG4 | 375-1,875 | Events |
## Foundry Gas Profiling
### Gas Reports
```bash
# Generate gas report
forge test --gas-report
# Filter by contract
forge test --match-contract MyContract --gas-report
```
### Gas Snapshots
```bash
# Create snapshot
forge snapshot
# Compare against previous
forge snapshot --diff
# Check threshold
forge snapshot --check --tolerance 5
```
### Inline Gas Measurement
```solidity
function testGasUsage() public {
uint256 gasBefore = gasleft();
// Code to measure
contract.doSomething();
uint256 gasUsed = gasBefore - gasleft();
console.log("Gas used:", gasUsed);
}
```
### Section Snapshots
```solidity
function testOptimization() public {
vm.startSnapshotGas("operation");
// Code to profile
value = 1;
uint256 gasUsed = vm.stopSnapshotGas();
}
```
## Optimization Checklist
**Storage:**
- [ ] Pack related variables together
- [ ] Use smaller integer types when possible
- [ ] Cache frequently accessed storage variables
- [ ] Use mappings instead of arrays for lookups
- [ ] Use immutable/constant for fixed values
**Functions:**
- [ ] Mark external (not public) when not called internally
- [ ] Use calldata for array parameters
- [ ] Use unchecked blocks for safe arithmetic
- [ ] Short-circuit expensive conditions
- [ ] Use custom errors instead of require strings
**Compilation:**
- [ ] Enable optimizer
- [ ] Set appropriate optimizer_runs
- [ ] Consider via_ir for production
**Testing:**
- [ ] Use `forge test --gas-report` regularly
- [ ] Create gas snapshots for regression detection
- [ ] Profile critical functions
## Quick Wins
| Pattern | Gas Saved | Effort |
|---------|-----------|--------|
| Custom errors | 20-50/revert | Low |
| Unchecked loops | 50-100/iter | Low |
| Calldata vs memory | 5,000+ | Low |
| Variable packing | 15,000/write | Low |
| Immutable vars | 2,000/read | Low |
| Bitwise flags | 15,000 | Medium |
| Events vs storage | 19,000+ | Medium |