Skip to content

chore(pragma): pin every concrete .sol file to =0.8.25 - #23

Merged
thedavidmeister merged 5 commits into
mainfrom
2026-07-29-issue-22-pin-concrete-pragmas
Aug 14, 2026
Merged

chore(pragma): pin every concrete .sol file to =0.8.25#23
thedavidmeister merged 5 commits into
mainfrom
2026-07-29-issue-22-pin-concrete-pragmas

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #22

The convention is ^ for library and abstract files, = for concrete
contracts including concrete test contracts. This applies it to every
concrete .sol in the repo.

What this pins

Every .t.sol under test/src/ declares a plain contract
contract LibRainDeployTest is Test,
contract AddressRegistryDeployChainTest is AddressRegistryDeploySuites, RainDeployVerifyChain,
and so on for all thirteen. Declarations were read from source, not inferred
from paths. All thirteen move ^0.8.25=0.8.25, one line each:

test/src/abstract/RainDeployBroadcast.t.sol
test/src/abstract/RainDeploySuitesBase.t.sol
test/src/abstract/RainDeployVerifyChain.t.sol
test/src/abstract/RainDeployVerifyChainCandidate.t.sol
test/src/abstract/RainDeployVerifySnapshot.t.sol
test/src/concrete/AddressRegistryDeployChain.t.sol
test/src/concrete/AddressRegistryDeploySnapshot.t.sol
test/src/concrete/AddressRegistryGet.t.sol
test/src/concrete/AddressRegistryRegister.t.sol
test/src/lib/GeneratedSnapshotShape.t.sol
test/src/lib/LibAddressRegistry.t.sol
test/src/lib/LibRainDeploy.t.sol
test/src/lib/LibRainDeploySnapshot.t.sol

Thirteen files, thirteen lines. No reformatting, no reordering, no other edit of
any kind.

It closes #22's category, not its three-row table

#22 tabulated three files because three was all the repo had when it was filed.
Two of those were pinned and moved to test/concrete/ by #21. #26 then landed
twelve more concrete contract declarations under test/src/, every one of them
floating.

The clause that binds is #22's own check — "Every concrete contract in this repo
pins exactly."
That is a property, not a list. Pinning only the single file #22
happened to name would have closed the issue with the property false in twelve
places, and CodeRabbit's linked-issues check flagged exactly that. So the sweep
covers the category as main stands today.

What stays floating, and why

file(s) declares why the caret is correct
src/lib/*.sol library downstream soldeer consumers compile these from source; a hard pin breaks a consumer sitting on another 0.8.x
src/abstract/*.sol, test/abstract/ExampleDeploySuites.sol abstract contract inherited, never deployed on their own — same reason
src/interface/IAddressRegistryV1.sol interface same
src/generated/candidate/AddressRegistry.sol nothing see below

src/lib/LibRainDeploy.sol is the file the convention exists to protect, and it
is absent from the diff.

The generated record is the one that needs spelling out. It declares no contract
at all — four file-level constants and a header — so it is not a concrete
contract in the first place. It is also autogenerated by rain-sol-codegen, and
the caret is the text that generator emits:
test/src/lib/LibRainDeploySnapshot.t.sol:584 and :627 assert
"pragma solidity ^0.8.25;\n\n" as the expected generated output, so pinning it
would fail the suite. It keeps the caret deliberately. Only line 3 of each file
in the diff was edited, for exactly this reason — those two string literals sit
in a file this PR does touch.

Bytecode-neutral, and no longer an argument about 0.8.35

An earlier revision of this PR argued that ^0.8.25 floats to whatever solc
rainix ships — 0.8.35 at the time — so pinning moved MockDeployable's creation
code, moved its zero-salt CREATE2 address, and broke seven tests that hardcoded
it. That argument is dead, and every claim downstream of it goes too. #26
added to foundry.toml:

solc = "0.8.25"
optimizer = true
optimizer_runs = 100000
evm_version = "cancun"

The compiler is now pinned by build config, so every file in the repo already
compiles at 0.8.25 whatever its pragma says. Measured on unmodified main:
forge build --forceCompiling 64 files with Solc 0.8.25. Identical on this
head. No creation code moves, no address moves, no code hash moves, and not one
literal is touched anywhere in the diff.

What the pragma pin still buys is that the constraint is declared at the file
that has it, rather than inherited from a build setting three directories away.
A future solc bump in foundry.toml then fails loudly on the concrete files
whose bytecode is pinned under src/generated/, instead of silently recompiling
them at a new compiler.

QA

  • Discriminating tests: n/a. A pragma is a compile-time directive with no
    runtime footprint, so no test can observe it directly. It is also
    bytecode-neutral on this base — foundry.toml pins solc = "0.8.25", so all
    thirteen files already compiled at 0.8.25 and the pin makes explicit what an
    import graph and a build setting were enforcing implicitly. The evidence that
    nothing moved is the suite run as a differential:
    forge test --no-match-contract Chain gives 93 passed / 26 failed on this
    head and 93 passed / 26 failed on bare origin/main — same counts, same
    test names. All 26 are vm.createSelectFork: environment variable ARBITRUM_RPC_URL / BASE_RPC_URL not found: no .env in the run, so
    environmental, and all of them in LibRainDeployTest.
  • Mutations applied: n/a — thirteen compiler directives, no executable logic to
    mutate. The mutation that mattered ran on the head rejected on 2026-07-30:
    hand-pinning moved the CREATE2 address, 7 tests failed, and the response was to
    hand-update 18 literals. That head is gone. main derives the address and
    codehash from type(MockDeployable).creationCode via
    LibRainDeploy.zoltuAddress (landed in fix(deploy): check the derived Zoltu address before skipping a network #21), so a compiler bump recomputes the
    expected value instead of falsifying it and no literal in this diff can go
    stale.
  • Oracle: the org convention — ^ for library and abstract files, = for
    concrete ones including test contracts — applied to every concrete .sol in
    the repo rather than to the three All three concrete .sol files float their pragma; only the library is correct #22 listed, because the issue's check clause
    states the property and the table only illustrated it.
  • Category check: All three concrete .sol files float their pragma; only the library is correct #22 asks that the repo's concrete .sol files pin exactly,
    that LibRainDeploy.sol be left alone, and that the suite stay green. All
    three hold on this head. git grep 'pragma solidity' shows =0.8.25 on every
    concrete contract — both script/*.sol, src/concrete/AddressRegistry.sol,
    all seven files under test/concrete/, and all thirteen
    test/src/**/*.t.sol — and ^0.8.25 only on libraries, abstracts, the
    interface, and the generated record. The linter follow-up All three concrete .sol files float their pragma; only the library is correct #22 mentions stays
    out of scope.

