feat(multichain): HyperEVM token-owner Safe pin plumbing (RAI-1511) - #275
Conversation
📝 WalkthroughWalkthroughThe change adds HyperEVM Safe address routing for chain ID 999, validates the pinned Safe against shared invariants on a fork, and passes the HyperEVM RPC secret to Rainix workflows. ChangesHyperEVM Safe support
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
c61c4b9 to
8e010e5
Compare
75a463a to
773cda8
Compare
8e010e5 to
9b894c3
Compare
773cda8 to
9777630
Compare
9b894c3 to
bb342c0
Compare
7865d57 to
f3df426
Compare
bb342c0 to
3e90fed
Compare
f3df426 to
d8b790b
Compare
3e90fed to
b14ffe4
Compare
d8b790b to
6751d21
Compare
b14ffe4 to
54e4482
Compare
eaea4fe to
bc35fb2
Compare
6751d21 to
95b64ee
Compare
95b64ee to
f448568
Compare
bc35fb2 to
860e444
Compare
860e444 to
a4014f5
Compare
56c25f9 to
fcb3b38
Compare
b4ea3ba to
fa517d7
Compare
b110fc7 to
db861f0
Compare
fa517d7 to
b4ea3ba
Compare
db861f0 to
b110fc7
Compare
b4ea3ba to
fa517d7
Compare
b110fc7 to
db861f0
Compare
f6dee27 to
b814c8e
Compare
- STOX_TOKEN_OWNER_SAFE_HYPEREVM = address(0) until the freshly-created HyperEVM Safe's address lands (canonical Safe v1.4.1 infra verified live on HyperEVM at the canonical addresses, 2026-07-22). - safeForChainId branch for chain 999 — every consumer of assertActiveChainTokenOwnerSafe becomes HyperEVM-aware the moment the pin hydrates, with zero further code changes. - HyperEvmTokenOwnerSafeParityTest, mirroring the Ethereum parity pin: PENDING (loud) while the pin is unhydrated, then asserts the shared policy against the live HyperEVM Safe. Second loud PENDING gate on the RPC env: the shared rainix test workflow has no HyperEVM secret slot yet, so CI cannot fork HyperEVM until rainix grows one (noted for follow-up in RAI-1511). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
db861f0 to
52076bd
Compare
rainix's reusable rainix-sol.yaml already declares RPC_URL_HYPEREVM_FORK (and binds it to RAINIX_RPC_SECRET_HYPEREVM in the rpc-preflight step); this repo's two callers just never passed it through, so HYPEREVM_RPC_URL was absent in CI. Added the one missing pass-through line to both rainix-sol.yaml and rainix-sol-scheduled.yaml. With the secret carried, both PENDING return guards in HyperEvmTokenOwnerSafeParityTest go: the address guard was already dead (STOX_TOKEN_OWNER_SAFE_HYPEREVM ships non-zero) and the RPC guard turned a missing fork endpoint into a silent pass. The test is now fork-and-assert unconditionally, so a missing RPC is a hard failure. Docstring follows the code: the pending-gate prose is gone, and the claim that the HyperEVM Safe "is a distinct address from the other chains' Safes" is corrected — it is byte-identical to STOX_TOKEN_OWNER_SAFE_ETHEREUM by design, as the constant's own doc says, because the canonical Safe proxy factory with the same initializer gives the same CREATE2 address across chains. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Forwarded the HyperEVM RPC secret; the parity test now actually runsHead is now 1.
|
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 `@test/src/concrete/deploy/HyperEvmTokenOwnerSafeParity.t.sol`:
- Around line 25-26: Update the HyperEVM parity test to assert block.chainid
equals LibSafeInvariants.HYPEREVM_CHAIN_ID, resolve the Safe via
assertActiveChainTokenOwnerSafe(block.chainid), and compare the resolved address
against both pinned Safe addresses instead of reading
STOX_TOKEN_OWNER_SAFE_HYPEREVM directly.
🪄 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: 63b5725a-ee6a-45d8-936b-bf6b4f7c3187
📒 Files selected for processing (4)
.github/workflows/rainix-sol-scheduled.yaml.github/workflows/rainix-sol.yamlsrc/lib/LibSafeInvariants.soltest/src/concrete/deploy/HyperEvmTokenOwnerSafeParity.t.sol
| vm.createSelectFork(LibStoxDeployNetworks.HYPEREVM); | ||
| LibSafeInvariants.assertTokenOwnerSafePolicy(IGnosisSafe(hyperevmSafe)); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
Exercise the HyperEVM routing path in this parity test.
The production consumer in src/lib/LibInvariants.sol:45-54 resolves the Safe through assertActiveChainTokenOwnerSafe, but this test reads STOX_TOKEN_OWNER_SAFE_HYPEREVM directly. A broken safeForChainId(999) branch can pass this test. Because the HyperEVM and Ethereum pins share an address, an incorrectly selected Ethereum fork can also pass.
Assert block.chainid == LibSafeInvariants.HYPEREVM_CHAIN_ID, resolve the Safe through assertActiveChainTokenOwnerSafe(block.chainid), and compare the result with both pinned addresses.
Proposed fix
vm.createSelectFork(LibStoxDeployNetworks.HYPEREVM);
- LibSafeInvariants.assertTokenOwnerSafePolicy(IGnosisSafe(hyperevmSafe));
+ assertEq(block.chainid, LibSafeInvariants.HYPEREVM_CHAIN_ID);
+ IGnosisSafe resolvedSafe = IGnosisSafe(
+ LibSafeInvariants.assertActiveChainTokenOwnerSafe(block.chainid)
+ );
+ assertEq(address(resolvedSafe), hyperevmSafe);
+ assertEq(address(resolvedSafe), LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_ETHEREUM);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| vm.createSelectFork(LibStoxDeployNetworks.HYPEREVM); | |
| LibSafeInvariants.assertTokenOwnerSafePolicy(IGnosisSafe(hyperevmSafe)); | |
| vm.createSelectFork(LibStoxDeployNetworks.HYPEREVM); | |
| assertEq(block.chainid, LibSafeInvariants.HYPEREVM_CHAIN_ID); | |
| IGnosisSafe resolvedSafe = IGnosisSafe( | |
| LibSafeInvariants.assertActiveChainTokenOwnerSafe(block.chainid) | |
| ); | |
| assertEq(address(resolvedSafe), hyperevmSafe); | |
| assertEq(address(resolvedSafe), LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_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 `@test/src/concrete/deploy/HyperEvmTokenOwnerSafeParity.t.sol` around lines 25
- 26, Update the HyperEVM parity test to assert block.chainid equals
LibSafeInvariants.HYPEREVM_CHAIN_ID, resolve the Safe via
assertActiveChainTokenOwnerSafe(block.chainid), and compare the resolved address
against both pinned Safe addresses instead of reading
STOX_TOKEN_OWNER_SAFE_HYPEREVM directly.
|
Reviewed 9f18d50: pass The HyperEVM Safe pin, with the coverage now actually executing rather than skipping. Verified against the chain, not the repo. Verified in CI, not only locally. Same gas as the local run against What changed since my first read, and why it matters. As originally written both branches returned early — an The skip is provably gone rather than merely edited: with One docstring correction. The contract's doc claimed the Safe "is a distinct address from the other chains' Safes". It is not, the test asserts no such thing, and the constant twelve lines away says the opposite. That false premise is precisely what led me to misread the sibling pin in #274 as a copy-paste defect before the chain corrected me — an identical address across chains reads as a bug unless something states it is deterministic. Now it does. Rulings-conformance:
CI: every check passes. Merging with |
Both premises the gates were waiting on are now false: `STOX_TOKEN_OWNER_SAFE_HYPEREVM` is pinned non-zero, and #275 forwards `RPC_URL_HYPEREVM_FORK` to the shared rainix workflow from both `rainix-sol.yaml` and `rainix-sol-scheduled.yaml`, so `HYPEREVM_RPC_URL` resolves in CI. The docstring's own condition — "Remove the env gate once CI carries the secret" — is met, so the invariant now runs unconditionally and the `@dev` paragraph documents that instead of the removed gates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
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.

HyperEVM Safe's address lands (canonical Safe v1.4.1 infra verified live
on HyperEVM at the canonical addresses, 2026-07-22).
assertActiveChainTokenOwnerSafe becomes HyperEVM-aware the moment the
pin hydrates, with zero further code changes.
PENDING (loud) while the pin is unhydrated, then asserts the shared
policy against the live HyperEVM Safe. Second loud PENDING gate on the
RPC env: the shared rainix test workflow has no HyperEVM secret slot
yet, so CI cannot fork HyperEVM until rainix grows one (noted for
follow-up in RAI-1511).
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
Summary by CodeRabbit
New Features
Tests