Skip to content

refactor(invariants): structured token table + per-chain-Safe-parametric invariants - #228

Merged
thedavidmeister merged 3 commits into
mainfrom
feat/rai-1096-chain-principals
Jul 20, 2026
Merged

refactor(invariants): structured token table + per-chain-Safe-parametric invariants#228
thedavidmeister merged 3 commits into
mainfrom
feat/rai-1096-chain-principals

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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 TokenInstance struct (underlying ticker as
    the 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 accessors
    cannot drift. (Carries the SKHY 21st entry pulled through from main.)

  • LibAuthoriserInvariantsexpectedGrants / assertExpectedGrants
    gain an overload parameterised over the token-owner Safe address, so the
    role-grant STRUCTURE (5 _ADMIN Safe + 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.

  • LibInvariantsassertProductionState resolves the token-owner Safe
    from block.chainid (LibSafeInvariants.safeForChainId) and asserts the
    Safe policy matches Base, so the same production-state assertion runs on
    any supported chain.

Supersedes the earlier LibChainPrincipals approach (a per-chain principal
struct), 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

    • Production-state checks now support token and authoriser configurations across multiple chains.
    • Token ownership and authoriser validation use a consistent production token inventory.
    • Authoriser grant verification now accommodates each chain’s token-owner Safe while preserving required service permissions.
    • Existing validation entry points remain compatible with prior configurations.
  • Tests

    • Added live Base-network coverage for production-state invariant validation.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

Production invariant generalisation

Layer / File(s) Summary
Token table and parameterized vault checks
src/lib/LibTokenInvariants.sol
Adds TokenInstance records and derives receipt-vault ownership and authoriser checks from the production token table.
Multi-chain production-state pre-flight
src/lib/LibInvariants.sol, test/src/lib/LibInvariants.t.sol
Resolves the Safe by chain ID, validates supplied token data and authoriser state, and tests the Base fork path.
Chain-specific authoriser grant expectations
src/lib/LibAuthoriserInvariants.sol
Parameterizes _ADMIN grantees by token-owner Safe while preserving existing overloads and service signer grants.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: thedavidmeister, thedavidmeister

Poem

I’m a rabbit who hops through the grants,
With token tables tucked in my pants.
Safe by chain, vaults aligned,
Every pinned role is checked and defined.
Base still passes—what delightful romance!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main refactor: structured token tables and per-chain Safe parameterization for invariants.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rai-1096-chain-principals

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hardyjosh
hardyjosh marked this pull request as ready for review July 6, 2026 22:22
@hardyjosh
hardyjosh changed the base branch from feat/rai-1095-ethereum-network-wiring to graphite-base/228 July 7, 2026 10:55
@hardyjosh
hardyjosh changed the base branch from graphite-base/228 to feat/rai-1109-deploy-safe-ethereum July 7, 2026 10:55
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch from 51c6245 to 83c4fc8 Compare July 7, 2026 10:55
@hardyjosh
hardyjosh force-pushed the feat/rai-1109-deploy-safe-ethereum branch from 199bcee to 4b188bd Compare July 7, 2026 11:03
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch from 83c4fc8 to 5043963 Compare July 7, 2026 11:03
@hardyjosh
hardyjosh force-pushed the feat/rai-1109-deploy-safe-ethereum branch from 4b188bd to 779689c Compare July 15, 2026 13:48
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch from 5043963 to 693fc62 Compare July 15, 2026 13:48
@hardyjosh
hardyjosh force-pushed the feat/rai-1109-deploy-safe-ethereum branch from 779689c to 7b93f95 Compare July 15, 2026 15:04
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch 2 times, most recently from 86a6cd3 to 0e0a058 Compare July 15, 2026 15:30
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch from 0e0a058 to d391089 Compare July 17, 2026 09:05
@hardyjosh
hardyjosh force-pushed the feat/rai-1109-deploy-safe-ethereum branch from 855d150 to 3cd70ce Compare July 17, 2026 09:05
@hardyjosh
hardyjosh changed the base branch from feat/rai-1109-deploy-safe-ethereum to graphite-base/228 July 17, 2026 09:59
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch from d391089 to f448270 Compare July 17, 2026 09:59
@hardyjosh
hardyjosh changed the base branch from graphite-base/228 to feat/ethereum-beacon-owner-migration July 17, 2026 09:59
@hardyjosh hardyjosh changed the title refactor(invariants): chain-parametric principals + structured token table refactor(invariants): structured token table + per-chain-Safe-parametric invariants Jul 17, 2026
@hardyjosh
hardyjosh force-pushed the feat/ethereum-beacon-owner-migration branch from 24aa2b4 to c38367a Compare July 17, 2026 15:25
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch from f448270 to 2483bf6 Compare July 17, 2026 15:25
@hardyjosh
hardyjosh force-pushed the feat/ethereum-beacon-owner-migration branch from c38367a to 7b78ffb Compare July 17, 2026 15:49
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch from 2483bf6 to 0eeb56f Compare July 17, 2026 15:49
Josh Hardy and others added 3 commits July 20, 2026 11:08
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
@hardyjosh
hardyjosh changed the base branch from feat/ethereum-beacon-owner-migration to graphite-base/228 July 20, 2026 11:10
@hardyjosh
hardyjosh force-pushed the feat/rai-1096-chain-principals branch from 0eeb56f to ec70a28 Compare July 20, 2026 11:10
@hardyjosh
hardyjosh changed the base branch from graphite-base/228 to main July 20, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants