Skip to content

chore(pragma): pin the three concrete .sol files to =0.8.25 - #23

Open
thedavidmeister wants to merge 3 commits into
mainfrom
2026-07-29-issue-22-pin-concrete-pragmas
Open

chore(pragma): pin the three concrete .sol files to =0.8.25#23
thedavidmeister wants to merge 3 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 mocks. All three concrete .sol files in
this repo floated ^0.8.25. They now pin exactly.

file declares before after
src/lib/LibRainDeploy.sol library ^0.8.25 ^0.8.25untouched
test/src/lib/LibRainDeploy.t.sol contract ^0.8.25 =0.8.25
test/src/lib/MockDeployable.sol contract ^0.8.25 =0.8.25
test/src/lib/MockReverter.sol contract ^0.8.25 =0.8.25

LibRainDeploy.sol is the one file the convention exists to protect. It is a
library, downstream soldeer consumers compile it from source, and a hard pin
there would break a consumer sitting on a different 0.8.x. It is not in this
diff at all.

The pin is not cosmetic, and the issue's premise was wrong

#22 predicted that "an exact pin either compiles under the repo's configured
solc or it does not, so this is a change whose correctness the toolchain settles
directly". It compiles fine — and it still breaks seven tests. That is worth
spelling out, because it is the whole substance of this PR.

^0.8.25 does not mean 0.8.25. It means "0.8.25 or newer", and forge resolves
it to the newest solc the rainix toolchain ships. On main that is 0.8.35:

$ forge build          # on main, unmodified
Compiling 23 files with Solc 0.8.35

=0.8.25 forces 0.8.25 for the whole compilation unit, which changes
MockDeployable's creation code. The Zoltu factory
(0x7A0D94F55792C434d74a40883C6ed8545E406D12) is a CREATE2 proxy with a
zero salt — its bytecode ends ...80368234f5..., and f5 is CREATE2 with
salt taken as zero from the stack — so the address it deploys to is a pure
function of the creation code it is handed. Different compiler, different
bytecode, different address.

The test file hardcoded that address in 13 places and its code hash in 5. With
the pragma change alone, seven tests fail:

Suite result: FAILED. 19 passed; 7 failed
[FAIL: assertion failed: 0x1fa1bBf9…  != 0xC24016f2…] testDeployZoltu()
[FAIL: UnexpectedDeployedAddress(0xC24016f2…, 0x1fa1bBf9…)] testDeployAndBroadcastHappyPath()
… and 5 more, every one reporting the same substituted address

So the constants are updated alongside the pragmas. They are not guessed —
two independent derivations agree exactly:

value
address, from live fork execution 0x1fa1bBf9Cf73B1aCCc1a3D9de5896E81Cd567854
address, from the CREATE2 formula via cast create2 --deployer 0x7A0D94F5… --salt 0x00…00 --init-code <0.8.25 initcode> 0x1fa1bBf9Cf73B1aCCc1a3D9de5896E81Cd567854
code hash, cast keccak <0.8.25 deployedBytecode> 0x6ea525f6523fe148810254f04b9a74a379f59ca0f3a7fa83db90b691c78cc299

Note which direction this cuts. Before this PR those constants silently track
whichever solc rainix happens to ship — the suite would have gone red on its own
the next time rainix bumped solc, with no commit to blame. The exact pin is what
makes them stable. This PR is not just tidying a caret; it closes a latent
time-bomb in the test suite.

Two consequences a reviewer should rule on rather than skim:

  1. The suite now exercises the library at 0.8.25, its declared floor, rather
    than at whatever is newest. Testing the declared minimum is defensible and is
    what the convention implies, but it is a real change in what is covered.
  2. The two magic constants are still hardcoded, now 18 times. Deriving them from
    type(MockDeployable).creationCode plus the CREATE2 formula would remove the
    duplication and the compiler dependence permanently. That is a test-oracle
    refactor, deliberately not bundled into a pragma sweep — same reason All three concrete .sol files float their pragma; only the library is correct #22
    itself gave for not sweeping three files into fix(deploy): report the zero address when the Zoltu factory call fails #20. Happy to open it
    separately if wanted.

Relationship to #20

#20 is open and adds a fourth concrete file,
test/src/lib/MockAddressRevertingFactory.sol, with a floating pragma. That
file is #20's to fix and is not touched here — it does not exist on main.
The two PRs touch disjoint files and land in either order. Whichever lands
second will want a rebuild, since #20 also changes LibRainDeploy.sol.

QA

  • Discriminating tests: n/a, and for a narrower reason than this PR first
    claimed. The diff against main is now exactly one line — test/src/lib/LibRainDeploy.t.sol
    moves from pragma solidity ^0.8.25; to =0.8.25;. A pragma is a
    compile-time directive with no runtime footprint, so no test can observe the
    change directly. It is also, on this base, bytecode-neutral: the three
    concrete mocks already pin =0.8.25 on main, and the test file imports
    them, so solc already had to resolve the whole compilation unit to 0.8.25.
    The pin makes that constraint explicit at the file that states it rather than
    inheriting it from an import. The evidence that nothing moved is the suite
    itself — rainix / test is green on the merge commit, including every fork
    test, with no expected-value edit anywhere in the diff.
  • Mutations applied: n/a — one compiler directive, no executable logic to
    mutate. The mutation that mattered was run on the previous head and is why
    this PR was sent back: hand-pinning the compiler moved MockDeployable's
    CREATE2 address, 7 tests failed, and the response was to hand-update 18
    literals. That version is gone. main now 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 the one file in this repo
    that was still floating. src/lib/LibRainDeploy.sol is a library and keeps
    ^0.8.25; it is absent from the diff. The remaining hardcoded address in
    testDeployZoltu is deliberately not converted to the derivation: the live
    Zoltu factory on an Arbitrum fork is the independent oracle there, and taking
    the expected value from zoltuAddress would check that function against
    itself.
  • 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 for
    test/src/lib/LibRainDeploy.t.sol and all three test/concrete/Mock*.sol,
    ^0.8.25 for src/lib/LibRainDeploy.sol only, and all four checks are green.
    The linter follow-up All three concrete .sol files float their pragma; only the library is correct #22 mentions stays out of scope.

Verification

Run with the exact rainix pin CI uses — rainix-sol-test.yaml sets
RAINIX_SHA=53e96a7d0a97d7c7c75c3b2412521324776fdac6 and invokes these same
commands:

nix develop github:rainlanguage/rainix/53e96a7d…#sol-shell -c forge soldeer install
nix develop github:rainlanguage/rainix/53e96a7d…#sol-shell -c forge build
nix develop github:rainlanguage/rainix/53e96a7d…#sol-shell -c forge test
nix develop github:rainlanguage/rainix/53e96a7d…#sol-shell -c forge fmt --check
  • forge buildCompiling 23 files with Solc 0.8.25Compiler run successful!
  • forge test26 passed; 0 failed; 0 skipped
  • forge fmt --check → clean, exit 0

The suite is fork-heavy — most tests vm.createSelectFork Arbitrum One or Base
— and it was run against live public RPC endpoints for
arbitrum/base/base_sepolia/flare/polygon. No fork test was skipped; all 26
genuinely executed, including the archive-dependent
testFindDeployBlockZoltuFactory and testIsStartBlockAtDeployBlock.

Summary by CodeRabbit

  • Tests
    • Updated the deployment library test to require Solidity compiler version 0.8.25 exactly.

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 summarizes the main change: pinning three 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 AI vetter: needs rework (code issue) 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:needs-work AI vetter: needs rework (code issue)

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