Verification

All via nix develop -c, on this head with main (8d1e5fd) merged in:

gate result
forge build --force Compiling 64 files with Solc 0.8.25Compiler run successful!
forge test --no-match-contract Chain 93 passed, 26 failed — all 26 missing-RPC, identical to main
slither . 44 contracts, 100 detectors, 0 results
forge fmt --check clean, exit 0
reuse lint compliant, 53 / 53 files

The 26 failures are environmental and pre-existing: there is no .env, and every
one is a vm.createSelectFork env-var error rather than an assertion. The same
command on bare origin/main gives the same 93 / 26.

One thing that surfaced during that run and is not this PR's to fix: CLAUDE.md
describes forge test --no-match-contract Chain as the fork-free snapshot gate,
"nothing reachable from those contracts forks anything". LibRainDeployTest
forks and is not matched by that filter, and it is where all 26 failures come
from. Worth an issue against the docs or the test layout, separately.

Relationship to #20

#20 is still open and adds a fourteenth concrete file,
test/concrete/MockAddressRevertingFactory.sol. It already pins =0.8.25
there, so there is nothing left for this PR to sweep on its behalf. The two
touch disjoint files and land in either order; whichever lands second wants a
rebuild, since #20 also changes src/lib/LibRainDeploy.sol.

claude added 2 commits July 29, 2026 18:42
The convention is `^` for library and abstract files, `=` for concrete
contracts including concrete test mocks. All three concrete .sol files in
this repo floated `^0.8.25`; they now pin exactly.

`src/lib/LibRainDeploy.sol` is a library that downstream soldeer consumers
compile, so it keeps `^0.8.25` — a hard pin there would break a consumer on
a different 0.8.x.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Zoltu factory is a CREATE2 proxy with a zero salt, so the address it
deploys to is a pure function of the creation code. Pinning the concrete
test files to =0.8.25 moves the whole compilation unit from solc 0.8.35
(what ^0.8.25 floats to in the rainix toolchain) down to 0.8.25, which
changes MockDeployable's creation code and therefore its deployed address
and code hash.

Address 0xC24016f2..0xC24016f209562fc151e5Ab7F88694ED5775feb36 becomes
0x1fa1bBf9Cf73B1aCCc1a3D9de5896E81Cd567854 and the code hash becomes
0x6ea525f6523fe148810254f04b9a74a379f59ca0f3a7fa83db90b691c78cc299.

Both values are confirmed twice over: cast create2 derives the address
from the CREATE2 formula given the factory, a zero salt and the 0.8.25
creation code, and live fork execution deploys to exactly that address.

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

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The deployment test pragma changes from ^0.8.25 to =0.8.25.

Changes

Deployment test consistency

