Skip to content

pin(invariants): HyperEVM invariant surface (RAI-1511) - #277

Merged
thedavidmeister merged 5 commits into
mainfrom
pin/hyperevm-invariant-surface
Aug 10, 2026
Merged

pin(invariants): HyperEVM invariant surface (RAI-1511)#277
thedavidmeister merged 5 commits into
mainfrom
pin/hyperevm-invariant-surface

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Pins the HyperEVM production surface into the shared invariant libraries and turns on the HyperEVM legs of the existing invariants. main is merged in, bringing #274, #275 and #276.

  • productionTokensHyperEvm(): 29 real token rows in Base row order — 87 pinned production addresses (receipt, receipt vault and wrapped vault per underlying, MSTR through RKLB), matching Base's underlyings and ordering one-for-one. There is no address(0) in the table: every row is a live HyperEVM deployment, so the parity pin asserts against real chain state rather than flagging a gap. Verified in CI, not by inspection — StoxCrossChainParityTest.testCrossChainParity forks HyperEVM and checks ownership by the HyperEVM token-owner Safe, the clone as sole authoriser, per-token config, and the shared beacon, then compares all of it against Base.

  • Cross-chain parity: HyperEVM third leg + full HyperEVM-vs-Base comparisons. The HyperEVM fork and its legs run unconditionally, exactly like Base and Ethereum — there is no RPC gate. HYPEREVM_PARITY_DEADLINE (2026-11-01) remains as the bootstrap forcing function: past it, a HyperEVM Safe, authoriser or token leg still reporting pending fails the suite, so a leg that never arms cannot stay indistinguishable from one that passes. Each individual comparison is still gated on both chains carrying the relevant leg.

  • StoxProdV4.testProdDeployHyperEvmV4: the impl-deploy + beacon-migration forcing function, forking unconditionally the same way.

  • NatSpec counts corrected to 29 in LibTokenInvariants. All three token tables allocate 29 and fill indices 0..28, and productionReceiptVaults() derives its length from productionTokensBase(), but five doc lines still said 28 — including a @return sitting four lines under a @notice that already said 29.

