Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
189 changes: 182 additions & 7 deletions src/lib/LibTokenInvariants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -335,13 +335,6 @@ library LibTokenInvariants {
/// @notice Returns the production token instance triples on Ethereum
/// mainnet — the same 28 underlyings as Base, in the same order, so the
/// two tables pair by index as well as by key.
///
/// **ALL PLACEHOLDERS** (`address(0)`) until the Ethereum token
/// deployments execute and the post-execution pin PR hydrates every entry
/// in one reviewed change. Hydration is all-or-nothing across the table:
/// the multichain issuance cutover is lockstep over the full token set, so
/// a partially hydrated table is an error state, which the cross-chain
/// parity suite rejects rather than half-checks.
/// @return tokens The 28 production token instances on Ethereum.
function productionTokensEthereum() internal pure returns (TokenInstance[] memory tokens) {
// Deployed on Ethereum mainnet 2026-07-22 by
Expand Down Expand Up @@ -534,6 +527,188 @@ library LibTokenInvariants {
);
}

/// @notice Returns the 29 production token instance triples on HyperEVM,
/// in the same row order as `productionTokensBase()` (the cross-chain
/// parity pin asserts the alignment).
/// @return tokens The 29 production token instances on HyperEVM.
function productionTokensHyperEvm() internal pure returns (TokenInstance[] memory tokens) {
tokens = new TokenInstance[](29);
tokens[0] = TokenInstance(
"MSTR",
0xE3772C8695c2cf3dcAA2Dd29759f4Bb91a342763,
0x8500189061e2206Bc33Bf04DC10fFB1Fe7dED637,
0xd9fE7488B86D3aEaf457b181C744BD1A5a120833
);
tokens[1] = TokenInstance(
"TSLA",
0x3a3E00d6fb65E686941f77DD375ca677Ad7772c2,
0xB41fD00d0bA60D9Ae8dCE405cB6AAd5710E5F84d,
0x550499e28A3CE8cb1dB3e3Db23fDD0357eD3ff48
);
tokens[2] = TokenInstance(
"COIN",
0x5cF43A3fFd5B979C36B6512800cdAa6A2EF4f352,
0x5100ED387Ab3ED37667199aD8f9F6D963157d28e,
0xa3d1be1Ab9F4E72309Cb9Bfeaa14aFe46D012e36
);
tokens[3] = TokenInstance(
"SPYM",
0xB023d277f4a50cE056Dd921C28fF7e27F56B3445,
0x484AaA9e6542774026b24aeD4EC3058400eD2439,
0x7dF3ad1ECC10DBF0296D05F91a697e4A059771c2
);
tokens[4] = TokenInstance(
"SIVR",
0x89dC2d5B33e8DcbD24A2e4A07f7B06f55b5bb24e,
0xc6100518997004eFb0701Da3c56000B3d093470a,
0x2B310218001C38cf82816c2098AedDCE22D3Df27
);
tokens[5] = TokenInstance(
"CRCL",
0xDf228279B380e8445970a3B4162B9509D4339520,
0xeF63EdB9F39Dcd03C103A11e2e7E1878308b9586,
0x25Ab6926BA171e1618b214cda774104B2f6ec884
);
tokens[6] = TokenInstance(
"NVDA",
0xA26C89357e6cb53Bf670db2e0e939f5489Ba26A8,
0xf6A89b0c9FF897000E37bBD06397992278FfC50d,
0xa3947d2A74F5a3A7135A9d0c66B28fC7315f30Da
);
tokens[7] = TokenInstance(
"IAU",
0x9003CAc529d1299891C2aaa08b4c810b81F49488,
0xb9A1D1822F57f52959b8c5097A8322D534bceDEe,
0x33f3a6401ce08f705eE4B8a03fc37F298395b720
);
tokens[8] = TokenInstance(
"PPLT",
0x216cbEeC16cF7e4dBd53e6e3D8B54b7dA23BE146,
0x47C2e6644eFDF58E86dA45dC60e0f67A65043B99,
0xCbD06C802BFe993de94d0d635AB5B1fa764519D8
);
tokens[9] = TokenInstance(
"AMZN",
0xA583addaA69142E8588D9ffE3767Dbb0F23fd516,
0x6615f3D82989949fa7d167b40FEc0Ef30cdbA476,
0x4404E24629a33b85FC1E2D7beA673Cd283054594
);
tokens[10] = TokenInstance(
"BMNR",
0x34BF824C28121EbD421026D70a03A7Ec9Ee5a0d4,
0x00472aA0D0611F933c22b8148F02B0cDd1Ae5fbc,
0x35e1D3d3Fa6D43438641d2b8f63cBf84C84435c0
);
tokens[11] = TokenInstance(
"IBHG",
0x8e05CB17994Ee9B207e87993717F6Fb2ee7D0bD3,
0x36b30F5B5D1AcD3D8135Afe7a5516A300021f139,
0x03bBb4148ba82d63993D32fB8E6Eff8Cc51A56f2
);
tokens[12] = TokenInstance(
"SGOV",
0xE4B5Af0bAc3dC97b77d6eE4E95F59c74c679ee5c,
0x344147366F648640076d363FAF659c214788E99d,
0x06b17E431a957Dd8522Bf106653BAc6B39F437E6
);
tokens[13] = TokenInstance(
"QQQM",
0x1Eb9583d1BB2b00B3CB5ead921d021b445F77dBc,
0x5Aa65dfF455C7f18C21370086EaFaEe4f2b63608,
0x63fDfe9cf53cB1FD5d98fE7537B881B86aE04A31
);
tokens[14] = TokenInstance(
"VWO",
0x1A09A154E2ae3890d1e18507a37bC3e2FCfA10cc,
0x2cc8DCfC649f9633C482C81473cC251226375fE3,
0x01EE8b582147D1Aa8A8f2Adc2EBB91fA60082AB0
);
tokens[15] = TokenInstance(
"ARKK",
0xF1E5f11e7fAA40b2C2CEdfAD825D81207f793862,
0xDf406836D5A092894ee5d5bdC7F58e5bdc8D196A,
0x5D80cAcaABCe0ab4C1541493ceB25D97970D250a
);
tokens[16] = TokenInstance(
"SPCX",
0x46dC08972b1d5D5876d244292c551dbbbC7d7e80,
0x659Ea9dd1C833fc76E5328E0E616d8b9D9836dc3,
0xC32C8166164E2cA18BB5fbf3E82CB776943d97FB
);
tokens[17] = TokenInstance(
"CEG",
0x99Ffbf38E7c0D9C440BA629D0108E7d0E96cB710,
0x6240E93f1A08d43002e52b210cD49c54C813D780,
0xc842fC56Dd39B62357d027a0F3B3266353cac991
);
tokens[18] = TokenInstance(
"DRAM",
0xbD4Fa8Fd2643b46eADC499b5A468108817988830,
0x5F5a1Bdc00ade7702ea5944B1374DC598dA3759f,
0x7e1263f1e1EeF7eCe7fA7e568842f213a6CEa956
);
tokens[19] = TokenInstance(
"TSM",
0x2Ce6A6B9E573ec0444C00103ACEee9dC23223621,
0x7Ac659f601bEa2d9f490E0D0D8a68c4282fD9B21,
0x050dB961D36DC2047c779798694AeE455CD40BE6
);
tokens[20] = TokenInstance(
"SKHY",
0xd3150728FFDb338eFd9b07ae90A7D5e688bc3B20,
0xa3a7BEcF428b250Ec1b88Dbc65F579a9570670c7,
0xD13bBc46C4582246656868d32227A99A538D97E1
);
tokens[21] = TokenInstance(
"ASML",
0x6AEBd84df448d2E25080590797075b399Ca43549,
0x77Ee94C8B85cF48a426F84B0F2d796eC77e41c7b,
0xb8605a64B57E7A8631c87F3187c783493231bdD9
);
tokens[22] = TokenInstance(
"MU",
0x98A3887f51AA93079978D265Cb20E215AfEB8b0f,
0x2C845ed32c5fDE012eb14508d0d24BD3300B1D71,
0xF01523C4ca52dF88f07938D4Fb32FCBb6b159867
);
tokens[23] = TokenInstance(
"AMD",
0x0e257A952817ef779407aA27E5CD5F27863883Aa,
0xe2De878d8Ca6bA544BFE80a10fAb0395DF32Cff0,
0xb904955bC71FE6c8e6aBE676ACca640Abc771622
);
tokens[24] = TokenInstance(
"AVGO",
0x607991FE92F9476eBedA4761295E5E2cc37e2834,
0x503a69b2918DFA152eb0e0803174ed90ba5be756,
0xCee4981Fc273C105e2b9eA1fD19031Aa9a6cf113
);
tokens[25] = TokenInstance(
"AMAT",
0x8C8810F83a0F5a8C1A3D541411100DBFB68C5b0c,
0x26139195f7a82a52c62C4047F843C99c61Ed9a5E,
0x44DE27A07B33CD708A49fa79629fAC1df03C56d3
);
tokens[26] = TokenInstance(
"LRCX",
0x2630CA3D952b265F88C189f3679FA985dF34A93D,
0xe68A46547CdBBB181587B32cCD1A505Bedf0a994,
0xD1c1D974837d3f79A92E14048993AD28a6228831
);
tokens[27] = TokenInstance(
"TTWO",
0x47E3487B12272Bb1F933452F855CEb1C5e27204E,
0xb62E913f0cC881862527Fa7e41e1C98eEf09cedD,
0x1D6F0763e58FA6d472d470Eaaef0a4C08080d208
);
tokens[28] = TokenInstance(
"RKLB",
0xFf5b15a4f478F296893b0b244D9b118Be87bCda2,
0xED0c085d92C262FB46937CB0B3C9763Af7fCCf30,
0x8FC87Be766C0cB6f254F1FDc9351D4B85B560FB3
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/// @notice Returns the 28 production receipt vault addresses on Base, in
/// the order they were deployed. Provided so consumers (e.g. invariant
/// assertions, migration scripts) can iterate without hardcoding the
Expand Down
82 changes: 82 additions & 0 deletions test/src/concrete/deploy/StoxCrossChainParity.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,12 @@ contract StoxCrossChainParityTest is Test {
/// point is that the choice is made deliberately rather than by silence.
uint256 internal constant ETHEREUM_PARITY_DEADLINE = 1_790_812_800;

/// @notice Unix timestamp past which the HyperEVM legs must have armed —
/// `2026-11-01T00:00:00Z`, tracking the RAI-1511 bootstrap. PLACEHOLDER
/// in the same sense as the Ethereum deadline: move it deliberately if
/// the bootstrap slips.
uint256 internal constant HYPEREVM_PARITY_DEADLINE = 1_793_491_200;

/// @notice Assert every LIVE leg of a chain on the ACTIVE fork, skipping
/// (with a loud PENDING log) any leg whose pins are still placeholders, and
/// capture what it read for the cross-chain comparison. The legs are nested
Expand Down Expand Up @@ -502,6 +508,18 @@ contract StoxCrossChainParityTest is Test {
LibTokenInvariants.productionTokensEthereum()
);

// HyperEVM forks unconditionally, like Base and Ethereum: CI supplies
// `HYPEREVM_RPC_URL` to the shared rainix test workflow from the
// `RPC_URL_HYPEREVM_FORK` secret, so a missing RPC fails at fork time
// rather than leaving the legs unasserted behind a green run.
vm.createSelectFork(LibStoxDeployNetworks.HYPEREVM);
ChainLegs memory hyper = assertChainLegs(
"HyperEVM",
LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_HYPEREVM,
LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_HYPEREVM,
LibTokenInvariants.productionTokensHyperEvm()
);

// ---- Cross-chain comparisons, each gated on both sides being live ----

// Safe policy: same owner SET (order-insensitive) + threshold, compared
Expand Down Expand Up @@ -551,6 +569,62 @@ contract StoxCrossChainParityTest is Test {
}
}

// HyperEVM vs Base — the same comparisons as Ethereum's, gated on
// each leg being live on both sides.
if (base.safeLive && hyper.safeLive) {
assertEq(hyper.threshold, base.threshold, "Safe threshold diverges cross-chain (HyperEVM)");
assertSameOwnerSet(base.owners, hyper.owners);
}
if (base.cloneLive && hyper.cloneLive) {
assertEq(
hyper.cloneCodehash,
base.cloneCodehash,
"authoriser clone impl codehash diverges cross-chain (HyperEVM)"
);
}
if (base.tokenLegLive && hyper.tokenLegLive) {
assertEq(hyper.beaconImpl, base.beaconImpl, "receipt-vault beacon impl diverges cross-chain (HyperEVM)");
assertEq(
hyper.beaconImplCodehash,
base.beaconImplCodehash,
"receipt-vault beacon impl codehash diverges cross-chain (HyperEVM)"
);
assertEq(
hyper.receiptBeaconImpl, base.receiptBeaconImpl, "receipt beacon impl diverges cross-chain (HyperEVM)"
);
assertEq(
hyper.receiptBeaconImplCodehash,
base.receiptBeaconImplCodehash,
"receipt beacon impl codehash diverges cross-chain (HyperEVM)"
);
assertEq(base.tokenConfigs.length, hyper.tokenConfigs.length, "token table lengths diverge (HyperEVM)");
for (uint256 i = 0; i < base.tokenConfigs.length; i++) {
TokenConfigSnapshot memory b = base.tokenConfigs[i];
TokenConfigSnapshot memory h = hyper.tokenConfigs[i];
assertEq(h.underlying, b.underlying, "token table underlying order diverges (HyperEVM)");
assertEq(h.vaultName, b.vaultName, string.concat(b.underlying, ": vault name diverges (HyperEVM)"));
assertEq(
h.vaultSymbol, b.vaultSymbol, string.concat(b.underlying, ": vault symbol diverges (HyperEVM)")
);
assertEq(
h.vaultDecimals, b.vaultDecimals, string.concat(b.underlying, ": vault decimals diverge (HyperEVM)")
);
assertEq(
h.wrappedName, b.wrappedName, string.concat(b.underlying, ": wrapped name diverges (HyperEVM)")
);
assertEq(
h.wrappedSymbol,
b.wrappedSymbol,
string.concat(b.underlying, ": wrapped symbol diverges (HyperEVM)")
);
assertEq(
h.wrappedDecimals,
b.wrappedDecimals,
string.concat(b.underlying, ": wrapped decimals diverge (HyperEVM)")
);
}
}

Comment thread
thedavidmeister marked this conversation as resolved.
// ---- The suite must prove it actually ran ----

// Every comparison above is gated on both chains carrying the leg, so a
Expand All @@ -576,5 +650,13 @@ contract StoxCrossChainParityTest is Test {
assertTrue(eth.cloneLive, "Ethereum authoriser leg still pending past the parity deadline");
assertTrue(eth.tokenLegLive, "Ethereum token leg still pending past the parity deadline");
}

// HyperEVM's legs arm as the RAI-1511 bootstrap lands. Same forcing
// function as Ethereum's.
if (block.timestamp >= HYPEREVM_PARITY_DEADLINE) {
assertTrue(hyper.safeLive, "HyperEVM Safe leg still pending past the parity deadline");
assertTrue(hyper.cloneLive, "HyperEVM authoriser leg still pending past the parity deadline");
assertTrue(hyper.tokenLegLive, "HyperEVM token leg still pending past the parity deadline");
}
}
}
13 changes: 13 additions & 0 deletions test/src/concrete/deploy/StoxProdV4.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,17 @@ contract StoxProdV4Test is Test {
checkProd_0_1_1OnChain();
LibBeaconInvariants.assertProdBeaconsOwnedByChainSafe(block.chainid);
}

/// Only the audited 0.1.1 production set ships to HyperEVM (the RAI-1511
/// bootstrap), mirroring Ethereum. The fork runs unconditionally: CI
/// supplies `HYPEREVM_RPC_URL` to the shared rainix test workflow from the
/// `RPC_URL_HYPEREVM_FORK` secret, so a missing RPC fails at fork time
/// rather than passing having asserted nothing. RED if the audited 0.1.1
/// suite is absent from HyperEVM or its in-use beacons are not owned by
/// the HyperEVM token-owner Safe; green otherwise, catching later drift.
function testProdDeployHyperEvmV4() external {
vm.createSelectFork(LibStoxDeployNetworks.HYPEREVM);
checkProd_0_1_1OnChain();
LibBeaconInvariants.assertProdBeaconsOwnedByChainSafe(block.chainid);
}
}
Loading