test(parity): cross-chain deployment parity pin + scheduled CI - #230
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds canonical configuration for 28 production ST0x tokens, Ethereum token-table placeholders, cross-chain Base/Ethereum parity tests, supporting library tests, and a scheduled Rainix Solidity workflow. ChangesToken configuration and cross-chain parity
Scheduled Rainix execution
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ParityTest as StoxCrossChainParityTest
participant BaseFork as Base fork
participant EthereumFork as Ethereum fork
participant TokenLegs as Production token legs
ParityTest->>BaseFork: snapshot chain legs
BaseFork->>TokenLegs: read token and beacon state
TokenLegs-->>BaseFork: return deployment state
ParityTest->>EthereumFork: snapshot chain legs
EthereumFork->>TokenLegs: read token and beacon state
TokenLegs-->>EthereumFork: return deployment state
ParityTest->>ParityTest: compare hydrated snapshots
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Checkov (3.3.8).github/workflows/rainix-sol-scheduled.yamlTraceback (most recent call last): 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 |
584bdc9 to
4424f0c
Compare
7f66d31 to
5769d2e
Compare
5769d2e to
2320571
Compare
4424f0c to
fb3493d
Compare
2320571 to
94b4454
Compare
336cf88 to
76f8116
Compare
94b4454 to
458ecd9
Compare
76f8116 to
ae562f4
Compare
458ecd9 to
f030d37
Compare
6c7b2d8 to
92666ff
Compare
92666ff to
495a253
Compare
fce7843 to
33f3b0a
Compare
495a253 to
b411c7b
Compare
RAI-1097. Automated invariant asserting every ST0x chain carries an identical deployment, re-run on every push AND daily on a schedule so parity cannot silently drift once multichain is live. StoxCrossChainParity.t.sol pins, per chain vs the Base baseline: - Core artifacts: deterministic Zoltu addresses/codehashes are already pinned per-network by StoxProdV4Test; this suite re-asserts the one non-deterministic core artifact (the per-chain authoriser clone: pinned address + shared EIP-1167 codehash). - Token instances: name/symbol/decimals of both vault legs equal the Base values per underlying; wrapped.asset() wiring; uniform authorizer() (chain's V4 clone) and owner() (chain's Safe); per-leg proxy-codehash uniformity within each chain. Cross-chain implementation parity is asserted through the beacon (proxy codehash embeds the beacon address, so it legitimately differs across chains). - Beacon lineage: all of a chain's receipt-vault proxies resolve (via the ERC-1967 beacon slot) to one beacon serving the deterministic V4 impl, owner principal-mapped from Base via the ChainPrincipals table. - Role parity: assertExpectedGrants(clone, chain principals) — same structure, per-chain addresses. - Base V2 corruption carve-out encoded explicitly: clean chains must not carry any LibProdDeployV2BaseOverrides corruption-era value. - Pending-bootstrap handling is all-or-nothing: a chain is fully pending (loud log, skip) or fully hydrated (all layers asserted); partial hydration fails. LibTokenInvariants gains productionTokensEthereum() (same 20 underlyings, address(0) placeholders until the bootstrap pin PR). rainix-sol-scheduled.yaml re-runs the full suite daily + on dispatch — on-chain drift between pushes was previously invisible to CI. View-call assumptions validated against live Base (wrapped.asset(), metadata reads, shared beacon slot 0xea08..., uniform proxy codehash). Red until the V4 stack + Ethereum bootstrap execute (Base V4 clone pin gate), same draft pattern as the stack it sits on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
…ash parity Follows the LibChainPrincipals removal: the token-owner Safe and the grant map are shared across chains, so the parity suite drops `mapPrincipal` (now an identity) and the per-chain principal plumbing. - `assertProductionState` uses the new `(tokens, authoriser)` signature. - `baseClone` reads from `LibProdAuthoriserClones.STOX_PROD_AUTHORISER_V4_CLONE_BASE` (its new home) instead of the removed generated alias, so both chains' clones now read from the same hand-maintained lib. - Beacon-owner parity is a straight equality against Base's owner (shared Safe), not a principal mapping. - Add EXPLICIT cross-chain impl-codehash assertions (Josh): the authoriser clone codehash (EIP-1167 over the shared impl) and the receipt-vault beacon impl codehash must match Base's — so the only per-chain-unique artifacts (clone address, token addresses) still resolve to identical implementations everywhere. Still red-until-executed at the Base clone placeholder guard (unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
… compare The token-owner Safe is now a per-chain deploy artifact, so the parity suite: - folds the Safe pin into isFullyPending / isFullyHydrated (a chain is pending until its Safe, clone AND tokens are all pinned); - resolves each chain's Safe inside assertProductionState (by chain id) and, on Ethereum, compares the live Safe's owner SET + threshold DIRECTLY to Base's live Safe (order-insensitive) — matching Base "in every way that matters" against Base's actual current state, not only the shared pins; - corrects the beacon-owner assertion's rationale: the beacon owner is the chain-agnostic deployer (BEACON_INITIAL_OWNER), shared across chains — the Safe owns the vaults, not the beacon. Still red-until-executed at the Base clone placeholder guard (unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
Replace the all-or-nothing isFullyPending/isFullyHydrated (and the hard "Base clone placeholder" red guard) with nested per-artifact gating: each chain asserts only the legs whose pins are live and skips placeholder legs with a loud PARITY PENDING log; cross-chain comparisons gate on both sides carrying the leg. Legs nest by dependency: - Safe leg (Safe): policy matches Base. - Authoriser leg (clone; grant map also needs Safe): clone codehash + grant map — assertable as soon as the clone is up, NOT blocked on tokens. - Token leg (Safe + clone + full table): ownership + sole authoriser + config. So the whole multichain stack goes green before any chain is bootstrapped, and each pin PR turns its leg (and cross-chain comparison) on. A PARTIALLY hydrated token table is the one hard failure (operator error). Verified: parity passes green with PENDING logs for Base clone + Ethereum Safe/clone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
…g w/ Base) Write productionTokensEthereum() as explicit per-token rows mirroring productionTokensBase() (same underlyings, same order, all addresses address(0) placeholders) instead of a derive-from-Base loop. Consistent cross-chain formatting, and the token pin PR becomes a clean per-row literal swap. Add a pure guard test that the two tables stay row-aligned on the underlying key (the derivation guarantee is gone now that the table is explicit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
Every cross-chain comparison is gated on both chains carrying the leg, so a suite that skipped all of them is green in precisely the same way as one that checked all of them. Base is fully bootstrapped, so a pending leg there is not a pending bootstrap -- it is the placeholder detection reading a live pin as a placeholder, which silently disables the whole test. Asserted directly. Past the deadline a still-pending Ethereum leg stops being "not yet" and becomes a chain nothing asserts anything about, so it forces the same operator choice as the beacon-owner migration pin: land the pins, move the deadline, or delete the invariant deliberately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12e722f to
e822482
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In @.github/workflows/rainix-sol-scheduled.yaml:
- Around line 18-23: Add RPC_URL_ETHEREUM_FORK to the secrets forwarded by the
scheduled Rainix workflow, matching the existing push-based wrapper and
preserving the other RPC secret mappings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 53d1baee-3186-4478-940f-e894d1bec9e7
📒 Files selected for processing (6)
.github/workflows/rainix-sol-scheduled.yamlsrc/lib/LibProdTokenConfig.solsrc/lib/LibTokenInvariants.soltest/src/concrete/deploy/StoxCrossChainParity.t.soltest/src/lib/LibProdTokenConfig.t.soltest/src/lib/LibTokenInvariants.t.sol
|
@coderabbitai review |
✅ Action performedReview finished.
|
The schedule exists to catch on-chain drift between pushes, and the parity suite it runs forks Ethereum. Without this secret the daily job cannot reach the chain the check is about, so the one thing the schedule was added for is the one thing it could not do. The push-based caller in this repo already forwards all six. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Reviewed f2db6de: approve Run 29979066122 leaves one test failure — This closes a gap nothing else covered: drift introduced directly on-chain between pushes is invisible to push-triggered CI. A role grant, a beacon upgrade, an authoriser swap — none of it touches the repo, so nothing runs. The daily schedule is the only thing that would catch it.
Changes made during review:
One thing deliberately left outstanding. |

RAI-1097. An automated invariant asserting every ST0x chain carries an identical
deployment, re-run on every push and daily on a schedule, so parity cannot
silently drift once multichain is live.
Push-triggered CI only catches drift that arrives with a code change. State
drift introduced directly on-chain between pushes — a role grant, a beacon
upgrade, an authoriser swap — is invisible to it.
rainix-sol-scheduled.yamlcloses that gap: the prod-state pins and the parity suite run against live chain
state daily regardless of repo activity. The cron is off the hour (
17 6 * * *)to dodge GitHub's scheduled-run thundering herd, which delays or drops
on-the-hour jobs.
What
StoxCrossChainParity.t.solpins, per chain against the Base baselinepinned per-network by
StoxProdV4Test; this suite re-asserts the onenon-deterministic core artifact, the per-chain authoriser clone (pinned
address + shared EIP-1167 codehash).
values per underlying;
wrapped.asset()wiring; uniformauthorizer()(thechain's V4 clone) and
owner()(the chain's Safe); per-leg proxy-codehashuniformity within each chain. Cross-chain implementation parity is asserted
through the beacon, since a proxy codehash embeds the beacon address and so
legitimately differs across chains.
ERC-1967 beacon slot, to one beacon serving the deterministic V4 impl, owner
principal-mapped from Base through the
ChainPrincipalstable.assertExpectedGrants(clone, chain principals): samestructure, per-chain addresses.
no
LibProdDeployV2BaseOverridescorruption-era value.LibTokenInvariantsgainsproductionTokensEthereum()— the same 28underlyings as Base,
address(0)placeholders until the token pin PR.testEthereumTokenTableMirrorsBaseUnderlyingsasserts the two tables stay thesame length with the same underlyings row-for-row, so the mirror cannot rot when
Base gains tokens.
Pending legs, and proving the suite ran
A chain's legs arm independently as its pins hydrate: each pending leg logs
PARITY PENDINGloudly and is skipped, and every cross-chain comparison isgated on both sides carrying the leg. That is what lets the stack merge green
before the bootstrap completes.
The hazard in that design is that a suite which skipped every comparison is
green in exactly the same way as one that checked every comparison. Two
assertions separate the signals:
pending leg there is not a pending bootstrap — it is the placeholder detection
misreading a live pin, which silently disables the whole test.
ETHEREUM_PARITY_DEADLINE(2026-10-01), a still-pending Ethereumleg fails. Before it, pending is the expected mid-bootstrap state; after it, a
leg that never armed is a chain nothing asserts anything about. Same forcing
function as the beacon-owner migration pin: land the pins, move the deadline,
or delete the invariant deliberately.
View-call assumptions were validated against live Base (
wrapped.asset(),metadata reads, the shared beacon slot, uniform proxy codehash).
Summary by CodeRabbit