Agent Skills: Gas Optimization Skill

Advanced gas optimization techniques for EVM smart contracts. Covers storage packing, memory vs calldata optimization, assembly/Yul, efficient data structures, batch operations, and benchmark-driven optimization strategies.

UncategorizedID: a5c-ai/babysitter/gas-optimization

Install this agent skill to your local

pnpm dlx add-skill https://github.com/a5c-ai/babysitter/tree/HEAD/plugins/babysitter/skills/babysit/process/specializations/cryptography-blockchain/skills/gas-optimization

Skill Files

Browse the full folder contents for gas-optimization.

Download Skill

Loading file tree…

plugins/babysitter/skills/babysit/process/specializations/cryptography-blockchain/skills/gas-optimization/SKILL.md

Skill Metadata

Name
gas-optimization
Description
Advanced gas optimization techniques for EVM smart contracts. Covers storage packing, memory vs calldata optimization, assembly/Yul, efficient data structures, batch operations, and benchmark-driven optimization strategies.

Gas Optimization Skill

Advanced gas optimization techniques for EVM smart contracts with benchmark-driven analysis.

Capabilities

  • Storage Optimization: Storage packing, slot management, SLOAD/SSTORE minimization
  • Memory Management: Memory vs calldata optimization, expansion costs
  • Assembly/Yul: Low-level optimization for critical paths
  • Data Structures: Gas-efficient mappings, arrays, and structs
  • Batch Operations: Multi-call patterns, bulk transfers
  • Benchmarking: Gas profiling, comparison analysis

MCP/Tool Integration

| Tool | Purpose | Reference | |------|---------|-----------| | Foundry MCP | Gas reports, testing | foundry-mcp-server | | EVM MCP Tools | Opcode analysis | evm-mcp-tools |

Storage Optimization

Storage Layout Packing

// BAD: 3 storage slots (96 bytes used, 96 bytes allocated)
contract BadPacking {
    uint128 a;  // slot 0 (16 bytes)
    uint256 b;  // slot 1 (32 bytes) - can't pack with a
    uint128 c;  // slot 2 (16 bytes)
}

// GOOD: 2 storage slots (80 bytes used, 64 bytes allocated)
contract GoodPacking {
    uint128 a;  // slot 0, bytes 0-15
    uint128 c;  // slot 0, bytes 16-31
    uint256 b;  // slot 1
}

// Gas savings: ~20,000 gas per SSTORE avoided

Minimize Storage Writes

// BAD: Multiple storage writes
function badUpdate(uint256 newA, uint256 newB) external {
    a = newA;  // SSTORE: 20,000 gas (cold) or 2,900 gas (warm)
    b = newB;  // SSTORE: 2,900 gas (warm slot in same tx)
}

// GOOD: Single storage write with packed struct
struct Data {
    uint128 a;
    uint128 b;
}
Data public data;

function goodUpdate(uint128 newA, uint128 newB) external {
    data = Data(newA, newB);  // Single SSTORE: 20,000 gas
}

Use Mappings Over Arrays for Lookups

// BAD: O(n) lookup, expensive for large arrays
uint256[] public values;
function exists(uint256 value) public view returns (bool) {
    for (uint i = 0; i < values.length; i++) {
        if (values[i] == value) return true;  // SLOAD per iteration
    }
    return false;
}

// GOOD: O(1) lookup
mapping(uint256 => bool) public valueExists;
function exists(uint256 value) public view returns (bool) {
    return valueExists[value];  // Single SLOAD
}

Memory vs Calldata

Function Parameters

// BAD: Copies array to memory
function processArray(uint256[] memory data) external {
    // Memory copy cost: 3 gas per word + expansion
}

// GOOD: Read directly from calldata
function processArray(uint256[] calldata data) external {
    // No copy, just pointer to calldata
    // Savings: ~60 gas per 32 bytes
}

// Note: Use memory if you need to modify the array

String/Bytes Handling

// For read-only operations, use calldata
function validate(string calldata input) external pure returns (bool) {
    return bytes(input).length > 0;
}

// For modifications, use memory
function transform(string memory input) internal pure returns (string memory) {
    bytes memory b = bytes(input);
    b[0] = 'X';
    return string(b);
}

Unchecked Arithmetic

// BAD: Overflow checks on every operation (Solidity 0.8+)
function sumArray(uint256[] calldata arr) external pure returns (uint256) {
    uint256 sum = 0;
    for (uint256 i = 0; i < arr.length; i++) {
        sum += arr[i];  // Overflow check: ~40 gas per operation
    }
    return sum;
}

// GOOD: Unchecked when overflow is impossible
function sumArray(uint256[] calldata arr) external pure returns (uint256) {
    uint256 sum = 0;
    uint256 length = arr.length;
    for (uint256 i = 0; i < length;) {
        unchecked {
            sum += arr[i];
            ++i;  // ++i is cheaper than i++
        }
    }
    return sum;
}
// Savings: ~40 gas per iteration