Layer / File(s) Summary
Pin deployment test compiler
test/src/lib/LibRainDeploy.t.sol
The test now requires Solidity compiler version 0.8.25 exactly.

Estimated code review effort: 1 (Trivial) | ~2 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The change pins only LibRainDeploy.t.sol, but issue #22 requires exact pragmas on all three concrete Solidity files. Also change MockDeployable.sol and MockReverter.sol to pragma =0.8.25, then verify the build and full test suite.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The single pragma change is within issue #22 scope and does not introduce unrelated code or formatting changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: pinning concrete Solidity files to compiler version 0.8.25.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-07-29-issue-22-pin-concrete-pragmas

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

Copy link
Copy Markdown
Contributor Author

👤 human
Ruled d6fcdf4: reject — Right convention, wrong consequence accepted. Pinning the test file to =0.8.25 forces the whole test compilation to 0.8.25, and LibRainDeploy.sol correctly floats ^0.8.25 — so after this the suite exercises the library at 0.8.25 while consumers compiling it anywhere in 0.8.x get something the tests never ran. That is a real coverage loss traded for determinism, and it should not be bought by hand-updating 18 literals. The 18 constant updates are the tell, not the fix: MockDeployable's creation code changed because solc changed, its zero-salt CREATE2 address moved with it, and the test file hardcodes that address 22 times and its codehash 7 times. Those are derived constants — the output of a documented formula with a canonical derivation — so the answer is to DERIVE them, not to re-pin them at a new compiler and wait for the next bump. Note what the two directions cost: before this PR those constants silently tracked whichever solc rainix ships, so the suite would have gone red on its own at the next bump with no commit to blame; after it, they are correct for exactly one compiler and wrong for every other. Deriving makes both problems go away, because the expected value recomputes from the creation code whatever solc is in use. The derivation already exists in this repo: PR #21 adds zoltuAddress(bytes memory creationCode) doing precisely the zero-salt CREATE2 computation, and the codehash is keccak256 over the deployed bytecode. So the shape is: land #21 first, then pin the pragmas and replace the hardcoded literals with zoltuAddress(type(MockDeployable).creationCode) everywhere the literal is not itself the independent oracle. Keep the literals ONLY in testZoltuAddressMatchesFactoryDeploy, where the expected value must NOT come from the function under test or the test verifies it against itself. Everything else in this PR was done well and should survive: LibRainDeploy.sol correctly untouched, the full 26-test suite run against live RPCs with no fork test skipped, and the discovery that ^0.8.25 resolves to 0.8.35 rather than 0.8.25 stated plainly instead of buried.

@thedavidmeister thedavidmeister added the human:needs-work Human reviewer: needs rework label Jul 30, 2026
@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Correcting the emphasis of the reject above, because as written it could send the rework the wrong way.

The defect is the magic numbers. Not the compiler version.

My note led with the suite exercising the library at 0.8.25 rather than 0.8.35, and framed that as the headline. That was wrong. It is not a defect of this PR — it is the ordinary consequence of the convention itself: concrete files pin exactly, libraries float, so tests always compile a library at the pinned version while consumers may compile it anywhere in 0.8.x. Every repo in the org is already in that state. This PR does not introduce it, and the pragma pins are correct and should stay.

Read literally, my note invited the rework to preserve 0.8.35 coverage by un-pinning — the opposite of what the convention asks. Do not do that.

The actual finding, which stands and is the whole reason this is a reject: the test file hardcodes MockDeployable's Zoltu address 22 times and its codehash 7 times. Those are derived constants — the output of a documented formula with a canonical derivation — and this PR responded to solc moving by hand-updating 18 of them at a new value. That leaves the same defect, re-pinned.

Derive them instead, and the compiler question stops existing: the expected value recomputes from the creation code under whatever solc is in use, so neither a pragma pin nor a future rainix bump can falsify it. zoltuAddress(bytes memory creationCode) on PR #21 is exactly that derivation, and the codehash is keccak256 over the deployed bytecode.

So: land #21 first, keep the pragma pins from this PR, and replace every hardcoded literal with the derivation — except in testZoltuAddressMatchesFactoryDeploy, where the expected value must not come from the function under test or the test verifies it against itself. Those literals are the independent oracle and are the only ones that should survive.

@thedavidmeister thedavidmeister added ai:needs-work Needs rework — the producer's inbox (vetter verdict or human ruling) and removed human:needs-work Human reviewer: needs rework labels Jul 30, 2026
Resolves the conflict in test/src/lib/LibRainDeploy.t.sol by taking main's
side in all nine hunks. #21 landed `LibRainDeploy.zoltuAddress`, and main
already derives MockDeployable's Zoltu address and codehash from
`type(MockDeployable).creationCode` instead of hardcoding them, and moved
the concrete mocks to test/concrete/. The hand-updated literals this branch
carried are exactly what the send-back rejected, so none of them survive.

What survives from this branch is the pragma pin itself: the test file is a
concrete contract and pins `=0.8.25`. The three mocks already pin on main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/src/lib/LibRainDeploy.t.sol (1)

305-309: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Move the hardcoded address to the independent-oracle test.

testDeployZoltu still checks Line 310 with a compiler-dependent literal. Use mockDeployableAddress() in that test. Keep the literal in testZoltuAddressMatchesFactory, where it can independently validate both the factory result and LibRainDeploy.zoltuAddress. This avoids manual updates when compiler settings or MockDeployable bytecode changes.

Suggested test split
-        // Pinned literal, deliberately not `mockDeployableAddress()`.
-        // The live factory on the fork is the oracle here.
-        assertEq(deployed, 0x1fa1bBf9Cf73B1aCCc1a3D9de5896E81Cd567854);
+        assertEq(deployed, mockDeployableAddress());

     function testZoltuAddressMatchesFactory() external {
         vm.createSelectFork(LibRainDeploy.ARBITRUM_ONE);
+        address deployed = this.externalDeployZoltu(type(MockDeployable).creationCode);
+        assertEq(deployed, 0x1fa1bBf9Cf73B1aCCc1a3D9de5896E81Cd567854);
         assertEq(
             LibRainDeploy.zoltuAddress(type(MockDeployable).creationCode),
-            this.externalDeployZoltu(type(MockDeployable).creationCode)
+            deployed
         );

Also applies to: 523-540

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/src/lib/LibRainDeploy.t.sol` around lines 305 - 309, Update
testDeployZoltu to use mockDeployableAddress() instead of the compiler-dependent
hardcoded address when checking the deployed result. Keep the pinned literal
exclusively in testZoltuAddressMatchesFactory so that test independently
compares the factory result with LibRainDeploy.zoltuAddress, including the
corresponding assertions in the later referenced test section.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@test/src/lib/LibRainDeploy.t.sol`:
- Around line 305-309: Update testDeployZoltu to use mockDeployableAddress()
instead of the compiler-dependent hardcoded address when checking the deployed
result. Keep the pinned literal exclusively in testZoltuAddressMatchesFactory so
that test independently compares the factory result with
LibRainDeploy.zoltuAddress, including the corresponding assertions in the later
referenced test section.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 950a7aec-ef78-48f4-bc56-a9caf9c2b5c7

📥 Commits

Reviewing files that changed from the base of the PR and between d6fcdf4 and f9944e7.

📒 Files selected for processing (1)
  • test/src/lib/LibRainDeploy.t.sol

@thedavidmeister

Copy link
Copy Markdown
Contributor Author

🤖 ai:producer
QA-block repair: replaced QA-GUIDE section 8's evidence block in the PR body via pr-review-report repair-qa-block. Every byte outside the ## QA section is unchanged.

thedavidmeister and others added 2 commits August 14, 2026 14:46
…enumerated

#22 enumerated three files because three was all the repo had. Its check clause
is the category: every concrete contract pins exactly, only libraries and
abstracts float. #26 landed twelve more concrete `contract` declarations under
`test/src/**.t.sol`, every one of them floating `^0.8.25`, so closing #22 on the
single file it named would have closed it with the category unmet.

Every `.t.sol` under `test/src/` declares a plain `contract` — concrete, nothing
downstream compiles it — so all thirteen now pin `=0.8.25`. Left floating, and
correctly so: `src/lib/*` libraries, `src/abstract/*` and `test/abstract/*`
abstracts, `src/interface/*`, and `src/generated/candidate/AddressRegistry.sol`,
which declares no contract at all and is emitted by rain-sol-codegen.

The two `"pragma solidity ^0.8.25;\n\n"` string literals in
LibRainDeploySnapshot.t.sol are untouched on purpose: they are the expected TEXT
of a generated file, and the generator emits a caret.

Bytecode-neutral. `foundry.toml` pins `solc = "0.8.25"` since #26, so every file
here already compiled at 0.8.25 and `forge build` reports the same 64 files at
the same compiler before and after. The pin makes the constraint explicit at the
file that states it instead of leaving it to a build setting that a future bump
would silently move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedavidmeister thedavidmeister changed the title chore(pragma): pin the three concrete .sol files to =0.8.25 chore(pragma): pin every concrete .sol file to =0.8.25 Aug 14, 2026
@thedavidmeister
thedavidmeister merged commit fc4fdc9 into main Aug 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:needs-work Needs rework — the producer's inbox (vetter verdict or human ruling)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

All three concrete .sol files float their pragma; only the library is correct

2 participants