Skip to content

Remove the dead gas-snapshot apparatus - #121

Merged
thedavidmeister merged 2 commits into
mainfrom
2026-08-16-issue-84
Aug 17, 2026
Merged

Remove the dead gas-snapshot apparatus#121
thedavidmeister merged 2 commits into
mainfrom
2026-08-16-issue-84

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

Closes #84

The call: remove, not start snapshotting

#84 offers two routes. Both ledgers, measured on this clone with the flake toolchain:

Route A — remove (taken). Build cost: one deleted file, two config lines, six
comment lines; net −10 lines, no additions. Carrying cost after: zero, nothing left
to keep in sync. Cost to reinstate: forge snapshot regenerates the whole file in
one command — measured at 13054 bytes / 134 lines, and byte-identical across two
runs — and the two config entries are one line each. Nothing authored is lost: the
blob has been 0 bytes since 88f5ac6 (2025-10-21), and the last commit that left it
non-empty (4b3725f, 2024-10-19, 206 bytes) recorded LibBloomTest runs — a test
that does not exist in this repo — so the file was already describing a different
tree the last time it held anything.

Route B — take a snapshot in CI (rejected). Three costs, in order of weight:

  1. It cannot be built here. All three workflows in .github/workflows/ are
    one-line calls into rainix reusables, and shared CI belongs in rainix rather
    than in a consumer. A snapshot gate is therefore a rainlanguage/rainix change
    landing on every consumer repo, plus a committed snapshot in each — not a change
    this issue can make.
  2. The measured value is nil. LibCodeGen, LibFs and LibHexString all import
    forge-std/Vm.sol and take a Vm as a parameter: this repo is build-time
    tooling, and none of it is deployed. The 134 numbers a snapshot would record are
    the gas of cheatcode calls (vm.toString, vm.writeFile, vm.readFileBinary)
    inside a test harness, which no consumer pays on chain. The runtime-gas artifact
    this repo exists to produce is the generated constant cache in a consumer's
    repo, and its gas is measurable there.
  3. Carrying cost. A 134-line generated file regenerated and committed by every PR
    that touches any library — a conflict surface across concurrent PRs — and a
    doubled test run in CI.

One thing that is not a reason to reject Route B, contrary to what fuzz-heavy
[fuzz] runs = 2048 suggests: the gate would be stable. forge snapshot fixes the
fuzz seed, so two runs produced byte-identical files and forge snapshot --check
against one of them exited 0, while plain forge test re-seeds every run (same
test, three runs: μ 312478 / 306992 / 304937). Route B is rejected for cost and
zero signal, not for flakiness.

What changed

  • Deleted the tracked, 0-byte .gas-snapshot.
  • REUSE.toml: dropped the .gas-snapshot annotation.
  • .soldeerignore: dropped the .gas-snapshot entry.
  • foundry.toml: dropped the commented-out via_ir/optimizer/optimizer_runs/
    optimizer_steps block and the "These settings should be used for snapshots"
    line. optimizer = true / optimizer_runs = 1000000 stay — they are the in-force
    compile settings and were never part of the snapshot apparatus except in the
    comment's claim.

Where the issue was wrong

The issue states that REUSE.toml:5 "lists .gas-snapshot among the annotated
paths so reuse lint passes on a file with no header". Measured, that is not true
at the state the repo is in: reuse skips zero-byte files, so with the annotation
deleted and the empty file still present, reuse lint passes 47/47 (B3 below). The
annotation only becomes load-bearing once the file has content (B3b/B3c). The
.soldeerignore entry, by contrast, is live today: deleting it alone ships the
0-byte file to every consumer (B2). Same fix either way; different account of which
of the two entries was doing anything.

The issue names four locations. There is a fifth reference, .audit/scope.json:5,
which lists .gas-snapshot in the audited file set for commit 7aa85a4. Left
untouched: it records what that audit run covered, and editing it would falsify the
record rather than clean anything up.

Why no new forge test

This change removes configuration, not code, so forge test has no visibility into
any of it — the removed entries are consumed by reuse lint (rainix-sol-legal) and
by forge soldeer push (rainix-autopublish) respectively, and both of those gates
already run in CI. The check ran below with a positive control on each.

A Solidity test asserting .gas-snapshot is absent would need
{ access = "read", path = ".gas-snapshot" } added to fs_permissions, or a read
grant on the repo root. The first is a new config entry servicing a removed file —
literally the defect being fixed. The second widens the test profile's filesystem
reach in the one repo whose libraries exist to confine generated paths. Neither is
worth it for a hygiene removal already covered by two live CI gates.

