Skip to content

test(parity): cross-chain deployment parity pin + scheduled CI - #230

Merged
thedavidmeister merged 7 commits into
mainfrom
feat/rai-1097-cross-chain-parity
Jul 23, 2026
Merged

test(parity): cross-chain deployment parity pin + scheduled CI#230
thedavidmeister merged 7 commits into
mainfrom
feat/rai-1097-cross-chain-parity

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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.yaml
closes 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.sol pins, per chain against 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() (the
    chain's V4 clone) and owner() (the chain's Safe); per-leg proxy-codehash
    uniformity 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.
  • Beacon lineage — every receipt-vault proxy on a chain resolves, via the
    ERC-1967 beacon slot, to one beacon serving the deterministic V4 impl, owner
    principal-mapped from Base through the ChainPrincipals table.
  • Role parityassertExpectedGrants(clone, chain principals): same
    structure, per-chain addresses.
  • Base V2 corruption carve-out, encoded explicitly: clean chains must carry
    no LibProdDeployV2BaseOverrides corruption-era value.

LibTokenInvariants gains productionTokensEthereum() — the same 28
underlyings as Base, address(0) placeholders until the token pin PR.
testEthereumTokenTableMirrorsBaseUnderlyings asserts the two tables stay the
same 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 PENDING loudly and is skipped, and every cross-chain comparison is
gated 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:

  • Base must report all three legs live. Base is fully bootstrapped, so a
    pending leg there is not a pending bootstrap — it is the placeholder detection
    misreading a live pin, which silently disables the whole test.
  • Past ETHEREUM_PARITY_DEADLINE (2026-10-01), a still-pending Ethereum
    leg 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

  • New Features
    • Added a canonical deploy-time configuration for 28 ST0x tokens with consistent underlying assets, names, and symbols.
    • Added an Ethereum token reference table to mirror Base for cross-chain verification.
  • Bug Fixes
    • Improved safeguards for token metadata, wrapped-to-receipt wiring, authorization clone verification, and beacon/proxy lineage parity.
  • Tests
    • Added cross-chain Base vs Ethereum parity tests with live fork validation and hydration checks.
    • Added targeted config and invariants tests to confirm Base alignment.
  • Chores
    • Added a scheduled daily CI run (and manual trigger) to execute the full SOL test suite.

@coderabbitai

coderabbitai Bot commented Jul 6, 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: 30e17b0e-3515-4d1b-a793-f791533e7038

📥 Commits

Reviewing files that changed from the base of the PR and between e822482 and f2db6de.

📒 Files selected for processing (1)
  • .github/workflows/rainix-sol-scheduled.yaml

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Token configuration and cross-chain parity

