ci: pin the Foundry toolchain and stabilise its cache key #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main, feat/**] | |
| pull_request: | |
| branches: [main] | |
| # Third-party actions are pinned to specific commit SHAs (not version tags) | |
| # to defend against tag-move supply-chain attacks. To update, look up the | |
| # new tag's SHA via `gh api repos/<owner>/<repo>/git/refs/tags/<tag>` and | |
| # bump both the SHA and the trailing version comment. | |
| permissions: | |
| contents: read | |
| jobs: | |
| forge-test: | |
| name: forge test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| submodules: recursive | |
| - name: Install Foundry | |
| uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 | |
| with: | |
| # Pin the toolchain: this repo's whole provenance story is that the | |
| # deployed bytecode is reproducible from source, which a drifting | |
| # compiler quietly undermines. It also makes the cache key stable -- | |
| # `stable` re-resolves on every Foundry release, missing the cache and | |
| # forcing a fresh download of the release + its attestation from the | |
| # GitHub CDN. That download is what broke CI on 2026-08-12 | |
| # (`curl: (56) Connection died` fetching the attestation, twice). | |
| version: v1.7.1 | |
| # Keyed on the pinned version rather than the commit SHA, so the | |
| # toolchain is restored from cache across commits and jobs instead of | |
| # being re-downloaded for each one. | |
| cache-key: foundry-${{ runner.os }}-v1.7.1 | |
| cache-restore-keys: foundry-${{ runner.os }}- | |
| - name: forge build | |
| run: forge build --sizes | |
| # Unit tests only. Fuzz + invariant campaigns are excluded from CI by policy | |
| # (they belong to the manual deep-run proof gates, e.g. 10k runs x 5M calls | |
| # before release) -- running them on every push would make CI take ages at | |
| # real run counts. Run them locally / in the release gates: | |
| # forge test --match-test "(testFuzz_|invariant_)" | |
| - name: forge test (unit only) | |
| run: forge test -vv --no-match-test "(testFuzz_|invariant_)" | |
| abi-equivalence: | |
| name: ABI equivalence guard | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| submodules: recursive | |
| - name: Install Foundry | |
| uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 | |
| with: | |
| # Pin the toolchain: this repo's whole provenance story is that the | |
| # deployed bytecode is reproducible from source, which a drifting | |
| # compiler quietly undermines. It also makes the cache key stable -- | |
| # `stable` re-resolves on every Foundry release, missing the cache and | |
| # forcing a fresh download of the release + its attestation from the | |
| # GitHub CDN. That download is what broke CI on 2026-08-12 | |
| # (`curl: (56) Connection died` fetching the attestation, twice). | |
| version: v1.7.1 | |
| # Keyed on the pinned version rather than the commit SHA, so the | |
| # toolchain is restored from cache across commits and jobs instead of | |
| # being re-downloaded for each one. | |
| cache-key: foundry-${{ runner.os }}-v1.7.1 | |
| cache-restore-keys: foundry-${{ runner.os }}- | |
| - name: forge build | |
| run: forge build | |
| - name: Run ABI equivalence check | |
| # Verifies the unified IExitFeeController.sol and the v0_4 outlier | |
| # have identical method selectors, event topics, error selectors, | |
| # SkipReason enum ordinals, and compile under solc 0.5.17 / | |
| # 0.6.11 / 0.8.20 / 0.4.26. | |
| run: tools/check-abi-equivalence.sh | |
| upgrade-safety-fixtures: | |
| name: Upgrade-safety regression | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| submodules: recursive | |
| - name: Install Foundry | |
| uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0 | |
| with: | |
| # Pin the toolchain: this repo's whole provenance story is that the | |
| # deployed bytecode is reproducible from source, which a drifting | |
| # compiler quietly undermines. It also makes the cache key stable -- | |
| # `stable` re-resolves on every Foundry release, missing the cache and | |
| # forcing a fresh download of the release + its attestation from the | |
| # GitHub CDN. That download is what broke CI on 2026-08-12 | |
| # (`curl: (56) Connection died` fetching the attestation, twice). | |
| version: v1.7.1 | |
| # Keyed on the pinned version rather than the commit SHA, so the | |
| # toolchain is restored from cache across commits and jobs instead of | |
| # being re-downloaded for each one. | |
| cache-key: foundry-${{ runner.os }}-v1.7.1 | |
| cache-restore-keys: foundry-${{ runner.os }}- | |
| - name: Ensure python3 | |
| # ubuntu-latest already ships python3; sanity-check. | |
| run: python3 --version | |
| - name: forge build | |
| run: forge build | |
| - name: Run upgrade-safety regression scenarios | |
| # Spins up an isolated anvil and exercises: | |
| # 1. identity (no source change) -> exit 0 | |
| # 2. GoodPackedV2 (packed safe upgrade) -> exit 0 | |
| # 3. BadV2 (out-of-namespace storage) -> exit 1 | |
| # 4. BadV3 (struct member reorder) -> exit 1 | |
| # Anvil binds 8548 by default; can be overridden via $ANVIL_PORT | |
| # if it clashes with another job on the same runner. | |
| env: | |
| ANVIL_PORT: 8548 | |
| run: tools/test-upgrade-safety.sh |