Skip to main content

gas-snapshot-testing

Use when tracking gas costs, detecting regressions in CI, or optimizing Solidity contracts. Covers forge snapshot, gas reports, CI integration, and optimization techniques.

Jump to install

Source facts

Repository
ccashwell/evm-cortex
Last source activity
April 10, 2026 at 16:31
Detected SKILL.md language
English
Stars
131
Forks
18

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
gas-snapshot-testing
description
Use when tracking gas costs, detecting regressions in CI, or optimizing Solidity contracts. Covers forge snapshot, gas reports, CI integration, and optimization techniques.
# Gas Snapshot Testing ## Overview Gas snapshots capture the gas cost of every test function and store them in `.gas-snapshot`. This file is committed to the repo and used in CI to detect gas regressions. ## Creating Snapshots ```bash # Generate gas snapshot forge snapshot # Output stored in .gas-snapshot: # MyContractTest:test_deposit() (gas: 45231) # MyContractTest:test_withdraw() (gas: 38912) # MyContractTest:test_transfer() (gas: 21453) ``` ## Checking for Regressions in CI ```bash # Compare current gas against committed snapshot # Fails if any test uses more gas than recorded forge snapshot --check # Allow up to 5% increase forge snapshot --tolerance 5 ``` ## Gas Report ```bash # Detailed per-function gas breakdown forge test --gas-report # Output: # | src/Vault.sol:Vault | | | | | | # |---------------------|-----------------|-------|--------|-------|---------| # | Function Name | min | avg | median | max | # calls | # | deposit | 45231 | 47892 | 45231 | 53214 | 12 | # | withdraw | 38912 | 40123 | 38912 | 42345 | 8 | # Filter to specific contracts forge test --gas-report --match-contract Vault ``` ## CI Integration ### GitHub Actions ```yaml name: Gas Check on: [pull_request] jobs: gas: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: foundry-rs/foundry-toolchain@v1 - name: Install dependencies run: forge install - name: Check gas snapshots run: forge snapshot --check --tolerance 3 - name: Gas report run: forge test --gas-report | tee gas-report.txt - name: Comment gas report on PR uses: actions/github-script@v7 if: github.event_name == 'pull_request' with: script: | const fs = require('fs'); const report = fs.readFileSync('gas-report.txt', 'utf8'); github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `## Gas Report\n\`\`\`\n${report}\n\`\`\`` }); ``` ## Snapshot-Driven Development ```bash # 1. Create baseline snapshot forge snapshot --snap .gas-snapshot-baseline # 2. Make optimizations # ... edit contracts ... # 3. Compare against baseline forge snapshot --diff .gas-snapshot-baseline # Output shows differences: # test_deposit() (gas: 45231 -> 42100) -3131 (-6.92%) # test_withdraw() (gas: 38912 -> 38900) -12 (-0.03%) ``` ## Gas-Specific Test Patterns ```solidity contract GasTest is Test { MyContract target; function setUp() public { target = new MyContract(); // Warm storage slots for realistic measurements target.initialize(); } // Naming convention: gas_ prefix for benchmark tests function test_gas_deposit_firstTime() public { // Cold storage write — most expensive target.deposit(1 ether); } function test_gas_deposit_subsequent() public { target.deposit(1 ether); // warm up target.deposit(1 ether); // measure this } function test_gas_batchDeposit_10() public { for (uint256 i = 0; i < 10; i++) { target.deposit(1 ether); } } // Measure specific operations with gasleft() function test_gas_specificOperation() public { // Setup (not measured) target.deposit(1 ether); uint256 gasBefore = gasleft(); target.withdraw(0.5 ether); uint256 gasUsed = gasBefore - gasleft(); // Log for manual review emit log_named_uint("withdraw gas", gasUsed); // Assert gas bound assertLt(gasUsed, 50_000, "withdraw too expensive"); } } ``` ## Common Gas Optimizations | Optimization | Gas Saved | Risk | |-------------|-----------|------| | `calldata` over `memory` for external args | 200-2000 | None | | Pack storage (multiple vars in one slot) | 2100 per cold SLOAD | Readability | | Use `uint256` over smaller uints | 0-30 per operation | None | | Unchecked math (where overflow impossible) | 40-100 per op | Must prove safety | | Cache storage reads in memory | 100 per SLOAD | None | | Use custom errors over require strings | 200-5000 per revert | Compatibility | | `immutable` for constructor-set values | 2100 per read | None | ```solidity // Before: 3 SLOADs function getInfo() external view returns (uint256, uint256, uint256) { return (value1, value2, value3); } // After: cache in memory function getInfo() external view returns (uint256 a, uint256 b, uint256 c) { a = value1; b = value2; c = value3; } // Unchecked math where overflow is impossible function distribute(uint256 total, uint256 share) internal pure returns (uint256) { unchecked { return total / share; // share is validated > 0 by caller } } ``` ## Checklist - [ ] `.gas-snapshot` committed to repo - [ ] CI runs `forge snapshot --check` on every PR - [ ] Tolerance threshold set appropriately (1-5%) - [ ] Separate gas benchmark tests from functional tests - [ ] Warm storage slots in setUp before measuring - [ ] Cold vs warm storage costs differentiated in benchmarks - [ ] Gas report reviewed for unexpected cost spikes after changes - [ ] Document accepted gas regression rationale in PR description
View on GitHub