Skip to content

fix(test): assert ownership of the IN-USE production beacons per chain - #266

Merged
thedavidmeister merged 3 commits into
mainfrom
fix/prodv4-ethereum-beacon-owner
Jul 22, 2026
Merged

fix(test): assert ownership of the IN-USE production beacons per chain#266
thedavidmeister merged 3 commits into
mainfrom
fix/prodv4-ethereum-beacon-owner

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

testProdDeployEthereumV4 failed because checkProd_0_1_1OnChain asserted every 0.1.1-address beacon is owned by BEACON_INITIAL_OWNER (rainlang.eth) — stale since the Ethereum beacon-ownership migration moved them to the Safe. The deeper problem: the check conflated deploy-artifact integrity with live ownership, and pinned the owner of beacons production doesn't even run on (Base's unadopted 0.1.1-address set).

Restructured around what matters operationally:

  • LibBeaconInvariants.prodBeaconsForChainId(chainId) — single source of truth for the beacons production tokens RUN ON per chain: Base = the V1-generation addresses (LibProdDeployV1), Ethereum = the 0.1.1 set (LibProdBeaconsEthereum). Beacon addresses are per-chain artifacts that never change; only their implementations upgrade.
  • assertProdBeaconsOwnedByChainSafe(chainId) — every in-use beacon is owned by THAT chain's token-owner Safe (whoever owns an in-use beacon can repoint every production vault proxy on the chain). Where beacons POINT is the cross-chain parity pin's concern, not duplicated here.
  • checkProd_0_1_1OnChain drops its owner parameter and owner asserts entirely: deploy-artifact integrity only (code, codehash, constructor wiring). Base's unadopted 0.1.1-address beacons no longer have their owner pinned.
  • Both network tests call the per-chain in-use ownership assert.

Verified against live Base + Ethereum: both network tests pass.

Sits at the bottom of the Ethereum-multichain stack so the stale owner assert doesn't red-line the PRs above it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr

Summary by CodeRabbit

  • New Features

    • Added chain-specific production beacon resolution for supported networks.
    • Added validation that production beacons are deployed and owned by the correct chain-safe account.
    • Unsupported chains now fail with a clear error.
  • Tests

    • Added coverage for beacon routing, ownership mismatches, undeployed beacons, and distinct beacon assignments.
    • Centralized production beacon ownership checks across deployment validation.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4789e317-e18f-4a08-921f-d83f164aa7d2

📥 Commits

Reviewing files that changed from the base of the PR and between 3a54ed0 and f06de9b.

📒 Files selected for processing (1)
  • test/src/lib/LibProdBeaconsBase.t.sol
📝 Walkthrough

Walkthrough

Production beacon definitions and chain dispatch are added for Base and Ethereum. Beacon invariants now verify deployment and Safe ownership, while deployment tests centralize ownership checks and add coverage for routing and failure cases.

Changes

Production beacon invariants

Layer / File(s) Summary
Base beacon definitions and validation
src/lib/LibProdBeaconsBase.sol, test/src/lib/LibProdBeaconsBase.t.sol
Defines fixed Base beacon and implementation arrays, then validates proxy mappings and pairwise beacon distinctness.
Chain dispatch and ownership checks
src/lib/LibBeaconInvariants.sol, test/src/lib/LibBeaconInvariantsHarness.sol, test/src/lib/LibBeaconInvariants.t.sol
Routes supported chain IDs to production beacon sets and verifies beacon deployment and ownership by the chain Safe, with tests for supported and failing cases.
Deployment test integration
test/src/concrete/deploy/StoxProdV4.t.sol
Removes scattered beacon ownership assertions and invokes the centralized per-chain ownership invariant for Base and Ethereum deployments.

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

Possibly related PRs

Suggested reviewers: thedavidmeister

Sequence Diagram(s)

sequenceDiagram
  participant DeploymentTest
  participant LibBeaconInvariants
  participant ProdBeaconSet
  participant ChainSafe
  DeploymentTest->>LibBeaconInvariants: assertProdBeaconsOwnedByChainSafe(chainId)
  LibBeaconInvariants->>ProdBeaconSet: resolve three beacons
  LibBeaconInvariants->>ProdBeaconSet: check code.length and owner()
  LibBeaconInvariants->>ChainSafe: resolve expected Safe
  ChainSafe-->>LibBeaconInvariants: return Safe address
  LibBeaconInvariants-->>DeploymentTest: complete or revert with typed error
