feat(script): deploy the Ethereum production tokens via the deploy key, matched to Base - #231
Conversation
|
Warning Review limit reached
Next review available in: 12 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 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 |
cf6c6be to
ee43a1a
Compare
584bdc9 to
4424f0c
Compare
4424f0c to
fb3493d
Compare
ee43a1a to
ee22f69
Compare
ee22f69 to
34f10f2
Compare
336cf88 to
76f8116
Compare
34f10f2 to
6012596
Compare
76f8116 to
ae562f4
Compare
a7013f2 to
94d1c09
Compare
6c7b2d8 to
92666ff
Compare
86f5e8c to
be31c30
Compare
92666ff to
495a253
Compare
495a253 to
b411c7b
Compare
4b4d1c1 to
a9c983c
Compare
… Base
RAI-1095. The missing piece between core-infra deploy and parity-green:
a script that actually stands up the 20 token pairs on Ethereum with
name/symbol/policy identical to Base.
- LibProdTokenConfig: the canonical name/symbol table, captured verbatim
from the live Base receipt vaults (2026-07-06), Base-table order. Only
name + symbol are free deploy inputs — offchain asset (asset=0),
deployer-wired receipt (receipt=0), Safe as initialAdmin, decimals
fixed by the shared impl, and the wrapped vault derives its own
name/symbol on-chain. Reproduces Base exactly including SGOV's
leading-space name (the parity pin would flag it otherwise).
- 20260706-deploy-tokens-ethereum.s.sol, two operations mirroring the
stack's deploy-then-configure split:
* run() (EOA broadcast): one newTokenAndWrapperVault per config with
initialAdmin = the Ethereum Safe, so every vault is Safe-owned from
block one and the deploying key holds nothing. Logs each deployed
(underlying, receiptVault, wrapped) for the pin PR (beacon-proxy
addresses are nonce-based, read from the run not predicted).
* authorizeTokens() (Safe bundle): 20 owner-gated setAuthorizer(clone)
calls wiring every vault onto the Ethereum V4 clone, with verify()
for signers. Typed pre-flights gate on principals hydrated, deployers
live, clone ready, table hydrated, and every vault Safe-owned.
- Tests: config table pinned equal to live Base name/symbol AND the
wrapped-name derivation, aligned index-for-index with the address
table; script pre-flight guards proven to fire while pins are pending.
- Runbook step 7 rewritten around the script (7a deploy, 7b authorise).
Full suite: 676 pass, 9 fail — the 9 are the pre-existing draft-stack
reds (V4 not yet deployed, placeholder pins); the 5 new tests here pass.
slither 0 findings, REUSE 185/185, fmt clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
…y script The token-owner Safe is shared across chains (matched-address deploy), so the script reads `LibSafeInvariants.STOX_TOKEN_OWNER_SAFE` directly instead of a per-chain `ChainPrincipals`. Removes the `_principals()` helper and the last `LibChainPrincipals` reference in the stack; the Safe's on-chain existence + policy alignment is still gated by `LibSafeInvariants.assertAll` in `authorizeTokens()` / `verify()`. Verified: DeployTokensEthereumTest passes (4/4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
The token-owner Safe is a distinct per-chain address, so the Ethereum token-deploy script targets STOX_TOKEN_OWNER_SAFE_ETHEREUM (not Base's Safe): - New `_assertSafeReady()` helper: reverts EthereumSafeNotReady until the Safe address is pinned, then asserts the live Safe matches Base's policy with assertPolicyMatchesBase (order-insensitive — a fresh per-chain Safe's owner order is incidental). Used by run() (after the core pre-flight), authorizeTokens() and verify(). - Tests: the Safe-not-ready guard is now the forcing function (testAuthorize/VerifyRevertsWhenSafeNotReady expect EthereumSafeNotReady). Dropped the matched-address Base-fork clone tests — the Ethereum Safe is no longer present on a Base fork, so the Safe guard fires first; the clone guard is now reachable only once the Safe is pinned. Verified: 3/3 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
…ddress(0) The Ethereum Safe is pinned (#232), so the token-deploy pre-flight reverts through assertPolicyMatchesBase's proxy-codehash check when the pinned Safe isn't live on the active chain (no code with no fork), rather than through the address(0) EthereumSafeNotReady guard. Update the authorize/verify inverted tests to expect SafeProxyCodehashMismatch. Stable regardless of the Safe's on-chain threshold. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
…e shared invariants
Three dispatch-blocking bugs, all of the same class the authoriser-clone
broadcast hit (assertions CI never exercised on an Ethereum fork):
1. The pre-flight pinned the `LibProdDeployCurrent` (latest-tag) deployers —
that generation has no code on Ethereum (only the 0.1.1 bootstrap set is
live), so `run()` reverted `DeployerNotDeployed` at dispatch. Worse, even
when the current tag lands, deploying through it would wire the new vault
proxies onto an unadopted beacon set. Pin the 0.1.1 deployers: the set
the bootstrap shipped, whose beacon set IS the chain's in-use production
beacons.
2. `run()` read `vm.envUint("DEPLOYMENT_KEY")` but `manual-broadcast.yaml`
supplies the key via `--private-key` only. Broadcast via
`vm.startBroadcast()` + `msg.sender`, matching the 20260619 pattern.
3. The only test was a no-fork pre-bootstrap revert check. Add an Ethereum
live-fork test that proves every landed pre-flight gate (0.1.1 core,
in-use beacon ownership) passes against real chain state and stops at
the first unlanded gate (the clone pin) — the forcing function.
Shared-invariant wiring (same assertions in the script as in CI):
- `LibBeaconInvariants.assertProdBeaconsOwnedByChainSafe(block.chainid)` in
the pre-flight — the beacons every proxy deployed here delegates through
must be owned by this chain's Safe.
- `_assertSafeReady` resolves + policy-asserts via
`assertActiveChainTokenOwnerSafe`, then guards the result is ETHEREUM's
Safe so a wrong-network dispatch reverts instead of deploying duplicate
tokens onto that chain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
Replace the hand-rolled 3-function IReceiptVaultAdmin with canonical homes: receipt() via rain-vats IReceiptVaultV3, transferOwnership via OZ Ownable. Only setAuthorizer keeps a local one-selector mirror (typed with IAuthorizeV1) — rain-vats ships no interface carrying it and importing the concrete vault would drag its full storage inheritance in, the same trade the 20260623 swap script documents. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
…eployed "clone" is ambiguous in the token-deploy script — the unified deployer also produces (nonce-based) clones, and the thing the vaults are wired onto is simply the chain's V4 authoriser. Rename the local surface (EthereumCloneNotReady -> EthereumAuthoriserNotReady, _assertCloneReady -> _assertAuthoriserReady, `clone` -> `authoriser`), keeping "clone" only where it describes the EIP-1167 deploy mechanics behind the `_CLONE` pin names. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr
12e722f to
e822482
Compare
a9c983c to
849ddcc
Compare
Nothing stopped a re-dispatch. Every other pre-flight passes once the deploy has succeeded -- deployers live, authoriser live, Safe live -- so a second run would mint 28 more production tokens, nonce-addressed and indistinguishable from the real ones except by deploy order, and hand them to the Safe. The only thing in the way was a human reading a dropdown that says outright it lists which scripts exist, not which have run. The token table is that record, so the guard reads it and refuses on any hydrated leg. setAuthorizer and transferOwnership are read back per token. Both happen in the same broadcast, and the acceptance test that would catch a silent miss does not run until the pin PR lands -- until then a missed handoff is a production vault owned by a CI key. Both checks take their subject as an argument rather than reaching for pinned state, so each can be driven against the failing case. A check reachable only from inside a broadcast loop can be shown not to fire and never shown to fire. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Reviewed 12bf0dc: approve Run 29981094045 leaves one test failure — The wiring is right, and the pre-flight is the strongest in the stack: the chain guard reverts unless the resolved Safe is Ethereum's (so a misdispatched network cannot deploy a duplicate set elsewhere), the authoriser is checked by codehash rather than a bare Two gaps found in review, both now fixed on this head: No run-once guard. The sibling clone script refuses to re-run via No post-handoff verification. Also added Both checks take their subject as an argument rather than reaching for pinned state, which is what makes them testable — a check reachable only from inside a broadcast loop can be shown not to fire and never shown to fire. 8 tests, and all three mutants killed against a committed baseline: guard-checks-row-0-only, Two issues filed from this review, neither blocking: rainix#278 on whether the one-contract-per-file gate should count interfaces, and rain.vats#330 — |

Stands up the full ST0x production token set on Ethereum in a single
deploy-key broadcast, matched to Base — no Safe signature required.
20260706-deploy-tokens-ethereum.s.sol— one operation (run()),broadcast from the CI deploy key. Mirrors the authoriser-clone deploy
pattern: the deploy key deploys, configures, then self-relinquishes. Per
token, in order:
StoxUnifiedDeployer.newTokenAndWrapperVaultwithinitialAdmin= thedeploy key and the Base-verbatim name/symbol — the deploy key transiently
owns the receipt vault (the wrapped vault has no owner).
setAuthorizer(clone)on the receipt vault while the key is still owner.transferOwnership(Safe)— single-step OZOwnable, so ownership landson the Safe with no accept step. The deploy key then holds nothing.
Pre-flight requires the core deployers, the V4 authoriser clone (the
setAuthorizertarget) and the Safe (the handoff target) all live, so thebootstrap order (Safe → clone → tokens) is enforced. Deployed addresses are
nonce-based (not known ahead), so it logs each
(underlying, receiptVault, wrapped)for the post-execution pin PR to hydrateproductionTokensEthereum().Name/symbol come from the canonical
LibProdTokenConfig(introduced in thebranch below), validated against live Base — SGOV's leading-space name and
all — so the tokens reproduce Base byte-identically.
Workflow — listed in
manual-broadcast.yaml(the deploy-key broadcastdispatcher), targeting the chosen
network. It is NOT a Safe-artifactscript, so it is not in
run-script.yaml.Pre-flight guard test fires while the pins are pending; red until the Ethereum
Safe + clone are live, by design.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VPs1hCTxusmaSeFKvoc4Kr