Skip to content

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

Open
thedavidmeister wants to merge 2 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 2 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 — a pragma is a compile-time directive with no
    runtime footprint, so no test can observe ^0.8.25 versus =0.8.25 directly,
    and writing one would be theatre. What is discriminated, and was observed
    rather than assumed, is the pin's downstream effect: on main the suite is
    green at solc 0.8.35 (26/26), with the pragma change alone it is
    19 passed / 7 failed, and with the constants corrected for 0.8.25 it is
    green again (26/26). That three-way comparison is the evidence the pin is
    load-bearing; the existing seven tests are what killed the naive version of
    this change.
  • Mutations applied: n/a — no mutation matrix is manufactured for this diff.
    Every changed byte is either a compiler directive or a compiler-derived
    constant, neither of which is executable logic to mutate. The nearest honest
    equivalent already happened by accident and is reported above: changing the
    pragma alone was the mutation, and seven existing tests killed it.
  • Oracle: independent of the implementation on both axes. For the convention,
    the org standard (^ library/abstract, = concrete including test mocks),
    which pragma solidity =0.8 satisfies in ~1126 files across rainlanguage.
    For the constants, the EVM CREATE2 address formula evaluated by
    cast create2 from the factory address, a zero salt and the 0.8.25 creation
    code — computed without running deployZoltu at all — plus cast keccak over
    the deployed bytecode for the code hash. Both match what live fork execution
    produces.
  • Category check: All three concrete .sol files float their pragma; only the library is correct #22 asks for three things — pin the three concrete files,
    leave LibRainDeploy.sol alone, and leave the suite green. All three covered:
    the three files pin at =0.8.25, src/lib/LibRainDeploy.sol is absent from
    the diff (git diff main --name-only lists only the three test files), and
    the full suite passes. The issue's #141 follow-up (a linter so this class is
    caught by code) is explicitly out of scope for this local cleanup and is not
    attempted.

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 deployment verification expectations to reflect the latest deterministic deployment results.
    • Expanded coverage alignment for successful deployments, skipped deployments, missing dependencies, and expected validation failures.
  • Chores
    • Standardized test compilation on Solidity 0.8.25 to improve reproducibility and consistency.

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ca0b6011-9b57-430c-ac06-d2c06f8ad143

📥 Commits

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

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

Walkthrough

Concrete Solidity test contracts are pinned to compiler version 0.8.25, and deployment tests update deterministic address and code-hash expectations across success, skip, and revert scenarios.

Changes

Deployment test consistency

Layer / File(s) Summary
Pin concrete test compilers
test/src/lib/LibRainDeploy.t.sol, test/src/lib/MockDeployable.sol, test/src/lib/MockReverter.sol
Concrete test contracts now require Solidity =0.8.25.
Refresh deterministic deployment expectations
test/src/lib/LibRainDeploy.t.sol
Deployment, broadcast, already-deployed, missing-dependency, and mismatch-revert assertions use the updated address and code-hash constants.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

  • rainlanguage/rain.deploy#13 — Its deployment and verification flow changes directly affect the constants and revert expectations updated in these tests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: pinning three concrete Solidity files to =0.8.25.
Linked Issues check ✅ Passed The PR matches issue #22 by pinning the three concrete files, leaving the library unchanged, and updating only the affected deploy constants.
Out of Scope Changes check ✅ Passed No unrelated code changes are evident; the address and code-hash updates are necessary fallout from the pragma pin.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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:reject 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:reject AI vetter: needs rework (code issue) and removed human:reject Human reviewer: needs rework labels Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:reject 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