Loading

Poem

I’m a rabbit who hops through the chain,
Three beacons aligned in a neat little train.
Safe owners checked, no ghosts in sight,
Base and Ethereum routed right.
Tests thump their paws: all green tonight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: per-chain ownership assertions for the in-use production beacons in tests.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/prodv4-ethereum-beacon-owner

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 changed the base branch from main to graphite-base/266 July 21, 2026 22:20
@hardyjosh
hardyjosh force-pushed the fix/prodv4-ethereum-beacon-owner branch from 83bec06 to 898c31a Compare July 21, 2026 22:20
@hardyjosh
hardyjosh changed the base branch from graphite-base/266 to lib/chain-aware-safe-preflight July 21, 2026 22:20
@hardyjosh hardyjosh changed the title fix(test): StoxProdV4 expects the Safe owner for Ethereum's migrated beacons fix(test): assert ownership of the IN-USE production beacons per chain Jul 21, 2026
@hardyjosh
hardyjosh force-pushed the fix/prodv4-ethereum-beacon-owner branch from 898c31a to 70d49c5 Compare July 21, 2026 22:23
`testProdDeployEthereumV4` failed because `checkProd_0_1_1OnChain` asserted
every 0.1.1-address beacon is owned by `BEACON_INITIAL_OWNER` (rainlang.eth) —
stale since the Ethereum beacon-ownership migration moved them to the Safe.
The deeper problem: the check conflated deploy-artifact integrity with live
ownership, and pinned the owner of beacons production doesn't even run on.

Restructure around what actually matters operationally:

- `LibBeaconInvariants.prodBeaconsForChainId(chainId)` — single source of
  truth for the beacons production tokens RUN ON per chain: Base = the
  V1-generation addresses (`LibProdDeployV1`), Ethereum = the 0.1.1 set
  (`LibProdBeaconsEthereum`). Beacon addresses are per-chain artifacts that
  never change; only their implementations upgrade.
- `assertProdBeaconsOwnedByChainSafe(chainId)` — every in-use beacon is owned
  by THAT chain's token-owner Safe (whoever owns an in-use beacon can repoint
  every production vault proxy on the chain). Where beacons POINT is the
  cross-chain parity pin's concern, not duplicated here.
- `checkProd_0_1_1OnChain` drops its owner parameter and owner asserts
  entirely: it now checks deploy-artifact integrity only (code, codehash,
  constructor wiring). Base's unadopted 0.1.1-address beacons no longer have
  their owner pinned — dead state that says nothing about production.
- Both network tests call the per-chain in-use ownership assert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
`prodBeaconsForChainId` and `assertProdBeaconsOwnedByChainSafe` had no unit
coverage: the per-chain dispatch, the unsupported-chain revert, the
not-deployed guard and the owner comparison were exercised only incidentally
by the network tests, which cannot distinguish "the right beacons are
Safe-owned" from "some Safe-owned beacons are".

`LibProdBeaconsBase` claimed to name the beacons Base production RUNS ON and
nothing enforced it — the only code that ever read a production proxy's
beacon slot, `LibTokenInvariantsAddressesTest.checkTokenSet`, has no callers
and still asserts the pre-migration owner. So the new test reads the beacon
out of every production token on a live fork and pins the lib against it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
Contributor

Pushed test coverage for the new library surface (3a54ed0) — the review found it had none, and the network tests can't substitute because they cannot distinguish "the right beacons are Safe-owned" from "some Safe-owned beacons are".

