Match each frozen record file to a released suite of its own - #241
Conversation
`checkFrozenSnapshotsReleased` asked, for each file in the frozen record, whether SOME declared released suite derives the address that file records. The derived address is a pure function of the creation code, so that question cannot tell two tags that froze identical bytes apart -- and identical bytes are not exotic: a release that changes only dependencies, metadata or documentation compiles to the same creation code, which is how `0_1_7`/`0_1_8` and `0_1_10`/`0_1_11` came to be two such pairs in this repo`s own record. Either half of such a pair could therefore be dropped from `releasedSuites()` and its own record file would go on being "declared" by its twin: the dropped key stops being dispatchable, the dropped entry`s frozen dependency list stops being checked, and the whole suite stays green. That is the state the record check exists to make impossible. The match is now one for one: N record files recording an address need N released suites deriving it. Still by derived address and by nothing else -- `DeploySuite.suite` is a free-form key on a virtual declaration, so matching on a tag spelled inside it would make this pass or fail on the shape of a string and would refuse every consumer that spells its keys some other way. Counting needs no convention at all. The inequality runs one way only. More declarations than record files at an address stays passing, because a release deployed before a repo adopted this machinery has no frozen record and never will. `FrozenSnapshotsOutnumberReleases` is new and is raised only where `FrozenSnapshotNotReleased` never could be: there ARE matching entries and there are not enough of them, so a reader told "not released" would go looking for an entry that is already there. Reported against a consumer in rainlanguage/rain.factory.deploy#33. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (2)
💤 Files with no reviewable changes (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. WalkthroughThe snapshot check now enforces one-to-one accounting between frozen record files and released suites that derive the same deployed address. It adds an error for excess records and tests twin releases with shared creation code. ChangesFrozen snapshot accounting
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
checkFrozenSnapshotsReleasedasked, for each file in the frozen record, whether SOMEdeclared released suite derives the address that file records. The derived address is a
pure function of the creation code, so that question cannot tell two tags that froze
identical bytes apart — and identical bytes are not exotic: a release that changes only
dependencies, metadata or documentation compiles to the same creation code. This repo's
own record holds two such pairs (
0_1_7/0_1_8and0_1_10/0_1_11), and so doesevery other deploy repo in the org.
Either half of such a pair could be dropped from
releasedSuites()while its own recordfile went on being "declared" by its twin: the dropped key stops being dispatchable, its
frozen dependency list stops being checked, and the whole suite stays green. That is the
state the record check exists to make impossible.
The match is now one for one: N record files recording an address need N released suites
deriving it. Still by derived address and by nothing else —
DeploySuite.suiteis afree-form key on a virtual declaration, so matching on a tag spelled inside it would make
this pass or fail on the shape of a string and would refuse every consumer that spells
its keys some other way. Counting needs no convention at all.
The inequality runs one way only: more declarations than record files at an address stays
passing, because a release deployed before a repo adopted this machinery has no frozen
record and never will.
FrozenSnapshotsOutnumberReleasesis new and is raised only whereFrozenSnapshotNotReleasednever could be: there ARE matching entries and there are notenough of them.
No breaking change to
DeploySuite— struct, fields, key alphabet andreleasedSuites()are untouched. The only API addition is the new error.Known limit, documented in the source: an entry RENAMED rather than dropped is still
not caught — the counts are unchanged. Nothing in a
DeploySuitesays which tag it wascut from.
Consumers
Checked against the new rule rather than assumed. All satisfy it as they stand; a bump
needs no source edit.
rain.factory.deploy— records0_1_1/0_1_9/0_1_10, declares three entries; twinpair
0_1_9/0_1_10is 2 records / 2 entries → passes, and issue docs: say what findDeployBlock, isStartBlock and the deploy actually do #33's mutants areboth killed on bump.
rain.math.float.deploy— 4 record files, 4 entries; both contracts are twin pairsacross those tags → 2/2 at each address → passes.
rain.tofu.erc20-decimals.deploy— 1 record file, 1 entry → passes.rain.deployitself — 8 record files, 8 entries, two twin pairs per contract → passes.A consumer whose record holds more files recording one address than its
releasedSuites()names entries deriving it now goes red. For a repo generating its declaration with
writeReleasedSuitesLib, that state IS the defect and the fix is to regenerate.QA
testFrozenSnapshotTwinReleaseDroppedFromTheDeclarationRevertsfails on base — written first with a deliberately loose
vm.expectRevert()so thefirst run was a real assertion failure rather than a compile error, and observed as
[FAIL: next call did not revert as expected]against unmodified source while itsthree siblings passed in the same run. Controls that must NOT fire:
testFrozenSnapshotTwinReleasesBothDeclaredPasses,testFrozenSnapshotMoreDeclarationsThanRecordFilesPasses(pins the one-wayinequality), and
testTwinReleasesFrozeTheSameCreationCode, which asserts the fixture'spremise so it cannot go vacuous.
address-registry@0_1_11from the generated
src/lib/LibAddressRegistryReleased.sol(array 4→3, entry removed),the M45 shape from the issue. Pre-fix source:
testEveryFrozenSnapshotIsReleasedPASSES (mutant survives). Fixed source, same mutation: FAILS with
FrozenSnapshotsOutnumberReleases("src/generated/0_1_11/AddressRegistry.sol", 0xDBb7Cf1cba967d2D577045E138eEd359675a6fa7, 2, 1).Mutation reverted; tree clean.
independent of the declaration being checked. The twin fixture uses the REAL append-only
files
src/generated/0_1_10/AddressRegistry.soland0_1_11/AddressRegistry.solratherthan a synthetic pair, so the premise is the repo's actual history.
declaration be caught regardless of whether another release shares its bytecode, without
breaking consumers that declare arbitrary keys. Both covered. The issue suggested keying
on the tag; that is deliberately NOT what this does, and the reason is in the body.
Suite: 490 → 494 passing from
forge cleanbuilds, with the same 76 failures before andafter, all
vm.createFork/createSelectFork: environment variable '<NETWORK>_RPC_URL' not found(fork tests needing RPC env not present on the build machine). No existing test wasweakened, changed or deleted.
forge fmt --checkandforge lint -D warningsclean.Closes rainlanguage/rain.factory.deploy#33
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests