refactor(invariants): structured token table + per-chain-Safe-parametric invariants - #228
Conversation
📝 WalkthroughWalkthroughThe invariant libraries now support chain-specific token tables, Safe addresses, and authoriser grant expectations. Base wrappers remain available, and a fork-based test validates equivalent Base invariant entrypoints. ChangesProduction invariant generalisation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
51c6245 to
83c4fc8
Compare
199bcee to
4b188bd
Compare
83c4fc8 to
5043963
Compare
4b188bd to
779689c
Compare
5043963 to
693fc62
Compare
779689c to
7b93f95
Compare
86a6cd3 to
0e0a058
Compare
0e0a058 to
d391089
Compare
855d150 to
3cd70ce
Compare
d391089 to
f448270
Compare
24aa2b4 to
c38367a
Compare
f448270 to
2483bf6
Compare
c38367a to
7b78ffb
Compare
2483bf6 to
0eeb56f
Compare
Makes the Base-pinned invariant framework work for any chain, so the Ethereum operational scripts + the cross-chain parity pin pre-flight through the same libraries instead of Base-hardcoded ones (the gap OPERATIONAL_SCRIPTS.md's LibInvariants.assertAll(safe) couldn't cover). - LibChainPrincipals: per-chain principal tables (token-owner Safe + service signer). Ethereum resolves to the SAME two addresses as Base: the token-owner Safe is reproduced at its Base address (matched-address Safe, LibStoxSafeGenesis) so TOKEN_OWNER_SAFE_ETHEREUM is a deterministic pin (= STOX_TOKEN_OWNER_SAFE), and the service signer is shared. Both principals are concrete source pins, never runtime-hydrated — so isPending/isHydrated are removed; whether a chain is BOOTSTRAPPED on-chain is answered by the deploy-artifact pins + live-code assertions, not the principals. - LibAuthoriserInvariants: expectedGrants / assertExpectedGrants gain chain-parametric overloads over ChainPrincipals; the grant STRUCTURE is defined once and filled per chain. Base no-arg overloads delegate with base() principals (pinned equal pair-for-pair by test). - LibTokenInvariants: TokenInstance struct + structured productionTokensBase() source of truth; assertUniformOwnership / assertUniformAuthoriser / assertAll gain TokenInstance[]-parametric overloads so any chain's token set is checkable; the Base overloads delegate. - LibInvariants: new chain-parametric assertProductionState(safe, tokens, authoriser, principals) — the multichain generalisation of assertAll(safe), which now delegates to it with Base's table + live authoriser + principals. The ST0x Safe is reproduced with the same address + owner set + threshold on every chain, so the Safe-side leg reuses the shared no-arg pins cross-chain. Proven live: the orchestrator wired with Base's args passes against a Base fork identically to the no-arg overload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
…ainPrincipals The token-owner Safe and the authoriser role-grant map are IDENTICAL on every chain — the Safe is reproduced at its Base address via the matched-address deploy, and the issuance service signer is shared. So a per-chain `ChainPrincipals` table was modelling a difference that does not exist. Per Josh: the only per-chain-unique things are the authoriser clone ADDRESS and the token ADDRESSES (their impl codehashes asserted equal across chains by the parity pin). - Delete `LibChainPrincipals` + its test. - Restore `LibAuthoriserInvariants` to the shared-grants form (literal `GRANTEE_SERVICE_1C66`, no-arg `expectedGrants()` / `assertExpectedGrants(address)`) — reverts the #228 parametrization. - Simplify `LibInvariants.assertProductionState(safe, tokens, authoriser, principals)` -> `assertProductionState(tokens, authoriser)`: the Safe is the shared `LibSafeInvariants.STOX_TOKEN_OWNER_SAFE` and the grant map is shared, so only the deploy artifacts are parameters. Verified against live Base: LibInvariantsTest + LibAuthoriserInvariantsTest pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
…r-chain Safe The token-owner Safe address is now a per-chain deploy artifact (matched address abandoned), so the grant map and the multichain production-state bundle resolve the Safe per chain instead of assuming one shared address: - `LibAuthoriserInvariants.expectedGrants(address safe)` / `assertExpectedGrants(authoriser, safe)` — the Safe-grantee slots take the chain's Safe; the service signer stays the shared constant. No-arg overloads default to Base. - `LibInvariants.assertProductionState(tokens, authoriser)` resolves the Safe via `LibSafeInvariants.safeForChainId(block.chainid)` and asserts it with `assertPolicyMatchesBase` (order-insensitive owner set), then wires that Safe into the token-ownership + grant-map checks. Signature is unchanged (still `(tokens, authoriser)`); the per-chain Safe is read from the pin by chain id. Verified against live Base. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
0eeb56f to
ec70a28
Compare

Groundwork for the Ethereum bootstrap. Makes the production-state
invariants resolve the token-owner Safe per chain and introduces a
structured token table. No behavioural change on Base — every existing
call site keeps its exact semantics (pinned by test).
LibTokenInvariants — new
TokenInstancestruct (underlying ticker asthe chain-agnostic join key + receipt / receipt-vault / wrapped-vault
triple) and
productionTokensBase()as the structured source of truth;productionReceiptVaults()now derives from it so the two accessorscannot drift. (Carries the SKHY 21st entry pulled through from main.)
LibAuthoriserInvariants —
expectedGrants/assertExpectedGrantsgain an overload parameterised over the token-owner Safe address, so the
role-grant STRUCTURE (5
_ADMINSafe + 3 service action + 3 Safe action)is defined once and filled with a given chain's Safe. The no-arg Base
overloads delegate to the parametric ones with the pinned Base Safe, so
the Base grant map is unchanged pair-for-pair.
LibInvariants —
assertProductionStateresolves the token-owner Safefrom
block.chainid(LibSafeInvariants.safeForChainId) and asserts theSafe policy matches Base, so the same production-state assertion runs on
any supported chain.
Supersedes the earlier
LibChainPrincipalsapproach (a per-chain principalstruct), which was removed in favour of shared principals + a single
per-chain Safe address — the only thing that actually differs per chain.
Summary by CodeRabbit
Improvements
Tests