From 17598fc556c24ec9e1809b61460d1bd42c18fbdb Mon Sep 17 00:00:00 2001 From: Alastair Ong Date: Thu, 3 Sep 2026 22:20:38 +0100 Subject: [PATCH 1/2] Pin CBRS, the EU batch and NKE/MCD into the Base tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine tokens live on Base that the config and Base invariant tables never picked up. Both tables go 41 -> 50, appended in Base deploy order: 41 CBRS 2026-08-14 42 AIR.PA 2026-08-28 \ 43 BMW.DE 2026-08-28 | 44 MC.PA 2026-08-28 | EU batch 45 SIE.DE 2026-08-28 | 46 MBG.DE 2026-08-28 | 47 RHM.DE 2026-08-28 / 48 MCD 2026-09-03 \ today 49 NKE 2026-09-03 / CBRS shipped the same day as FTF but only FTF got pinned; it has been a straggler since. The MBG.DE row is the third, good deploy — the two orphaned ones are marked deprecated in sft-ops ops/launches.json and are not referenced here. Every address is read off Base, not copied from the ledger: the receipt leg comes from `receipt()` on each vault, and owner()/authorizer() were checked against the production Safe and the shared authoriser before pinning. Dotted tickers are new to these tables. The underlying string keeps the dot ("AIR.PA") because that is what Base reports from symbol(); only the Solidity constant names substitute an underscore (AIR_PA_RECEIPT), since a dot is not valid in an identifier. MC.PA's name needs a `unicode"..."` literal — solc rejects the bare form, and dropping the ë from "Moët" would fail the live-Base name check. Verified against live Base: testConfigAlignsWithBaseTokenTable PASS testConfigMatchesLiveBase PASS (name + symbol, all 50) testWrappedDerivationHoldsOnBase PASS testAssertProductionStateBasePassesLive PASS Cross-chain parity is red until the copy runs — Base is 50, Ethereum and HyperEVM are still 41. That is the expected intermediate state; the target tables get pinned from the broadcast's logged tuples. Co-Authored-By: Claude Opus 5 (1M context) --- src/lib/LibProdTokenConfig.sol | 15 +++++- src/lib/LibTokenInvariants.sol | 87 ++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/lib/LibProdTokenConfig.sol b/src/lib/LibProdTokenConfig.sol index 6e100ff5..d380faf0 100644 --- a/src/lib/LibProdTokenConfig.sol +++ b/src/lib/LibProdTokenConfig.sol @@ -42,10 +42,10 @@ struct TokenConfig { /// means carrying that space forward; the parity pin would flag it as a /// divergence otherwise. library LibProdTokenConfig { - /// @notice The 41 production token deploy configs, Base table order. + /// @notice The 50 production token deploy configs, Base table order. /// @return configs The name/symbol table. function productionTokenConfigs() internal pure returns (TokenConfig[] memory configs) { - configs = new TokenConfig[](41); + configs = new TokenConfig[](50); configs[0] = TokenConfig("MSTR", "MicroStrategy Incorporated ST0x", "tMSTR"); configs[1] = TokenConfig("TSLA", "Tesla Inc ST0x", "tTSLA"); configs[2] = TokenConfig("COIN", "Coinbase Global Inc ST0x", "tCOIN"); @@ -88,5 +88,16 @@ library LibProdTokenConfig { configs[38] = TokenConfig("BABA", "Alibaba Group Holding Limited ADR ST0x", "tBABA"); configs[39] = TokenConfig("TQQQ", "ProShares UltraPro QQQ ST0x", "tTQQQ"); configs[40] = TokenConfig("FTF", "Franklin Limited Duration Income Trust ST0x", "tFTF"); + configs[41] = TokenConfig("CBRS", "Cerebras Systems Inc. ST0x", "tCBRS"); + configs[42] = TokenConfig("AIR.PA", "Airbus SE ST0x", "tAIR.PA"); + configs[43] = TokenConfig("BMW.DE", "Bayerische Motoren Werke Aktiengesellschaft ST0x", "tBMW.DE"); + // solc rejects a bare non-ASCII string literal; `unicode"..."` is the + // same bytes, and Base carries the ë — dropping it would break parity. + configs[44] = TokenConfig("MC.PA", unicode"LVMH Moët Hennessy Louis Vuitton SE ST0x", "tMC.PA"); + configs[45] = TokenConfig("SIE.DE", "Siemens Aktiengesellschaft ST0x", "tSIE.DE"); + configs[46] = TokenConfig("MBG.DE", "Mercedes-Benz Group AG ST0x", "tMBG.DE"); + configs[47] = TokenConfig("RHM.DE", "Rheinmetall AG ST0x", "tRHM.DE"); + configs[48] = TokenConfig("MCD", "McDonald's Corporation ST0x", "tMCD"); + configs[49] = TokenConfig("NKE", "NIKE, Inc. ST0x", "tNKE"); } } diff --git a/src/lib/LibTokenInvariants.sol b/src/lib/LibTokenInvariants.sol index 21debd43..779f7166 100644 --- a/src/lib/LibTokenInvariants.sol +++ b/src/lib/LibTokenInvariants.sol @@ -389,14 +389,86 @@ library LibTokenInvariants { /// https://basescan.org/address/0x9bDe199Ac6E7E050334306D9267c93e3D6d38333 address internal constant FTF_WRAPPED_TOKEN_VAULT = address(0x9bDe199Ac6E7E050334306D9267c93e3D6d38333); - /// @notice Returns the 41 production token instance triples on Base, in + // ---- tCBRS / wtCBRS — Cerebras Systems Inc. ST0x ---- + /// https://basescan.org/address/0xAe8aD2340aa19749A0b935C7F2245823fC4F14FB + address internal constant CBRS_RECEIPT = address(0xAe8aD2340aa19749A0b935C7F2245823fC4F14FB); + /// https://basescan.org/address/0xBeB0c2011bd5520A0998b69132E9245E39Ac5D1D + address internal constant CBRS_RECEIPT_VAULT = address(0xBeB0c2011bd5520A0998b69132E9245E39Ac5D1D); + /// https://basescan.org/address/0xB457cfBF31995d3aAAa704dA9999cC0b011820ca + address internal constant CBRS_WRAPPED_TOKEN_VAULT = address(0xB457cfBF31995d3aAAa704dA9999cC0b011820ca); + + // ---- tAIR.PA / wtAIR.PA — Airbus SE ST0x ---- + /// https://basescan.org/address/0xa618f8eB7c33a23C84d1500c5191746f86F98b2A + address internal constant AIR_PA_RECEIPT = address(0xa618f8eB7c33a23C84d1500c5191746f86F98b2A); + /// https://basescan.org/address/0xDdB07ADd0e23BB7eC97f53C0959d9c6Cc09624f4 + address internal constant AIR_PA_RECEIPT_VAULT = address(0xDdB07ADd0e23BB7eC97f53C0959d9c6Cc09624f4); + /// https://basescan.org/address/0x1C600EF167c675C878BF3222f3935C4fA2A43C31 + address internal constant AIR_PA_WRAPPED_TOKEN_VAULT = address(0x1C600EF167c675C878BF3222f3935C4fA2A43C31); + + // ---- tBMW.DE / wtBMW.DE — Bayerische Motoren Werke Aktiengesellschaft ST0x ---- + /// https://basescan.org/address/0x30864F2921886Dc491326D86015eB66c9601BF2b + address internal constant BMW_DE_RECEIPT = address(0x30864F2921886Dc491326D86015eB66c9601BF2b); + /// https://basescan.org/address/0x9610C265EB8F38B22Fec78498C74924D9fe55891 + address internal constant BMW_DE_RECEIPT_VAULT = address(0x9610C265EB8F38B22Fec78498C74924D9fe55891); + /// https://basescan.org/address/0xD0269618455cF7dA71665856f16C2D03A1b26290 + address internal constant BMW_DE_WRAPPED_TOKEN_VAULT = address(0xD0269618455cF7dA71665856f16C2D03A1b26290); + + // ---- tMC.PA / wtMC.PA — LVMH Moët Hennessy Louis Vuitton SE ST0x ---- + /// https://basescan.org/address/0xBEDD4C7219E35E6C45BE792F31F076737481FF2c + address internal constant MC_PA_RECEIPT = address(0xBEDD4C7219E35E6C45BE792F31F076737481FF2c); + /// https://basescan.org/address/0xF5245a17E5eb64D53dEa213d40aBa01821D465Cd + address internal constant MC_PA_RECEIPT_VAULT = address(0xF5245a17E5eb64D53dEa213d40aBa01821D465Cd); + /// https://basescan.org/address/0xd92cc92557BFC33028195983ED31465F99b4A01c + address internal constant MC_PA_WRAPPED_TOKEN_VAULT = address(0xd92cc92557BFC33028195983ED31465F99b4A01c); + + // ---- tSIE.DE / wtSIE.DE — Siemens Aktiengesellschaft ST0x ---- + /// https://basescan.org/address/0xe89995E4Bf9691486445917e3C69ed3d935183f1 + address internal constant SIE_DE_RECEIPT = address(0xe89995E4Bf9691486445917e3C69ed3d935183f1); + /// https://basescan.org/address/0xa662066a60964a528aD05209139a79BDa3a7577f + address internal constant SIE_DE_RECEIPT_VAULT = address(0xa662066a60964a528aD05209139a79BDa3a7577f); + /// https://basescan.org/address/0xed0B0fdD92F7eece606A0FB0457352616d6e6B34 + address internal constant SIE_DE_WRAPPED_TOKEN_VAULT = address(0xed0B0fdD92F7eece606A0FB0457352616d6e6B34); + + // ---- tMBG.DE / wtMBG.DE — Mercedes-Benz Group AG ST0x ---- + /// https://basescan.org/address/0x984d884fc03D17e7b9cCfE6A32f27e9b6d9B92bd + address internal constant MBG_DE_RECEIPT = address(0x984d884fc03D17e7b9cCfE6A32f27e9b6d9B92bd); + /// https://basescan.org/address/0x07d7ca9384812b86e7E429473dF354fb59DA73A0 + address internal constant MBG_DE_RECEIPT_VAULT = address(0x07d7ca9384812b86e7E429473dF354fb59DA73A0); + /// https://basescan.org/address/0xfae641bfE7009B5326396048b951E3aa31902a2B + address internal constant MBG_DE_WRAPPED_TOKEN_VAULT = address(0xfae641bfE7009B5326396048b951E3aa31902a2B); + + // ---- tRHM.DE / wtRHM.DE — Rheinmetall AG ST0x ---- + /// https://basescan.org/address/0xfbd76467e4FBbd64dD43057bF137a75ec676C3c2 + address internal constant RHM_DE_RECEIPT = address(0xfbd76467e4FBbd64dD43057bF137a75ec676C3c2); + /// https://basescan.org/address/0x725E0Df28D1EB09338f63776B76bCfc00f3b6f44 + address internal constant RHM_DE_RECEIPT_VAULT = address(0x725E0Df28D1EB09338f63776B76bCfc00f3b6f44); + /// https://basescan.org/address/0xD3c42aB4D5A60A50401b3A67D10B03Ce7D96DA19 + address internal constant RHM_DE_WRAPPED_TOKEN_VAULT = address(0xD3c42aB4D5A60A50401b3A67D10B03Ce7D96DA19); + + // ---- tMCD / wtMCD — McDonald's Corporation ST0x ---- + /// https://basescan.org/address/0x1Cfa3Af9DcEc40dCa8f8588c7b7FCE5a1BA6eC3f + address internal constant MCD_RECEIPT = address(0x1Cfa3Af9DcEc40dCa8f8588c7b7FCE5a1BA6eC3f); + /// https://basescan.org/address/0x6363657E19A82ABE0E210e9b8c88Ea61d96eceaB + address internal constant MCD_RECEIPT_VAULT = address(0x6363657E19A82ABE0E210e9b8c88Ea61d96eceaB); + /// https://basescan.org/address/0x7ecAE30Ed8ee4F72653ada7b0941bDE7a0a8eE8d + address internal constant MCD_WRAPPED_TOKEN_VAULT = address(0x7ecAE30Ed8ee4F72653ada7b0941bDE7a0a8eE8d); + + // ---- tNKE / wtNKE — NIKE, Inc. ST0x ---- + /// https://basescan.org/address/0x8A0037757d67084f5431c02eE2A352593eFe2957 + address internal constant NKE_RECEIPT = address(0x8A0037757d67084f5431c02eE2A352593eFe2957); + /// https://basescan.org/address/0x88C3F4E2E0a977Fed97ed246c70BFD7A01070246 + address internal constant NKE_RECEIPT_VAULT = address(0x88C3F4E2E0a977Fed97ed246c70BFD7A01070246); + /// https://basescan.org/address/0x0883f32d23Ed5535057a4B5E3eB1970FE08606AF + address internal constant NKE_WRAPPED_TOKEN_VAULT = address(0x0883f32d23Ed5535057a4B5E3eB1970FE08606AF); + + /// @notice Returns the 50 production token instance triples on Base, in /// the order they were deployed. This is the structured source of truth /// the flat `productionReceiptVaults()` accessor derives from; consumers /// that need the receipt / wrapped-vault legs or the underlying join key /// (cross-chain parity, per-token config checks) iterate this instead. - /// @return tokens The 41 production token instances on Base. + /// @return tokens The 50 production token instances on Base. function productionTokensBase() internal pure returns (TokenInstance[] memory tokens) { - tokens = new TokenInstance[](41); + tokens = new TokenInstance[](50); tokens[0] = TokenInstance("MSTR", MSTR_RECEIPT, MSTR_RECEIPT_VAULT, MSTR_WRAPPED_TOKEN_VAULT); tokens[1] = TokenInstance("TSLA", TSLA_RECEIPT, TSLA_RECEIPT_VAULT, TSLA_WRAPPED_TOKEN_VAULT); tokens[2] = TokenInstance("COIN", COIN_RECEIPT, COIN_RECEIPT_VAULT, COIN_WRAPPED_TOKEN_VAULT); @@ -448,6 +520,15 @@ library LibTokenInvariants { tokens[38] = TokenInstance("BABA", BABA_RECEIPT, BABA_RECEIPT_VAULT, BABA_WRAPPED_TOKEN_VAULT); tokens[39] = TokenInstance("TQQQ", TQQQ_RECEIPT, TQQQ_RECEIPT_VAULT, TQQQ_WRAPPED_TOKEN_VAULT); tokens[40] = TokenInstance("FTF", FTF_RECEIPT, FTF_RECEIPT_VAULT, FTF_WRAPPED_TOKEN_VAULT); + tokens[41] = TokenInstance("CBRS", CBRS_RECEIPT, CBRS_RECEIPT_VAULT, CBRS_WRAPPED_TOKEN_VAULT); + tokens[42] = TokenInstance("AIR.PA", AIR_PA_RECEIPT, AIR_PA_RECEIPT_VAULT, AIR_PA_WRAPPED_TOKEN_VAULT); + tokens[43] = TokenInstance("BMW.DE", BMW_DE_RECEIPT, BMW_DE_RECEIPT_VAULT, BMW_DE_WRAPPED_TOKEN_VAULT); + tokens[44] = TokenInstance("MC.PA", MC_PA_RECEIPT, MC_PA_RECEIPT_VAULT, MC_PA_WRAPPED_TOKEN_VAULT); + tokens[45] = TokenInstance("SIE.DE", SIE_DE_RECEIPT, SIE_DE_RECEIPT_VAULT, SIE_DE_WRAPPED_TOKEN_VAULT); + tokens[46] = TokenInstance("MBG.DE", MBG_DE_RECEIPT, MBG_DE_RECEIPT_VAULT, MBG_DE_WRAPPED_TOKEN_VAULT); + tokens[47] = TokenInstance("RHM.DE", RHM_DE_RECEIPT, RHM_DE_RECEIPT_VAULT, RHM_DE_WRAPPED_TOKEN_VAULT); + tokens[48] = TokenInstance("MCD", MCD_RECEIPT, MCD_RECEIPT_VAULT, MCD_WRAPPED_TOKEN_VAULT); + tokens[49] = TokenInstance("NKE", NKE_RECEIPT, NKE_RECEIPT_VAULT, NKE_WRAPPED_TOKEN_VAULT); } /// @notice Returns the production token instance triples on Ethereum From a2ebce2ed5320b37033a32ae999d0fd778b6eb89 Mon Sep 17 00:00:00 2001 From: Alastair Ong Date: Thu, 3 Sep 2026 22:36:10 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Pin=20CBRS=20on=20Ethereum=20and=20HyperEVM?= =?UTF-8?q?=20too=20=E2=80=94=20it=20is=20already=20deployed=20there?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pinning CBRS on Base alone would have been worse than not pinning it. _selectMissing decides what to deploy by diffing Base against the pinned target table, so a Base-only CBRS row makes the copy think CBRS is missing on Ethereum and HyperEVM and deploy it a second time. It is not missing. `20260807-deploy-missing-tokens` already copied it on 2026-08-14 (runs 31845108154 and 31845492796). The pin PR for that — #310 — was opened the same day and never merged, so the two target tables have sat a row behind the chain ever since. Verified live on both chains before pinning: vault 0x75E0d127…b37C symbol() tCBRS name() Cerebras Systems Inc. ST0x owner() 0x3840aeDa…0329, the token-owner Safe receipt 0x8Ea1ba9F…4AEA8 from receipt() on the vault wrapper 0x15925E1c…23962 symbol() wtCBRS Ethereum and HyperEVM go 41 -> 42. Base stays 50, so parity is still red by the eight tokens the copy has yet to deploy — the EU batch and NKE/MCD. #310 is now redundant and should be closed rather than merged; it pins CBRS at index 41 in a Base table that this branch already takes to 50. Co-Authored-By: Claude Opus 5 (1M context) --- src/lib/LibTokenInvariants.sol | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/lib/LibTokenInvariants.sol b/src/lib/LibTokenInvariants.sol index 779f7166..3a62a784 100644 --- a/src/lib/LibTokenInvariants.sol +++ b/src/lib/LibTokenInvariants.sol @@ -534,7 +534,7 @@ library LibTokenInvariants { /// @notice Returns the production token instance triples on Ethereum /// mainnet — Base's underlyings in Base row order, so the tables pair by /// index as well as by key. - /// @return tokens The 41 production token instances on Ethereum. + /// @return tokens The 42 production token instances on Ethereum. function productionTokensEthereum() internal pure returns (TokenInstance[] memory tokens) { // Deployed on Ethereum mainnet 2026-07-22 by // `20260706-deploy-tokens-ethereum` (manual-broadcast run @@ -544,7 +544,7 @@ library LibTokenInvariants { // run's logged (underlying, receipt, receiptVault, wrapped) tuples. // Order and underlyings match Base row-for-row (the cross-chain // parity pin asserts this). - tokens = new TokenInstance[](41); + tokens = new TokenInstance[](42); tokens[0] = TokenInstance( "MSTR", address(0xE3772C8695c2cf3dcAA2Dd29759f4Bb91a342763), @@ -807,12 +807,23 @@ library LibTokenInvariants { address(0x334ccaD2e7D774F5e6A13437977dD0878926deF8), address(0x710A14a41a8Ea2e25376124C48bf9cAdc1E69be5) ); + // CBRS, deployed 2026-08-14 by `20260807-deploy-missing-tokens` (run + // 31845108154) — the sole token that dispatch selected. It was never pinned + // here: the pin PR (#310) stayed open, so this table sat a row behind + // the chain. Verified live before pinning — symbol() tCBRS, receipt() + // and owner() the chain's token-owner Safe. + tokens[41] = TokenInstance( + "CBRS", + address(0x8Ea1ba9Fc0CF7338B41DdDa5B778a9118274AEA8), + address(0x75E0d127794b9C26eE35c55fbaBcc41c53Ccb37C), + address(0x15925E1c19c0F0d392F6FCb40FdE9144Dd823962) + ); } - /// @notice Returns the 41 production token instance triples on HyperEVM, + /// @notice Returns the 42 production token instance triples on HyperEVM, /// in the same row order as `productionTokensBase()` (the cross-chain /// parity pin asserts the alignment). - /// @return tokens The 41 production token instances on HyperEVM. + /// @return tokens The 42 production token instances on HyperEVM. function productionTokensHyperEvm() internal pure returns (TokenInstance[] memory tokens) { // Deployed on HyperEVM 2026-07-24 (manual-broadcast run 30114307165): // all 29 tokens via the 0.1.1 unified deployer, each wired onto the @@ -821,7 +832,7 @@ library LibTokenInvariants { // (underlying, receipt, receiptVault, wrapped) tuples. The script that // ran it was per-chain and has since been superseded by // `20260807-deploy-missing-tokens`, so this is the record of the run. - tokens = new TokenInstance[](41); + tokens = new TokenInstance[](42); tokens[0] = TokenInstance( "MSTR", 0xE3772C8695c2cf3dcAA2Dd29759f4Bb91a342763, @@ -1079,6 +1090,17 @@ library LibTokenInvariants { 0x334ccaD2e7D774F5e6A13437977dD0878926deF8, 0x710A14a41a8Ea2e25376124C48bf9cAdc1E69be5 ); + // CBRS, deployed 2026-08-14 by `20260807-deploy-missing-tokens` (run + // 31845492796) — the sole token that dispatch selected. It was never pinned + // here: the pin PR (#310) stayed open, so this table sat a row behind + // the chain. Verified live before pinning — symbol() tCBRS, receipt() + // and owner() the chain's token-owner Safe. + tokens[41] = TokenInstance( + "CBRS", + 0x8Ea1ba9Fc0CF7338B41DdDa5B778a9118274AEA8, + 0x75E0d127794b9C26eE35c55fbaBcc41c53Ccb37C, + 0x15925E1c19c0F0d392F6FCb40FdE9144Dd823962 + ); } /// @notice Returns the 29 production receipt vault addresses on Base, in