test/src/lib/LibBeaconInvariants.t.sol (+ two harness shims) — five tests: Base's map returns the V1-generation set, Ethereum's returns the 0.1.1 set (and provably not Base's), an unpinned chain reverts UnsupportedChainForProdBeacons, a rogue owner on an in-use beacon trips BeaconOwnerMismatch against that chain's Safe, and a codeless beacon trips BeaconNotDeployed rather than reaching owner().

test/src/lib/LibProdBeaconsBase.t.sol (new) — the one that matters. LibProdBeaconsBase is billed as the source of truth for the beacons Base production RUNS ON, and every ownership invariant built on it inherits that claim, but nothing enforced it. Worth flagging: the only code that ever read a production proxy's beacon slot — LibTokenInvariantsAddressesTest.checkTokenSethas no callers, and still asserts BEACON_INITIAL_OWNER (pre-migration). So the claim was held by author discipline alone. The new test reads the EIP-1967 beacon out of every productionTokensBase() entry on a live Base fork and pins the lib against what the chain says.

All 10 pass. Mutation-validated — each mutant killed by the intended test:

mutant killed by
beacons() indices 0/1 swapped testProductionTokensRunOnTheseBeaconsMSTR receipt beacon: 0x86e93c39… != 0xEa084c8F…
Base branch returns Ethereum's set testProdBeaconsForChainIdBaseIsTheV1Generation (+2 others)
owner check !=== testInvertedProdBeaconOwnerMismatch
unsupported chain falls back to Base testProdBeaconsForChainIdRevertsForUnpinnedChain

Separately verified against live chains: all three Base V1 beacons are owned by STOX_TOKEN_OWNER_SAFE and all three Ethereum 0.1.1 beacons by STOX_TOKEN_OWNER_SAFE_ETHEREUM, so this PR does fix testProdDeployEthereumV4. Base's V1 beacon implementations match the generated 0_1_1 pins exactly.

Not addressed here, for you to judge: LibProdBeaconsBase.implementations() is never called anywhere in the stack (#230 only names it in a comment), and BeaconOwnerMigrationPin.t.sol still accepts either the EOA or the Safe until 2026-09-01 — now slack, since the migration has landed on both chains.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/lib/LibProdBeaconsBase.sol`:
- Line 5: Update LibProdBeaconsBase to stop importing or referencing
LibProdDeployV1. Add the required retained Base-V1 beacon aliases to the current
versioned deployment library, such as LibProdDeployV4, then replace all
production references in LibProdBeaconsBase with those aliases while preserving
the existing addresses and codehashes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: dfa5bbb2-cfe1-4fec-a9dc-cd9cab15a4f7

📥 Commits

Reviewing files that changed from the base of the PR and between 1335a6a and 3a54ed0.

📒 Files selected for processing (6)
  • src/lib/LibBeaconInvariants.sol
  • src/lib/LibProdBeaconsBase.sol
  • test/src/concrete/deploy/StoxProdV4.t.sol
  • test/src/lib/LibBeaconInvariants.t.sol
  • test/src/lib/LibBeaconInvariantsHarness.sol
  • test/src/lib/LibProdBeaconsBase.t.sol

Comment thread src/lib/LibProdBeaconsBase.sol
@thedavidmeister

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
Contributor

Reviewed f06de9b: approve

The fresh test run (29951072083) confirms this does what it claims: testProdDeployEthereumV4 no longer fails. The only remaining test failure is testDeployTag (0_1_8 != 0_1_16), a moving target no open PR owns, plus git-clean — both inherited main-red. That clears one of the two failures reddening every PR in this repo.

Verified independently against live chains rather than taking the description on trust: all three Base V1-generation beacons are owned by STOX_TOKEN_OWNER_SAFE, all three Ethereum 0.1.1 beacons by STOX_TOKEN_OWNER_SAFE_ETHEREUM, and Base's beacon implementations match the generated 0_1_1 pins exactly.

The restructure is right. Conflating deploy-artifact integrity with live ownership is what made checkProd_0_1_1OnChain go stale, and pinning the owner of beacons production doesn't run on (Base's unadopted 0.1.1-address set) was asserting dead state. Ownership now follows the in-use set per chain, and an unpinned chain reverts rather than silently borrowing another chain's Safe.

Test coverage added in 3a54ed0 (mutation-validated, 4/4 mutants killed by the intended test). Worth flagging what writing it turned up: LibTokenInvariantsAddressesTest.checkTokenSet — the only code that ever read a production proxy's beacon slot — has no callers and still asserts the pre-migration owner, so the in-use claim this PR is built on was enforced by nothing. LibProdBeaconsBase.t.sol now reads the EIP-1967 beacon out of every production token on a live fork and pins the lib against it.

Two notes left for you, neither blocking: LibProdBeaconsBase.implementations() is never called anywhere in the stack (#230 only names it in a comment), and BeaconOwnerMigrationPin.t.sol still accepts either the EOA or the Safe until 2026-09-01 — slack now that the migration has landed on both chains.

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