diff --git a/.github/workflows/contract-coverage.yml b/.github/workflows/contract-coverage.yml index f1842a6d..324206d4 100644 --- a/.github/workflows/contract-coverage.yml +++ b/.github/workflows/contract-coverage.yml @@ -60,6 +60,40 @@ jobs: - run: bun install + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each + # source file and only recompiles what changed since the hash it last saw. A + # restored cache from an unrelated commit is always safe to build on top of. + # Forge just recompiles whatever the content hashes say is dirty, so a partial + # match via `restore-keys` still saves most of a cold `via_ir` build. This has + # to sit after `bun install`: its postinstall (`setup.bash`) force-reclones + # `lib/`'s pinned dependencies and reapplies the OZ patches on top, so hashing + # `lib/` any earlier would key the cache off content these steps are about to + # replace. + # + # Forge also drops persisted fuzz/invariant/test failure state under + # `cache/fuzz`, `cache/invariant`, and `cache/test-failures`, and replays it + # on the next run before exploring anything new (see foundry.toml's + # `[profile.ci.invariant]` comment). That state is cleared below, both before + # this job's `forge coverage` run and again right before the job ends, so it + # never rides along in what gets restored or saved here. + # + # This job keys its cache under `foundry-build-coverage-`, with `coverage` + # ahead of the OS on purpose. `forge coverage --ir-minimum` compiles under + # different settings from the test job and leaves different content in + # `cache/`/`out/`, so the two must never restore each other's builds. The + # test job restores anything starting with `foundry-build-${{ runner.os }}-` + # and GitHub picks the newest match, so any key that starts with that + # prefix (even `foundry-build-${{ runner.os }}-coverage-...`) would get + # picked up by it. Putting `coverage` first keeps it out of that prefix. + - uses: actions/cache@v4 + with: + path: | + cache + out + key: foundry-build-coverage-${{ runner.os }}-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} + restore-keys: | + foundry-build-coverage-${{ runner.os }}- + - name: Build contracts env: FOUNDRY_DISABLE_NIGHTLY_WARNING: "1" @@ -78,10 +112,17 @@ jobs: FOUNDRY_DISABLE_NIGHTLY_WARNING: "1" run: | set +e + + # A restored cache can carry a fuzz/invariant/test failure persisted by + # an earlier, unrelated run. Forge replays those before running anything + # new, so a stale one would fail this job even after the real bug behind + # it was fixed. Clear it before running coverage. + rm -rf cache/fuzz cache/invariant cache/test-failures + forge coverage --ir-minimum --report summary \ --no-match-coverage "test/|lib/" \ 2>&1 | tee coverage.log - CODE=$? + CODE=${PIPESTATUS[0]} python3 scripts/reports/coverage-report.py coverage.log exit $CODE @@ -231,6 +272,14 @@ jobs: await github.rest.issues.createComment({ owner, repo, issue_number, body }); } + # Last step in the job on purpose: `actions/cache`'s save only runs once + # every step above has finished, and it caches whatever is on disk at that + # point, not just what "Run coverage" left behind. Clearing here too means + # nothing this job did can leave a persisted failure in the saved cache. + - name: Clear persisted Foundry failure state before cache save + if: always() + run: rm -rf cache/fuzz cache/invariant cache/test-failures + cleanup: runs-on: ubuntu-latest if: github.event.action == 'closed' diff --git a/.github/workflows/format-lint-product.yml b/.github/workflows/format-lint-product.yml index 60c8e1ae..da489ef9 100644 --- a/.github/workflows/format-lint-product.yml +++ b/.github/workflows/format-lint-product.yml @@ -9,6 +9,12 @@ permissions: pull-requests: write issues: write +# Keyed by PR number so a new push cancels this PR's still-running lint job instead of +# queuing behind it; every other content-triggered workflow in this repo does the same. +concurrency: + group: format-lint-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-latest diff --git a/.github/workflows/gas-report.yml b/.github/workflows/gas-report.yml index bf47a9b8..1d23c298 100644 --- a/.github/workflows/gas-report.yml +++ b/.github/workflows/gas-report.yml @@ -118,10 +118,31 @@ jobs: - run: bun install - - name: Start revive-eth-rpc (paseo_local fork target) - run: | - docker compose up -d --build - bash scripts/shell/wait-for-eth-rpc.sh + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each + # source file and only recompiles what changed since the hash it last saw. A + # restored cache from an unrelated commit is always safe to build on top of. + # Forge just recompiles whatever the content hashes say is dirty, so a partial + # match via `restore-keys` still saves most of a cold `via_ir` build. This has + # to sit after `bun install`, not just after the master shard's patch step + # above: `bun install`'s postinstall (`setup.bash`) force-reclones `lib/`'s + # pinned dependencies for both shards and reapplies the OZ patches on top, + # so hashing `lib/` any earlier would key the cache off content that step + # is about to replace. + # + # Forge also drops persisted fuzz/invariant/test failure state under + # `cache/fuzz`, `cache/invariant`, and `cache/test-failures`, and replays it + # on the next run before exploring anything new (see foundry.toml's + # `[profile.ci.invariant]` comment). That state is cleared below, both before + # this job's `forge test` run and again right before the job ends, so it + # never rides along in what gets restored or saved here. + - uses: actions/cache@v4 + with: + path: | + cache + out + key: foundry-build-${{ runner.os }}-${{ matrix.target.id }}-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} + restore-keys: | + foundry-build-${{ runner.os }}-${{ matrix.target.id }}- - name: Generate gas report env: @@ -136,6 +157,13 @@ jobs: # while preserving the solc compilation cache under `cache/`. rm -rf out/build-info forge build + + # A restored cache can carry a fuzz/invariant/test failure persisted by + # an earlier, unrelated run. Forge replays those before running anything + # new, so a stale one would show up in this gas report even after the + # real bug behind it was fixed. Clear it before testing. + rm -rf cache/fuzz cache/invariant cache/test-failures + # Skip invariant tests in the gas report: invariant runs dominate # the wall-clock for ~20% of the gas signal (most invariants exercise # the same paths the unit/fuzz suites already cost). Keep the @@ -146,10 +174,6 @@ jobs: --no-match-path 'test/fork/**' \ 2>&1 | tee gas-report.txt - - name: Tear down revive-eth-rpc - if: always() - run: docker compose down --volumes --remove-orphans - - name: Stage shard output if: always() run: | @@ -163,6 +187,14 @@ jobs: path: shard retention-days: 7 + # Last step in the job on purpose: `actions/cache`'s save only runs once + # every step above has finished, and it caches whatever is on disk at that + # point, not just what "Generate gas report" left behind. Clearing here too + # means nothing this job did can leave a persisted failure in the saved cache. + - name: Clear persisted Foundry failure state before cache save + if: always() + run: rm -rf cache/fuzz cache/invariant cache/test-failures + report: name: Gas Report Diff needs: generate diff --git a/.github/workflows/genesis-extractor-test.yml b/.github/workflows/genesis-extractor-test.yml index d5c795dd..63221e33 100644 --- a/.github/workflows/genesis-extractor-test.yml +++ b/.github/workflows/genesis-extractor-test.yml @@ -13,6 +13,13 @@ on: permissions: contents: read +# Keyed by PR number so a new push cancels this PR's still-running extractor check +# instead of queuing behind it; every content-triggered workflow in this repo does +# the same. +concurrency: + group: genesis-extractor-test-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: genesis-extractor-test: runs-on: ubuntu-latest diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 9632d30b..d6275abd 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -9,6 +9,13 @@ permissions: pull-requests: write issues: write +# Keyed by PR number so a new push (or label change) cancels this PR's still-running +# title/label check instead of queuing behind it; every content-triggered workflow in +# this repo does the same. +concurrency: + group: pr-title-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: title: name: PR Title diff --git a/.github/workflows/push_checking.yml b/.github/workflows/push_checking.yml index 43b76d1a..d873a96c 100644 --- a/.github/workflows/push_checking.yml +++ b/.github/workflows/push_checking.yml @@ -78,6 +78,31 @@ jobs: - run: bun install + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each + # source file and only recompiles what changed since the hash it last saw. A + # restored cache from an unrelated commit is always safe to build on top of. + # Forge just recompiles whatever the content hashes say is dirty, so a partial + # match via `restore-keys` still saves most of a cold `via_ir` build. This has + # to sit after `bun install`: its postinstall (`setup.bash`) force-reclones + # `lib/`'s pinned dependencies and reapplies the OZ patches on top, so hashing + # `lib/` any earlier would key the cache off content these steps are about to + # replace. + # + # Forge also drops persisted fuzz/invariant/test failure state under + # `cache/fuzz`, `cache/invariant`, and `cache/test-failures`, and replays it + # on the next run before exploring anything new (see foundry.toml's + # `[profile.ci.invariant]` comment). That state is cleared below, both before + # this job's `forge test` runs and again right before the job ends, so it + # never rides along in what gets restored or saved here. + - uses: actions/cache@v4 + with: + path: | + cache + out + key: foundry-build-${{ runner.os }}-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} + restore-keys: | + foundry-build-${{ runner.os }}- + # Only the unit-fuzz job needs the fork adapter, and only when fork tests # actually exist in the tree. Fork tests are PR-scoped, so `test/fork/` is # empty on master and `hashFiles` returns '' (step skipped, no docker build). @@ -104,6 +129,13 @@ jobs: # build-info from another solc invocation would otherwise survive. # Preserving `cache/` keeps incremental compile fast. rm -rf out/build-info + + # A restored cache can carry a fuzz/invariant/test failure persisted by + # an earlier, unrelated run. Forge replays those before running anything + # new, so a stale one would fail this job even after the real bug behind + # it was fixed. Clear it before building or testing. + rm -rf cache/fuzz cache/invariant cache/test-failures + forge build --sizes > build.log 2>&1 BUILD=$? @@ -163,6 +195,14 @@ jobs: - if: steps.run.outputs.result && contains(steps.run.outputs.result, 'Failed') run: exit 1 + # Last step in the job on purpose: `actions/cache`'s save only runs once + # every step above has finished, and it caches whatever is on disk at that + # point, not just what "Run tests" left behind. Clearing here too means + # nothing this job did can leave a persisted failure in the saved cache. + - name: Clear persisted Foundry failure state before cache save + if: always() + run: rm -rf cache/fuzz cache/invariant cache/test-failures + report: name: Report Test Results needs: test diff --git a/.github/workflows/release-metadata.yml b/.github/workflows/release-metadata.yml index 837cc133..22a13fd2 100644 --- a/.github/workflows/release-metadata.yml +++ b/.github/workflows/release-metadata.yml @@ -23,6 +23,13 @@ on: permissions: contents: read +# Keyed by PR number so a new push cancels this PR's still-running manifest check +# instead of queuing behind it; every content-triggered workflow in this repo does +# the same. +concurrency: + group: release-metadata-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: validate: runs-on: ubuntu-latest