QA

  • Discriminating tests: no new forge test — the diff is config-only and forge test cannot see any of it (reasoning above). The discriminating checks are the two live CI gates over the removed entries, each run red-then-green: reuse lint (red at B3b — exit 1, .gas-snapshot named missing; green at A1 — 47/47) and forge soldeer push --dry-run (red at B2 — the 0-byte file shipped, 21 → 22 entries; green at A2 — 21 entries, file absent). Plus forge script script/Build.sol + git diff --exit-code src/generated, red at A6c and green at A5.
  • Mutations applied: .soldeerignore:2 deleted alone (file kept) -> killed by forge soldeer push --dry-run, package 21 → 22 files. REUSE.toml:6 deleted alone with a non-empty .gas-snapshot -> killed by reuse lint, exit 1, 47/48. REUSE.toml:6 deleted alone with the 0-byte file -> survived (reuse skips empty files) — reported above as a correction to the issue, not a gap. foundry.toml optimizer_runs = 1000000 -> 200, and optimizer = true -> false -> both survived the regenerate-and-compare gate, which falsified the replacement comment this branch first wrote; the comment was deleted in db22ba7 rather than the check weakened. test/concrete/CodeGennable.sol contract CodeGennable {} -> add function f() -> killed by the drift gate, BYTECODE_HASH 0x8016e5…a84d0x7b482d…c4a7, which is the positive control proving that gate is not dead.
  • Oracle: the REUSE 3.3 spec and reuse lint's own file accounting (47 vs 48 files carrying license info), and the actual zip forge soldeer push --dry-run hands to consumers — both external to this repo's code. For Route B's ledger, git log/git cat-file -s on .gas-snapshot's three commits, and git grep over rainlanguage/rainix@7f223b4's workflows, where every snapshot hit is a per-tag deploy-pin snapshot under src/generated/<tag>/ and none is forge snapshot.
  • Category check: the issue asks for all four of foundry.toml:14-23, .gas-snapshot, REUSE.toml:5, .soldeerignore:2 under a stated decision between removal and snapshotting; all four are covered, the decision is ledgered above, and a fifth reference the issue did not name (.audit/scope.json:5) is identified and deliberately left as an audit record. REUSE.toml:17 is left to REUSE.toml redundantly annotates src/lib/LibCodeGen.sol, which already carries its own SPDX header #85.

Everything below was run in this clone via nix develop -c, foundry from the flake.

Before the fix — each removed entry probed for whether it was load-bearing:

# Mutation on main's tree Gate Observed
B1 none (baseline) forge soldeer push --dry-run 21 files, 73093 bytes, .gas-snapshot absent
B2 drop the .soldeerignore entry only, keep the file forge soldeer push --dry-run 21 → 22 files; 0 01-01-1980 00:00 .gas-snapshot now shipped
B3 drop the REUSE.toml entry only, keep the empty file reuse lint exit 0, 47/47 — reuse skips zero-byte files
B3b drop the REUSE.toml entry, file made non-empty reuse lint exit 1, # MISSING COPYRIGHT AND LICENSING INFORMATION / * .gas-snapshot, 47/48
B3c keep the REUSE.toml entry, file non-empty reuse lint exit 0, 48/48

B2's red is the failure this PR has to avoid: removing the .soldeerignore line on
its own would have published a 0-byte .gas-snapshot to every consumer of
rain-sol-codegen. The file and both entries have to go together, which is what the
diff does.

After the fix, at db22ba7:

forge test            134 passed, 0 failed, 0 skipped (16 suites)   exit 0
forge fmt --check                                                   exit 0
reuse lint            47/47 copyright, 47/47 license, compliant     exit 0
forge soldeer push --dry-run   21 files, 73074 bytes, no .gas-snapshot
forge script script/Build.sol && git diff --exit-code src/generated exit 0

The package went 73093 → 73074 bytes at the same 21 files: the two deleted config
lines, and nothing else consumers receive.

Mutation matrix — every check above broken on purpose to prove it can fail:

# Mutation Expected Observed
B2 .soldeerignore entry removed, file kept package grows 21 → 22 files, .gas-snapshot shipped ✅
B3b REUSE.toml entry removed, file non-empty reuse lint red exit 1, .gas-snapshot named as missing ✅
A6 optimizer_runs 1000000 → 200, regenerate drift no drift ❌ — see below
A6b optimizer = false, regenerate drift no drift ❌ — see below
A6c add function f() external pure returns (uint256) to test/concrete/CodeGennable.sol, regenerate drift exit 1, BYTECODE_HASH 0x8016e5…a84d0x7b482d…c4a7

A6/A6b are a survived mutant, and they changed the diff. The first version of this
branch replaced the deleted comment with one asserting that the optimizer settings
determine the BYTECODE_HASH pinned in src/generated/. Regenerating under
optimizer_runs = 200, and again under optimizer = false, left
src/generated/CodeGennable.sol byte-identical — because it is generated from
contract CodeGennable {}, an empty contract whose runtime code is the same under
any optimizer setting. The claim was false, so the replacement comment was removed
in db22ba7 and the settings are left to stand on their own. A6c is the positive
control that keeps A5's "no drift" from being a dead harness: the regenerate-and-
compare gate does go red when the input actually changes, and the tree restored
clean afterwards.

Scope left to #85: REUSE.toml:18, the redundant src/lib/LibCodeGen.sol
annotation, is untouched here. This PR only removes line 6 of the same list.

claude added 2 commits August 16, 2026 18:39
No snapshot is taken anywhere: neither this repo's workflows nor the rainix
reusables they call run `forge snapshot`. The tracked `.gas-snapshot` was
0 bytes, and `REUSE.toml` and `.soldeerignore` each carried an entry to service
it.

`foundry.toml` kept a commented-out optimizer block whose rationale describes
settings that are not in force, under a comment claiming the settings that are
in force exist for snapshots. The settings stay -- they determine the bytecode
hash pinned in `src/generated/` -- and the comment now says that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The claim it made is false for this repo: `src/generated/CodeGennable.sol` is
generated from an empty contract, and regenerating under `optimizer_runs = 200`
and again under `optimizer = false` leaves the pinned `BYTECODE_HASH` unchanged.
The settings stand on their own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Aug 16, 2026
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b45446a4-f0ba-45be-864f-ff16f2b45dfe

📥 Commits

Reviewing files that changed from the base of the PR and between 47073e7 and db22ba7.

📒 Files selected for processing (4)
  • .gas-snapshot
  • .soldeerignore
  • REUSE.toml
  • foundry.toml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thedavidmeister
thedavidmeister merged commit 297ad1e into main Aug 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dead gas-snapshot apparatus: an empty tracked .gas-snapshot plus three config entries servicing it, and no snapshot taken anywhere

2 participants