Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/rainix-sol-scheduled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ jobs:
RPC_URL_BASE_SEPOLIA_FORK: ${{ secrets.RPC_URL_BASE_SEPOLIA_FORK }}
RPC_URL_ETHEREUM_FORK: ${{ secrets.RPC_URL_ETHEREUM_FORK }}
RPC_URL_FLARE_FORK: ${{ secrets.RPC_URL_FLARE_FORK }}
RPC_URL_HYPEREVM_FORK: ${{ secrets.RPC_URL_HYPEREVM_FORK }}
RPC_URL_POLYGON_FORK: ${{ secrets.RPC_URL_POLYGON_FORK }}
1 change: 1 addition & 0 deletions .github/workflows/rainix-sol.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ jobs:
RPC_URL_BASE_SEPOLIA_FORK: ${{ secrets.RPC_URL_BASE_SEPOLIA_FORK }}
RPC_URL_ETHEREUM_FORK: ${{ secrets.RPC_URL_ETHEREUM_FORK }}
RPC_URL_FLARE_FORK: ${{ secrets.RPC_URL_FLARE_FORK }}
RPC_URL_HYPEREVM_FORK: ${{ secrets.RPC_URL_HYPEREVM_FORK }}
RPC_URL_POLYGON_FORK: ${{ secrets.RPC_URL_POLYGON_FORK }}
12 changes: 12 additions & 0 deletions src/lib/LibSafeInvariants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ library LibSafeInvariants {
/// the L1 `Safe` singleton while Base runs the L2 `SafeL2`).
address internal constant STOX_TOKEN_OWNER_SAFE_ETHEREUM = 0x3840aeDaEc8e82f79d8F6a8F6ADCa271E13E0329;

/// @notice The ST0x token-owner Safe on **HyperEVM** (chain id 999).
///
/// Deliberately the SAME address as Ethereum's Safe: created through the
/// canonical Safe proxy factory with the same initializer, so the CREATE2
/// address matches across chains. Still a per-chain deployment (its own
/// proxy, its own state), asserted against the shared policy by
/// `assertTokenOwnerSafePolicy` exactly like every other chain's Safe.
address internal constant STOX_TOKEN_OWNER_SAFE_HYPEREVM = 0x3840aeDaEc8e82f79d8F6a8F6ADCa271E13E0329;

/// @notice The current expected threshold for `STOX_TOKEN_OWNER_SAFE`:
/// 3-of-6 against the post-rotation owner roster. Scripts and the
/// prod-state invariant pin treat this as the canonical current truth
Expand Down Expand Up @@ -477,6 +486,9 @@ library LibSafeInvariants {
if (chainId == ETHEREUM_CHAIN_ID) {
return STOX_TOKEN_OWNER_SAFE_ETHEREUM;
}
if (chainId == HYPEREVM_CHAIN_ID) {
return STOX_TOKEN_OWNER_SAFE_HYPEREVM;
}
revert UnsupportedChainForTokenOwnerSafe(chainId);
}

Expand Down
28 changes: 28 additions & 0 deletions test/src/concrete/deploy/HyperEvmTokenOwnerSafeParity.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Test} from "forge-std-1.16.1/src/Test.sol";
import {IGnosisSafe} from "../../../../src/interface/IGnosisSafe.sol";
import {LibSafeInvariants} from "../../../../src/lib/LibSafeInvariants.sol";
import {LibStoxDeployNetworks} from "../../../../src/lib/LibStoxDeployNetworks.sol";

/// @title HyperEvmTokenOwnerSafeParityTest
/// @notice The HyperEVM ST0x token-owner Safe (deliberately the same
/// CREATE2 address as Ethereum's, created through the canonical Safe proxy
/// factory — a per-chain deployment with its own state) must carry the
/// chain-agnostic token-owner policy: the same owner SET
/// (order-insensitive), threshold, and v1.4.1 identity as every other
/// chain's Safe. Mirrors `EthereumTokenOwnerSafeParityTest`.
contract HyperEvmTokenOwnerSafeParityTest is Test {
/// The pinned HyperEVM Safe carries the shared token-owner policy in
/// every way that matters, at the same address as Ethereum's Safe —
/// the canonical Safe proxy factory with the same initializer yields
/// the same CREATE2 address on both chains.
function testHyperEvmSafeMatchesSharedPolicy() external {
address hyperevmSafe = LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_HYPEREVM;

vm.createSelectFork(LibStoxDeployNetworks.HYPEREVM);
LibSafeInvariants.assertTokenOwnerSafePolicy(IGnosisSafe(hyperevmSafe));
Comment on lines +25 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Suggested change
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.

}
}
Loading