Layer / File(s) Summary
Canonical token tables and configuration
src/lib/LibProdTokenConfig.sol, src/lib/LibTokenInvariants.sol, test/src/lib/*
Defines Base-aligned metadata for 28 tokens, Ethereum underlying placeholders, and tests for table ordering, live metadata, and wrapped-token derivation.
Cross-chain parity snapshot and gating
test/src/concrete/deploy/StoxCrossChainParity.t.sol
Snapshots Safe, authoriser, token-leg, proxy, and beacon state across Base and Ethereum, with hydration gating and deadline-based parity assertions.

Scheduled Rainix execution

Layer / File(s) Summary
Scheduled workflow wiring
.github/workflows/rainix-sol-scheduled.yaml
Runs the reusable Rainix Solidity workflow daily at 06:17 UTC or through manual dispatch, forwarding network RPC secrets.

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
Loading

Possibly related PRs

Suggested reviewers: thedavidmeister

Poem

I’m a rabbit checking chains in the night,
Base and Ethereum lined up just right.
Twenty-eight tokens hop into view,
Beacons and clones pass inspection too.
At dawn, Rainix runs on schedule anew.

🚥 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 summarizes the main changes: cross-chain parity pinning plus scheduled CI for the deployment checks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rai-1097-cross-chain-parity

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.yaml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


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.

@hardyjosh
hardyjosh force-pushed the feat/rai-1097-cross-chain-parity branch from 584bdc9 to 4424f0c Compare July 7, 2026 10:55
@hardyjosh
hardyjosh force-pushed the feat/rai-1095-ethereum-bootstrap branch from 7f66d31 to 5769d2e Compare July 7, 2026 10:55
@hardyjosh
hardyjosh force-pushed the feat/rai-1095-ethereum-bootstrap branch from 5769d2e to 2320571 Compare July 7, 2026 11:04
@hardyjosh
hardyjosh force-pushed the feat/rai-1097-cross-chain-parity branch from 4424f0c to fb3493d Compare July 7, 2026 11:05
@hardyjosh
hardyjosh force-pushed the feat/rai-1095-ethereum-bootstrap branch from 2320571 to 94b4454 Compare July 15, 2026 13:48
@hardyjosh
hardyjosh force-pushed the feat/rai-1097-cross-chain-parity branch 2 times, most recently from 336cf88 to 76f8116 Compare July 15, 2026 15:04
@hardyjosh
hardyjosh force-pushed the feat/rai-1095-ethereum-bootstrap branch from 94b4454 to 458ecd9 Compare July 15, 2026 15:04
@hardyjosh
hardyjosh force-pushed the feat/rai-1097-cross-chain-parity branch from 76f8116 to ae562f4 Compare July 15, 2026 15:30
@hardyjosh
hardyjosh force-pushed the feat/rai-1095-ethereum-bootstrap branch from 458ecd9 to f030d37 Compare July 15, 2026 15:30
@hardyjosh
hardyjosh force-pushed the feat/rai-1097-cross-chain-parity branch from 6c7b2d8 to 92666ff Compare July 22, 2026 08:48
@hardyjosh
hardyjosh changed the base branch from graphite-base/230 to pin/ethereum-clone-address July 22, 2026 08:48
@hardyjosh
hardyjosh force-pushed the feat/rai-1097-cross-chain-parity branch from 92666ff to 495a253 Compare July 22, 2026 09:58
@hardyjosh
hardyjosh force-pushed the pin/ethereum-clone-address branch from fce7843 to 33f3b0a Compare July 22, 2026 09:58
@hardyjosh
hardyjosh marked this pull request as ready for review July 22, 2026 10:44
@hardyjosh
hardyjosh force-pushed the feat/rai-1097-cross-chain-parity branch from 495a253 to b411c7b Compare July 22, 2026 13:17
@thedavidmeister
thedavidmeister changed the base branch from pin/ethereum-clone-address to main July 22, 2026 21:02
Josh Hardy and others added 6 commits July 22, 2026 21:35
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>

@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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 430d3d1 and e822482.

📒 Files selected for processing (6)
  • .github/workflows/rainix-sol-scheduled.yaml
  • src/lib/LibProdTokenConfig.sol
  • src/lib/LibTokenInvariants.sol
  • test/src/concrete/deploy/StoxCrossChainParity.t.sol
  • test/src/lib/LibProdTokenConfig.t.sol
  • test/src/lib/LibTokenInvariants.t.sol

Comment thread .github/workflows/rainix-sol-scheduled.yaml
@thedavidmeister

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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>
@thedavidmeister

Copy link
Copy Markdown
Contributor

Reviewed f2db6de: approve

Run 29979066122 leaves one test failure — testDeployTag (0_1_8 != 0_1_22) — plus git-clean. Both inherited main-red; the tag has drifted 0_1_15 → 16 → 17 → 22 across the day, so it is a moving target no open PR owns.

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.

testEthereumTokenTableMirrorsBaseUnderlyings is the other quiet win: it pins the two token tables to the same length and the same underlyings row-for-row, which is what stops the Ethereum mirror rotting the next time Base gains tokens. It was already load-bearing — both tables are 28 rows because the mirror was updated in lockstep when #265 took Base from 22.

Changes made during review:

  • The scheduled caller omitted RPC_URL_ETHEREUM_FORK while the push-based caller forwards all six (f2db6de). The parity suite forks Ethereum, so the daily job could not reach the chain the check is about — the one thing the schedule was added to do was the one thing it could not have done, and it would have gone red environmentally on its first fire. Caught by CodeRabbit.
  • The suite now proves it ran. Every cross-chain comparison is gated on both chains carrying the leg, so a suite that skipped all of them was green in exactly the same way as one that checked all of them. Base's three legs must now report live — Base is fully bootstrapped, so a pending leg there means the placeholder detection misread a live pin and silently disabled the whole test.
  • Body corrected — it claimed 20 underlyings against the code's 28, and "red until the V4 stack executes", which stopped being true once feat(lib): chain-aware token-owner Safe assertion #267 and pin(ethereum): hydrate the V4 authoriser clone address #258 landed. It passes today with exactly one pending leg (the Ethereum token table, awaiting pin(tokens): hydrate the Ethereum token table + add RKLB to the canonical set #259).

One thing deliberately left outstanding. ETHEREUM_PARITY_DEADLINE is clock-gated, and #256 argues — correctly — that "the deadline is an operator SLA pretending to be a test… wall-clock time is not the state under test", listing the sibling deadline constants for removal. I added it by matching BeaconOwnerMigrationPin's existing pattern, which is how that pattern keeps spreading. The fact-based replacement needs no registry: a hydrated pin must produce a live leg, asserted directly. Worth doing as a follow-up under #256 rather than blocking this.

@thedavidmeister
thedavidmeister merged commit bd2f5c0 into main Jul 23, 2026
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants