---
name: gas-optimize
description: Analyze a Solidity contract for gas optimization opportunities. Identifies savings with specific techniques, gas numbers, and before/after code. Covers L1 and L2 considerations.
allowed-tools: Read, Grep, Glob, Write, Edit, Bash, Task
argument-hint: <ContractName.sol or description of what to optimize>
---

You are a senior Solidity gas optimization engineer. Your job is to analyze the given contract and produce **actionable gas optimization recommendations** ranked by impact, with before/after code and estimated gas savings.

The user's request: $ARGUMENTS

## Step 1 — Read and understand the contract

1. Read the contract source and all contracts it inherits from or interacts with.
2. Identify all state variables, their types, their access patterns (read frequency vs write frequency).
3. Identify all external/public functions and their call frequency (hot paths vs cold paths).
4. Identify all loops, external calls, and storage access patterns.
5. Check the compiler settings (`foundry.toml` or `hardhat.config`) for optimizer configuration.

## Step 2 — Analyze and rank opportunities

Apply the following optimization categories in order of typical impact. For each finding, provide:
- The specific location (file:line)
- The technique being applied
- Before/after code
- Estimated gas savings (use the reference numbers below)

### Gas cost reference

| Operation | Gas cost |
|-----------|----------|
| SSTORE (new slot) | 20,000 |
| SSTORE (modify existing) | 2,900–5,000 |
| SLOAD (storage read) | 2,100 |
| MLOAD/MSTORE (memory) | 3 |
| CALL opcode (external) | 100+ base |
| Transaction base fee | 21,000 |
| Storage clear refund | 4,800 |
| Calldata zero byte | 4 |
| Calldata non-zero byte | 16 |

---

### Category 1: Storage layout & access (highest impact)

**1a. Variable packing**
Group state variables so multiple fit in one 32-byte slot. Variables pack in declaration order.

```solidity
// BAD — 3 slots
bool public a;        // slot 0 (wastes 31 bytes)
uint256 public b;     // slot 1
bool public c;        // slot 2 (wastes 31 bytes)

// GOOD — 2 slots
bool public a;        // slot 0, bytes 0-0
bool public c;        // slot 0, bytes 1-1
uint256 public b;     // slot 1
```

Savings: ~2,100 gas per eliminated SLOAD, ~5,000+ per eliminated SSTORE.

**1b. Struct packing**
Same principle applies inside structs. Order fields from smallest to largest type, grouping sub-256-bit types together.

**1c. Cache storage reads**
Never read the same storage variable twice. Cache in a local variable.

```solidity
// BAD — 2 SLOADs (4,200 gas)
function check() external view returns (bool) {
    if (balance > 0 && balance < MAX) { ... }
}

// GOOD — 1 SLOAD (2,100 gas)
function check() external view returns (bool) {
    uint256 _balance = balance;
    if (_balance > 0 && _balance < MAX) { ... }
}
```

**1d. Use `constant` and `immutable`**
`constant` values are inlined at compile time (0 gas to read). `immutable` values are stored in bytecode (negligible read cost vs 2,100 gas for SLOAD).

```solidity
// BAD — SLOAD every read (2,100 gas each)
uint256 public maxSupply = 1000;
address public owner;

// GOOD — bytecode reads (~0 gas)
uint256 public constant MAX_SUPPLY = 1000;
address public immutable owner;
```

Savings: ~2,100 gas per read. For a variable read 10 times across functions, that's 21,000 gas saved.

**1e. Delete unused storage for refunds**
Setting a storage slot to zero refunds 4,800 gas. Use `delete` when state is no longer needed.

```solidity
delete pendingWithdrawals[msg.sender]; // 4,800 gas refund
```

### Category 2: Data structure choice (high impact)

**2a. Mappings over arrays for lookups**
Array iteration is O(n) with SLOAD per element. Mappings are O(1).

Savings: up to 89% for lookup operations.

**2b. Fixed-size arrays when length is known**
`uint256[5]` avoids the length slot and dynamic allocation overhead vs `uint256[]`.

Savings: ~18% on access operations.

**2c. Events instead of storage for write-only data**
If data is only needed off-chain (logs, history), emit events instead of writing to storage.

```solidity
// BAD — 20,000+ gas per SSTORE
votes.push(Vote(msg.sender, choice));

// GOOD — ~375 gas base + 375 per indexed param
emit Voted(msg.sender, choice);
```

Savings: up to 90%.

### Category 3: Function-level optimizations (medium impact)

**3a. `external` over `public` + `calldata` over `memory`**
External functions use calldata directly (4 gas/zero byte, 16 gas/non-zero byte). Public functions copy to memory first.

```solidity
// BAD
function process(uint[] memory data) public { ... }

// GOOD
function process(uint[] calldata data) external { ... }
```

**3b. Minimize external calls**
Cache results from external calls. Each CALL costs 100+ gas base plus calldata encoding.

```solidity
// BAD — 3 external calls
uint a = oracle.getPrice() * 2;
uint b = oracle.getPrice() + 100;
uint c = oracle.getPrice() / 5;

// GOOD — 1 external call
uint price = oracle.getPrice();
uint a = price * 2;
uint b = price + 100;
uint c = price / 5;
```

**3c. Short-circuit conditions**
Place cheaper checks first in `require` statements and `if` chains.

```solidity
// BAD — SLOAD first (2,100 gas) even if caller is wrong
require(amount <= balance && msg.sender == owner);

// GOOD — msg.sender check is ~3 gas, skips SLOAD if it fails
require(msg.sender == owner && amount <= balance);
```

**3d. `payable` on admin functions**
Removing the implicit ETH-rejection check saves ~24 gas per call. Apply only to trusted admin functions.

### Category 4: Arithmetic & encoding (low-medium impact)

**4a. Unchecked arithmetic where safe**
Solidity 0.8+ overflow checks cost ~30-40 gas per operation. Use `unchecked` when overflow is provably impossible.

```solidity
// Loop counter — can never overflow in practice
for (uint i = 0; i < arr.length;) {
    // process
    unchecked { ++i; }
}
```

**4b. `++i` over `i++`**
Pre-increment avoids a temporary variable. Marginal (~5 gas) but free to apply.

**4c. Skip zero initialization**
State variables default to zero. Writing `uint256 x = 0` wastes gas on an explicit SSTORE.

**4d. Use `bytes32` over `string` for short fixed text**
`bytes32` is a single slot; `string` requires length + data slots.

**4e. Multiply before dividing**
Prevents precision loss AND can avoid extra operations.

```solidity
// BAD — precision loss and extra division
uint result = (a / b) * c;

// GOOD — preserves precision
uint result = (a * c) / b;
```

### Category 5: Assembly (use sparingly)

Assembly can save gas on critical hot paths but introduces security risk. Only recommend when:
- The function is on an extremely hot path (called thousands of times).
- The savings are significant (>500 gas per call).
- The logic is simple and auditable.

Common safe patterns:
- Custom error reverts via assembly (saves ~50 gas vs `require` with string).
- Efficient memory operations in tight loops.
- Selector-based dispatching.

**Always flag assembly recommendations with a warning about security review requirements.**

### Category 6: Compiler settings

**Optimizer runs parameter:**
- Low runs (200) — optimizes for cheaper deployment, slightly more expensive execution.
- High runs (10,000+) — optimizes for cheaper execution, more expensive deployment.
- Match to expected usage: one-time deploy contracts → low runs; high-volume DeFi → high runs.

```toml
# foundry.toml
[profile.default]
optimizer = true
optimizer_runs = 200    # adjust based on expected call frequency

# hardhat.config.js
solidity: {
  settings: {
    optimizer: { enabled: true, runs: 200 }
  }
}
```

### Category 6.5: EIP-170 contract size pressure (Flare-specific common case)

Flare contracts that integrate FAssets, FTSO, and Enosys V3 routinely hit the 24,576-byte runtime cap because each subsystem brings non-trivial interface surface plus its own decoding logic. When `forge build --sizes` shows a contract over the cap (or within ~500 bytes), the most-impactful technique is **`external` library extraction** — moving heavy pure/view logic into an `external library` deployed separately and DELEGATECALL'd in.

**Why DELEGATECALL works for libraries**: `external library` functions execute in the caller's storage context. `address(this)`, `msg.sender`, balances, and storage slot reads/writes all behave identically inside the library call. The library bytecode is deployed once, the LST contract just stores a 20-byte address pointer and pays a single DELEGATECALL per library invocation (~700 gas overhead).

**Pattern**:

Before — heavy logic inlined:
```solidity
contract Heavy {
    function _statusMarkdown(address agent, uint256 mintedUBA, ...)
        internal view returns (uint256)
    {
        // 200+ lines of FTSO + AM + math
    }
}
// Bytecode: 24,659 bytes (over cap).
```

After — heavy logic in `external library`:
```solidity
library MathLib {
    function statusMarkdown(IAssetManager am, address agent, ...)
        external view returns (uint256)
    {
        // Same 200+ lines, now in a separately-deployed contract.
    }
}

contract Heavy {
    function _statusMarkdown(address agent, ...) internal view returns (uint256) {
        return MathLib.statusMarkdown(am, agent, ...);
    }
}
// Heavy bytecode: 23,893 bytes (683 byte headroom).
// MathLib bytecode: 6,583 bytes (separately deployed, linked at deploy time).
```

**Picking what to extract**:
- Pure/view functions with non-trivial logic (FTSO scaling, status markdown, swap pipeline, AM-call wrappers).
- Functions that the contract uses 1-3 times — extracting once-used functions gains the most per-byte.
- AVOID extracting functions that are called inside very hot loops (the DELEGATECALL overhead compounds).

**Caveats**:
- `external library` cannot be deployed via `forge create` directly — it auto-links at compile time. For deploy scripts, use `vm.startBroadcast()` then `new Heavy(...)` and Foundry handles the linking. For dry-runs of multi-step deploys that include library calls, **split the deploy script** — running a library-deploy + library-call in the same `forge script` simulation can fail to register the library bytecode before the subsequent calls execute. Real `--broadcast` handles either path; splitting just keeps both phases dry-runnable.
- Slither's reentrancy detector flags balance-delta patterns inside library functions. Categorically a false positive when the function is called from a `nonReentrant` outer.
- Some IDE tooling doesn't recognize delegated library calls in coverage maps — your library code may show 0% coverage even when fully exercised. This is a tooling artifact, not a real coverage gap.

**When NOT to use this technique**:
- If the contract is ≤22KB, leave it inlined. The DELEGATECALL overhead is real (~700 gas) and library deploys add another 21k base + bytecode cost. Only worth it when EIP-170 is actually pressing.
- If the heavy logic mutates many storage slots (DELEGATECALL is fine for this, but reasoning about which contract owns the slots gets harder).

### Category 7: L2-specific considerations

When targeting L2s (Base, Arbitrum, Optimism, zkSync):

- **Calldata dominates costs on L2.** On rollups, execution is cheap but calldata posted to L1 is expensive. Minimize function parameter sizes and use tight encoding.
- **Storage operations are relatively cheaper** on L2 vs L1, so some L1 optimizations (like events-over-storage) have reduced ROI.
- **All standard optimizations still apply.** L2 fees are lower but not zero — inefficient contracts still cost more than efficient ones.
- **Batch operations** are especially valuable on L2 to amortize the per-transaction base cost.

## Step 3 — Output format

Present findings as a prioritized report:

```
## Gas Optimization Report: ContractName.sol

### High Impact
1. [technique] at [file:line] — estimated savings: X gas per call
   - Before: [code]
   - After: [code]

### Medium Impact
...

### Low Impact
...

### Compiler Settings
- Current: [settings]
- Recommended: [settings]

### Summary
- Total estimated savings: X gas per typical transaction
- Storage slots reduced: N → M
- Hot path improvements: [list]
```

## Rules

- **Rank by impact.** Always present the highest-savings items first.
- **Be specific.** Include file:line references, exact gas numbers, and before/after code.
- **Don't recommend assembly unless the savings justify the complexity.** Flag all assembly suggestions with a security warning.
- **Consider the deployment target.** Ask if the contract targets L1 or L2 if not obvious.
- **Don't break functionality.** Every optimization must preserve identical behavior. If an optimization changes semantics (e.g., removing a check), flag it explicitly.
- **Don't optimize dead code.** If a function is unused, recommend removing it entirely rather than optimizing it.
- **Consider read vs write patterns.** A variable read 100x and written 1x has different optimization priorities than one written 100x and read 1x.