CI supplies HYPEREVM_RPC_URL to the shared rainix test workflow from the RPC_URL_HYPEREVM_FORK secret (#275), so a missing RPC is a hard failure at fork time rather than a test that passes having asserted nothing.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr

Summary by CodeRabbit

  • New Features

    • Added support for the HyperEVM production token set, including receipt and vault token addresses.
    • Added HyperEVM deployment verification for the audited production release and beacon ownership.
  • Tests

    • Added cross-chain parity checks comparing HyperEVM deployments with Base.
    • Added validation for token metadata, implementations, code integrity, ordering, and deployment readiness.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a 29-token HyperEVM production table and extends fork-based deployment validation. HyperEVM parity checks compare deployment legs with Base and enforce a bootstrap deadline. A production test verifies deployment pins and beacon ownership.

Changes

HyperEVM production support

Layer / File(s) Summary
HyperEVM token table
src/lib/LibTokenInvariants.sol
Adds productionTokensHyperEvm() with 29 ordered token triples and removes obsolete placeholder documentation.
Cross-chain parity validation
test/src/concrete/deploy/StoxCrossChainParity.t.sol
Adds HyperEVM fork capture, Base parity checks, metadata validation, codehash checks, ordering checks, and deadline enforcement.
Production deployment verification
test/src/concrete/deploy/StoxProdV4.t.sol
Adds HyperEVM V4 deployment checks for audited pins and beacon ownership.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ParityTest
  participant HyperEVM
  participant Base
  participant ProductionChecks
  ParityTest->>HyperEVM: Fork and capture deployment legs
  ParityTest->>Base: Read reference deployment state
  ParityTest->>ParityTest: Compare policy, implementations, metadata, and ordering
  ProductionChecks->>HyperEVM: Validate deployment pins and beacon ownership
Loading

Possibly related PRs

Suggested reviewers: thedavidmeister

Poem

I’m a rabbit checking tokens in a row,
HyperEVM pins now match the flow.
Beacons guard their Safe-held throne,
Forks compare each deployed stone.
Parity hops past every gate! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding the HyperEVM invariant surface.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pin/hyperevm-invariant-surface

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 pin/hyperevm-invariant-surface branch from e62574b to febb397 Compare July 23, 2026 14:14
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch 2 times, most recently from 1754e75 to e525075 Compare July 23, 2026 22:33
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch 2 times, most recently from 06a06a1 to 8dcc2c4 Compare July 23, 2026 23:05
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch 2 times, most recently from e6ef7d0 to 9d29e63 Compare July 23, 2026 23:06
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch 2 times, most recently from 103833b to 6a9b197 Compare July 24, 2026 11:20
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch 2 times, most recently from d7ff7bb to ce3d0ee Compare July 24, 2026 12:21
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch from 6a9b197 to 866cabe Compare July 24, 2026 12:21
@thedavidmeister
thedavidmeister force-pushed the ops/hyperevm-beacon-owner-migration branch from ce3d0ee to 3115e88 Compare July 24, 2026 13:46
@thedavidmeister
thedavidmeister force-pushed the pin/hyperevm-invariant-surface branch from 866cabe to 2d5c3bf Compare July 24, 2026 13:47
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch from 3115e88 to 9ee4c51 Compare July 24, 2026 14:25
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch from 2d5c3bf to e421b17 Compare July 24, 2026 14:25
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch from e421b17 to eb0a0a4 Compare July 24, 2026 14:29
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch from 9ee4c51 to 0265805 Compare July 24, 2026 14:29
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch from eb0a0a4 to 7e515e4 Compare July 24, 2026 15:00
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch from 0265805 to 0c844b2 Compare July 24, 2026 15:00
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch from 6097022 to 2b644e8 Compare August 10, 2026 09:18
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch from 3548b6c to 1117683 Compare August 10, 2026 09:18
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch from 2b644e8 to 6097022 Compare August 10, 2026 10:03
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch from 1117683 to 3548b6c Compare August 10, 2026 10:03
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch from 6097022 to 2b644e8 Compare August 10, 2026 10:18
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch from 3548b6c to 1117683 Compare August 10, 2026 10:18
@hardyjosh
hardyjosh force-pushed the ops/hyperevm-beacon-owner-migration branch from 2b644e8 to 43080d8 Compare August 10, 2026 11:05
Josh Hardy and others added 2 commits August 10, 2026 11:05
- productionTokensHyperEvm(): 29 all-zero rows in Base row order — the
  explicit "missing on this chain" state the gap-filling deploy targets
  and the parity pin flags until hydrated from the executed deploy.
- Cross-chain parity: HyperEVM third leg + full HyperEVM-vs-Base
  comparisons, gated on RPC availability (the shared rainix test workflow
  has no HyperEVM secret slot yet — loudly pending, and the new
  HYPEREVM_PARITY_DEADLINE (2026-11-01) turns a still-missing RPC or
  still-pending leg into a failure rather than letting the gap live
  forever).
- StoxProdV4.testProdDeployHyperEvmV4: the impl-deploy + beacon-migration
  forcing function, env-gated the same way.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
@hardyjosh
hardyjosh force-pushed the pin/hyperevm-invariant-surface branch from 1117683 to 6e82100 Compare August 10, 2026 11:05
@thedavidmeister
thedavidmeister changed the base branch from ops/hyperevm-beacon-owner-migration to main August 10, 2026 12:14
The premise these guards stated — the shared rainix test workflow has no
HyperEVM secret slot, so CI cannot create the fork — is dead. #275 forwards
`RPC_URL_HYPEREVM_FORK` to the shared workflow and #276 deleted the identical
guards from `HyperEvmBeaconOwnership.t.sol` against a real fork.

They were fail-open: had the RPC lapsed, `testProdDeployHyperEvmV4` and the
HyperEVM half of `testCrossChainParity` would have gone green having asserted
nothing. Both now fork unconditionally, so a missing RPC fails at fork time.

`assertTrue(hyperRpcAvailable, ...)` in the parity deadline block goes with the
bool — a missing RPC is no longer something to detect later. The deadline and
its three leg assertions stay, as does every inner `base.X && hyper.X` gate.

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

Copy link
Copy Markdown
Contributor

Guard-removal experiment: the HyperEVM RPC gates are gone, and CI stayed green

be8f416afaa1f8b05c5260fdafb238d792489492 removes the three stale RPC-availability guards. This was run as an experiment — the guards came out, nothing was weakened to compensate, and CI was left to answer whether the suite still holds.

Why they were stale

Every guard stated the same premise in code or comment: the shared rainix test workflow has no HyperEVM secret slot yet (RAI-1511), so in CI the fork cannot even be created. That premise is dead:

The guards were dead code. They were still worth removing because they were fail-open: had the RPC ever lapsed, both tests would have gone green having asserted nothing — the exact hole #275/#276 closed.

What was removed

  1. StoxProdV4.t.soltestProdDeployHyperEvmV4's vm.envOr early return and its emit log. The fork call now runs unconditionally.
  2. StoxCrossChainParity.t.sol — the hyperRpcAvailable bool, the if/else around the HyperEVM leg, and the if (hyperRpcAvailable) wrapper around the HyperEVM-vs-Base comparisons.
  3. assertTrue(hyperRpcAvailable, "HYPEREVM_RPC_URL still unavailable past the parity deadline") — with the bool gone it had nothing to check; a missing RPC is now a hard failure at fork time, not something to detect at a deadline.

Deliberately kept: every inner if (base.X && hyper.X) live-leg gate, HYPEREVM_PARITY_DEADLINE, and its three leg assertions (hyper.safeLive / hyper.cloneLive / hyper.tokenLegLive) — that forcing function is the same one Ethereum carries and is not what was under test here. Comments that stated the now-false premise were rewritten to describe current behaviour. Nothing else changed; the remaining reflow in the diff is forge fmt re-wrapping the de-indented comparison block.

Verdict: green, and demonstrably not vacuous

Run 31388402997 — all six checks pass. A vacuous pass looks identical to a real one, so the evidence:

  • RPC preflight passed and resolved the alias: rpc-preflight: hyperevm: SELECTED default[0] (chain 999, latest, 3/3 samples)rpc-preflight: clean
  • PENDING: appears 0 times in the test job log; [SKIP] appears 0 times
  • Real fork work on HyperEVM, comparable to the other chains:
    • [PASS] testProdDeployHyperEvmV4() (gas: 174483) — alongside Base 180,572 and Ethereum 174,392
    • [PASS] testCrossChainParity() (gas: 6584560)
  • Suite total: 808 tests passed, 0 failed, 0 skipped (808 total tests) across 83 suites

The gas deltas against the pre-removal run are the guard overhead and nothing else: testProdDeployHyperEvmV4 175,638 → 174,483 (−1,155, the removed vm.envOr) and testCrossChainParity 6,587,010 → 6,584,560 (−2,450, the removed vm.envOr + assertTrue). The HyperEVM leg was doing the same real work before and after — the guards were simply never firing.

One thing worth knowing

Note the preflight line says SELECTED default[0], not secret[0]: RAINIX_RPC_SECRET_HYPEREVM is empty in this run because this branch is 5 commits behind main and its own rainix-sol.yaml predates #275's forwarding line. The rainix preflight fell back to its public HyperEVM endpoint, which is why the fork resolves anyway. On main — where this merges — the secret is forwarded, so the alias resolves from RPC_URL_HYPEREVM_FORK as the new comments describe. Either path resolves it, so the guards are unreachable in both; but it means this run's green does not by itself exercise the secret path. Merging main in would.

Also out of scope but adjacent: HyperEvmTokenOwnerSafeParity.t.sol still carries the same fail-open RPC guard (and its @dev still says "remove it once CI carries the secret so a missing RPC becomes a hard failure instead of a skip" — its own stated condition is met). It passed here at 59,723 gas, so the guard is dead there too. Worth a follow-up.

Not merging — this is the experiment's result, not a merge request.

thedavidmeister and others added 2 commits August 10, 2026 12:44
Brings in #274, #275 and #276. #275's `RPC_URL_HYPEREVM_FORK` forwarding in
`rainix-sol.yaml` is what lets this branch's own push run resolve the HyperEVM
alias from the secret rather than the rainix preflight's public default, so the
unconditional forks this branch introduces are exercised against the secret
path the new comments describe.
All three token tables allocate `new TokenInstance[](29)` and assign indices
0..28 contiguously, and `productionReceiptVaults()` derives its array from
`productionTokensBase().length`. Five NatSpec lines still said 28 — including a
`@return` four lines under a `@notice` that already said 29, so one doc block
contradicted itself.

Counted from the arrays rather than assumed: Base, Ethereum and HyperEVM are
29 rows each with no `address(0)` entry in any of them.

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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/lib/LibTokenInvariants.sol (1)

335-338: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the stale token count in the docstring.

productionTokensEthereum() allocates 29 entries and populates indices 0 through 28. The docstring still says "28 underlyings" and "The 28 production token instances on Ethereum". Update both to 29.

📝 Proposed doc fix
     /// `@notice` Returns the production token instance triples on Ethereum
-    /// mainnet — the same 28 underlyings as Base, in the same order, so the
+    /// mainnet — the same 29 underlyings as Base, in the same order, so the
     /// two tables pair by index as well as by key.
-    /// `@return` tokens The 28 production token instances on Ethereum.
+    /// `@return` tokens The 29 production token instances on Ethereum.
🤖 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 `@src/lib/LibTokenInvariants.sol` around lines 335 - 338, Update the
productionTokensEthereum() documentation to state 29 underlyings and 29
production token instances, matching its 29 allocated and populated entries.
🤖 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 `@src/lib/LibTokenInvariants.sol`:
- Around line 534-709: Replace every tuple in productionTokensHyperEvm() with
the verified HyperEVM deployment-specific addresses rather than reusing Ethereum
records. Add the corresponding broadcast name, run ID, and date provenance, and
ensure tokenTableState does not mark tokenLegLive or pass assertAll until those
HyperEVM tuples are available.

In `@test/src/concrete/deploy/StoxCrossChainParity.t.sol`:
- Around line 572-627: Extract the duplicated per-chain parity assertions from
testCrossChainParity into one descriptively named helper that accepts the
baseline and comparison ChainLegs values plus the chain-specific message suffix.
Move all safe, clone, token-leg, and TokenConfigSnapshot field comparisons into
that helper, then invoke it for each non-baseline chain while preserving the
existing live-leg gating and assertion behavior.

---

Outside diff comments:
In `@src/lib/LibTokenInvariants.sol`:
- Around line 335-338: Update the productionTokensEthereum() documentation to
state 29 underlyings and 29 production token instances, matching its 29
allocated and populated entries.
🪄 Autofix

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 Plus

Run ID: 163abd4c-7926-4686-b70d-2092ab5d9aeb

📥 Commits

Reviewing files that changed from the base of the PR and between f6b1979 and fff11c8.

📒 Files selected for processing (3)
  • src/lib/LibTokenInvariants.sol
  • test/src/concrete/deploy/StoxCrossChainParity.t.sol
  • test/src/concrete/deploy/StoxProdV4.t.sol

Comment thread src/lib/LibTokenInvariants.sol
Comment thread test/src/concrete/deploy/StoxCrossChainParity.t.sol
@thedavidmeister

Copy link
Copy Markdown
Contributor

Follow-up: main merged in, and the secret path is now actually exercised

Head is dbb758811dd6626d33d32276f8ebc12fd9353614. Two commits on top of the guard removal: a merge of main (never a rebase) bringing #274, #275 and #276, and a NatSpec count correction.

The preflight line — the point of this pass

rpc-preflight: hyperevm: SELECTED secret[0] (chain 999, latest, 3/3 samples)rpc-preflight: clean

secret[0], not default[0]. RAINIX_RPC_SECRET_HYPEREVM is populated for the first time on this branch. Every prior HyperEVM run — this PR's earlier ones and #276's 31385844185 — read default[0], because a push event runs the branch's own workflow copy and every branch predated #275's RPC_URL_HYPEREVM_FORK: ${{ secrets.RPC_URL_HYPEREVM_FORK }} line. The rainix preflight was silently falling back to its public HyperEVM endpoint. Merging main brought the forwarding line into this branch's rainix-sol.yaml, so the green below is anchored to the secret path the code comments describe rather than to the fallback.

Full suite on the merge commit: green

Run 31389908949 — all six checks pass. The merge was textually clean (zero file overlap between the two sides), which is not evidence on its own, so the suite ran on the merge result:

  • PENDING: 0, [SKIP] 0, [FAIL 0
  • [PASS] testProdDeployHyperEvmV4() (gas: 174483)
  • [PASS] testCrossChainParity() (gas: 6584560)
  • Ran 83 test suites in 447.86s: 808 tests passed, 0 failed, 0 skipped (808 total tests)

Gas figures are identical to the pre-merge run, which is the expected result: the endpoint changed, the assertions did not.

NatSpec counts corrected to 29

All three token tables allocate new TokenInstance[](29) and assign indices 0..28 contiguously; productionReceiptVaults() derives its length from productionTokensBase(). Five doc lines still said 28 — including a @return four lines under a @notice that already said 29, so one block contradicted itself. Counted from the arrays, not assumed: Base, Ethereum and HyperEVM are 29 rows each, with zero address(0) entries in any of them.

PR body rewritten

The body claimed productionTokensHyperEvm() shipped "29 all-zero rows … the explicit 'missing on this chain' state". It ships 29 rows of real addresses — 87 pinned production addresses, MSTR through RKLB — and CI verifies them against live HyperEVM state. That bullet told a reviewer "placeholders, nothing to check" about the one thing in this PR that most needs a human eye. Two further bullets described RPC-availability gating that no longer exists. The body now describes the head being pushed, including that HYPEREVM_PARITY_DEADLINE still forces the three legs live past 2026-11-01 — it simply no longer has an RPC gap to force closed.

No guard reinstated, no skip added, no assertion weakened. Still not merging.

@thedavidmeister

Copy link
Copy Markdown
Contributor

Reviewed dbb7588: approve
Rulings-conformance: checked against every ruling stated for this work. (1) "remove the guards and see if ci passes" — all three RPC guards deleted (the testProdDeployHyperEvmV4 early return, the parity hyperRpcAvailable branch and its wrapper, and the now-meaningless assertTrue(hyperRpcAvailable, …)); CI passes. (2) "fix the body and the natspec counts" — body rewritten so every claim is true of this head, and five stale 28s corrected to 29 (301, 336, 338, 712, 718), each counted off the arrays rather than assumed. (3) "no keep-with-caveat" / "red is never dead-stated" — no guard reinstated, no skip added, no assertion weakened, no token address touched. (4) "coverage, not manual verification" — the green is proven non-vacuous: preflight reads SELECTED secret[0] (chain 999, latest, 3/3 samples), PENDING: 0, [SKIP] 0, [PASS] testProdDeployHyperEvmV4() (gas: 174483), [PASS] testCrossChainParity() (gas: 6584560), 808 passed / 0 failed / 0 skipped. Gas is identical to the pre-merge run, so the endpoint changed and the assertions did not. (5) "producer resolves merge conflicts — merge base in, never rebase" and "semantic conflicts hide outside markers" — main merged in with zero file overlap, and the merge commit was run on its own CI (31389532467) before the NatSpec commit, so neither masked the other. (6) "enumerate authority from chain, never repo greps" — CodeRabbit's Critical (HyperEVM table byte-identical to Ethereum, therefore reused addresses) was falsified the only way it can be: eth_getCode on chain 999 returns 267 bytes at every sampled address. The identity is deterministic CREATE2 working as designed, and assertAll executes against those addresses on the HyperEVM fork rather than skipping. Thread answered and resolved; the proposed fix would have replaced correct addresses with wrong ones. (7) "AI review ≠ human approval" — CodeRabbit's Major (extract the now-threefold duplicated per-chain comparison into a helper) is a real point, answered and tracked as follow-up rather than ridden into an address-pin PR. (8) "no squash-merge for Graphite stacks" — merging with --merge; 278 stacks behind this and will need retargeting. Follow-up filed in the threads, not folded in: the HyperEVM table carries no deploy-provenance comment where the Ethereum table carries one.

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