Loop Optimizations

Cache Array Length

// BAD: Length read from storage each iteration
for (uint i = 0; i < array.length; i++) { }  // SLOAD per iteration

// GOOD: Cache length
uint256 length = array.length;
for (uint i = 0; i < length; i++) { }  // Single SLOAD

Pre-increment

// BAD: Post-increment creates temporary
for (uint i = 0; i < length; i++) { }

// GOOD: Pre-increment is cheaper
for (uint i = 0; i < length; ++i) { }
// Savings: ~5 gas per iteration

Custom Errors

// BAD: String error messages
require(balance >= amount, "Insufficient balance");
// Cost: ~50 gas per character + memory expansion

// GOOD: Custom errors (Solidity 0.8.4+)
error InsufficientBalance(uint256 available, uint256 required);
if (balance < amount) revert InsufficientBalance(balance, amount);
// Cost: Fixed ~24 gas for error selector
// Savings: ~50+ gas for typical error messages

Assembly/Yul Optimization

Efficient Balance Check

// Solidity
function getBalance(address account) external view returns (uint256) {
    return account.balance;
}

// Assembly (slightly cheaper)
function getBalance(address account) external view returns (uint256 bal) {
    assembly {
        bal := balance(account)
    }
}

Efficient Memory Operations

// Copy 32 bytes efficiently
function copy32(bytes32 source) internal pure returns (bytes32 dest) {
    assembly {
        dest := source
    }
}

// Efficient keccak256
function efficientHash(bytes32 a, bytes32 b) internal pure returns (bytes32 result) {
    assembly {
        mstore(0x00, a)
        mstore(0x20, b)
        result := keccak256(0x00, 0x40)
    }
}

Batch Operations

Batch Transfers

// BAD: Individual transfers
function transferToMany(address[] calldata recipients, uint256 amount) external {
    for (uint i = 0; i < recipients.length; ++i) {
        token.transfer(recipients[i], amount);  // 21000 base + transfer cost
    }
}

// GOOD: Batch transfer (if supported)
function batchTransfer(
    address[] calldata recipients,
    uint256[] calldata amounts
) external {
    // Single function call overhead
    // Reduced SLOAD for token state
}

Multicall Pattern

function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
    results = new bytes[](data.length);
    for (uint256 i = 0; i < data.length; ++i) {
        (bool success, bytes memory result) = address(this).delegatecall(data[i]);
        require(success);
        results[i] = result;
    }
}
// Combines multiple operations in single transaction

Benchmarking Workflow

Using Foundry Gas Reports

# Run tests with gas report
forge test --gas-report

# Snapshot gas usage
forge snapshot

# Compare against previous snapshot
forge snapshot --check

Gas Snapshot Format

| Contract | Function | Min | Avg | Max | # Calls |
|----------|----------|-----|-----|-----|---------|
| Token    | transfer | 51234 | 54123 | 65432 | 100 |
| Token    | approve  | 24356 | 24356 | 24356 | 50  |

Comparison Testing

contract GasComparison is Test {
    function test_gasComparison_approach1() public {
        uint256 gasBefore = gasleft();
        // Approach 1
        uint256 gasUsed = gasBefore - gasleft();
        emit log_named_uint("Approach 1 gas", gasUsed);
    }

    function test_gasComparison_approach2() public {
        uint256 gasBefore = gasleft();
        // Approach 2
        uint256 gasUsed = gasBefore - gasleft();
        emit log_named_uint("Approach 2 gas", gasUsed);
    }
}

Common Optimizations Summary

| Technique | Savings | Risk | |-----------|---------|------| | Storage packing | 20,000 gas/slot | Low | | Calldata vs memory | 60 gas/32 bytes | Low | | Unchecked arithmetic | 40 gas/op | Medium | | Custom errors | 50+ gas/error | Low | | Cache storage reads | 100-2100 gas | Low | | Loop pre-increment | 5 gas/iteration | Low | | Assembly | Varies | High |

Process Integration

This skill integrates with:

  • gas-optimization.js - Full optimization process
  • smart-contract-development-lifecycle.js - Development best practices
  • amm-pool-development.js - DeFi-specific optimizations

Tools Reference

| Tool | Purpose | URL | |------|---------|-----| | Foundry | Gas reporting | foundry-rs | | Hardhat Gas Reporter | Gas reports | hardhat-gas-reporter | | evm.codes | Opcode costs | evm.codes | | Solidity Optimizer | Compiler optimization | Solidity Docs |

See Also

  • skills/evm-analysis/SKILL.md - Bytecode analysis
  • agents/gas-optimizer/AGENT.md - Gas optimization agent
  • references.md - Gas optimization resources
Gas Optimization Skill Skill | Agent Skills