Skip to content

Orchestrator instance deploy script + pinned invariants - #316

Merged
thedavidmeister merged 1 commit into
mainfrom
2026-08-18-deploy-orchestrator-instance
Aug 27, 2026
Merged

Orchestrator instance deploy script + pinned invariants#316
thedavidmeister merged 1 commit into
mainfrom
2026-08-18-deploy-orchestrator-instance

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Stacked on #314 (the audited 0.1.30 orchestrator-set workflow) — merge that first.

Why

#314 makes the orchestrator singletons deployable, but creating the production orchestrator instance (ST0xOrchestratorBeaconSetDeployer.deploy(owner)) had no sanctioned path. This adds it in the token-deploy pattern: a dated manual-broadcast script that broadcasts as the CI deploy key and lands DEFAULT_ADMIN_ROLE on the chain's token-owner Safe, with the result pinned and asserted the way the token beacons and token instances are.

What

  • script/20260818-deploy-orchestrator.s.sol (PENDING, registered in manual-broadcast.yaml, one dispatch per network): pre-flights the audited 0.1.30 closure by codehash, validates the chain's token-owner Safe policy, then calls deploy(safe) and asserts the end state. One divergence from 20260807-deploy-missing-tokens, documented in the NatSpec: there is no owner-gated wiring step (no setAuthorizer analogue), so the Safe is passed straight to deploy(owner) and the deploy key never holds DEFAULT_ADMIN_ROLE at all (asserted). Operational MINT_ROLE/BURN_ROLE grants for the issuance bot's signer stay Safe transactions per the issuance-side onboarding runbook.
  • Pins live in LibProdDeployV4 (emitted by BuildPointers beside the authoriser-clone pin) and are aliased by the new LibOrchestratorInvariants; testOrchestratorBeaconPin / testOrchestratorInstancePin re-derive both from the beacon-set deployer pin via the CREATE/RLP derivation, so a drifted literal fails a test. The whole surface is deterministic, so it is pinned up front rather than hydrated from the broadcast:
    • beacon 0xb9DCd744…4613 = the set deployer's CREATE at nonce 1 (CREATE nonce-1 of the 0.1.30 set-deployer);
    • instance 0x3A7387a4…03E2 = the first deploy()'s BeaconProxy, the set deployer's CREATE at nonce 2 — identical on every chain.
      Asserts: beacon reported by the set deployer, beacon impl = the audited 0.1.30 orchestrator, beacon owner = BEACON_INITIAL_OWNER, instance admin = chain Safe, vaultLogicIsExpected() true.
  • Drift guards: a live pinned instance ⇒ OrchestratorAlreadyDeployed (re-dispatch refusal, like NoMissingTokens); set-deployer nonce ≠ 2 without code at the pin ⇒ UnexpectedSetDeployerNonce (an unpinned earlier deploy() — resolve manually, never deploy a second instance). The pin, not Deployment-event discovery, is the source of truth — deploy is permissionless and the event is front-runnable (see the interface NatSpec).
  • test/script/20260818-deploy-orchestrator.prod.t.sol — live-fork rollout walk per chain (no mocks), with a 2026-10-01 deadline after which a still-pending chain red-lines cron:
    1. closure pending (Ethereum/HyperEVM today) → pins the ClosureNotDeployed refusal + logs the outstanding dispatch;
    2. instance pending (Base today) → drives run() end to end on the fork: instance lands at the pin, Safe 0xe70d…d611 holds admin, deploy key holds nothing, vault-logic lock passes, ERC-1967 beacon slot = pinned beacon;
    3. executed → steady-state instance asserts (the orchestrator analogue of the token-instance prod asserts) + the re-dispatch refusal. Never expires.
  • test/script/20260818-deploy-orchestrator.t.sol — every guard and assertDeployLanded failure mode shown to fire without a fork (11 tests).

Verification

  • Unit: 15/15 (incl. the nonce-1/nonce-2 pin-derivation tests). Prod fork: 3/3 — all chains assert the closure-pending refusal.
  • slither . 0 results; forge fmt --check, reuse lint clean.

Rollout order per chain

  1. manual-sol-artifacts-0-1-8 suites in listed order (no-ops on Base) — Deploy workflow for the audited 0.1.30 orchestrator set #314
  2. manual-broadcast20260818-deploy-orchestrator (this PR)
  3. Safe: grant MINT_ROLE/BURN_ROLE to the issuance bot's signer; then the issuance-side preflight/cutover runbook

Flag (not in scope)

The orchestrator beacon's owner is still BEACON_INITIAL_OWNER (0x8E4b…f5b) on Base — it never joined the beacon-owner migration that moved the token beacons to the per-chain Safes. The invariant pins that as current truth; if the beacon should follow the token beacons (or the timelock), that's a separate Safe op + pin flip.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KbsbYN4C4YDa8pu9DdudoX

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1675b9d3-28a5-49c2-8fd0-5b7b25b56a62

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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 force-pushed the 2026-08-17-orchestrator-manual-sol-artifacts branch from 76e72a1 to 1c7e986 Compare August 18, 2026 11:07
@hardyjosh
hardyjosh force-pushed the 2026-08-18-deploy-orchestrator-instance branch from d9c6bea to 4271d04 Compare August 18, 2026 11:07
@hardyjosh
hardyjosh force-pushed the 2026-08-17-orchestrator-manual-sol-artifacts branch from 1c7e986 to 511811c Compare August 25, 2026 08:09
@hardyjosh
hardyjosh force-pushed the 2026-08-18-deploy-orchestrator-instance branch from 4271d04 to e7eb280 Compare August 25, 2026 08:09
hardyjosh added a commit that referenced this pull request Aug 25, 2026
## Why

Main's test CI (and cron) has been red since **2026-08-22**, and #314's inherits it: the `20260810-revoke-fireblocks-service-signer` Safe bundles **executed on all three chains** (verified live — the retired signer `0x1C66…D1A9` holds no `DEPOSIT`/`WITHDRAW`/`CERTIFY` on Base, Ethereum, or HyperEVM), which flipped its forcing-function tests red by design: they demand this pin PR.

## What (per the script's own retire instructions)

- **Canonical grant map**: the retired signer's three rows leave `expectedGrants` (16 → 13 entries). The `GRANTEE_SERVICE_1C66` constant stays as the audit-trail record.
- **The revocation is pinned as an absence**: `assertExpectedGrants` gains `assertRetiredSignerAbsent` — any action role landing back on the retired signer red-lines cron with the new `UnexpectedRetiredSignerGrant`. Strictly stronger than the old presence rows.
- **Spent fixtures retire**: the script, its `run-script.yaml` registry entry, the `.prod.t.sol` forcing tests, the unit tests, and both harnesses are deleted — the standing absence assertion supersedes their pre-flight per the registry's deletion rule.

## Consumers of the retired rows (second round — surfaced by CI, masked locally by RPC rate limits)

- `20260619-deploy-v4-authoriser-clone`: `MIRROR_COUNT` 9 → 6 (the operational slice is now Safe + service signer) plus its replica test.
- `20260729-migrate-governance-to-timelock`: the governance-loop proof's sample no-op op re-granted `DEPOSIT` to the retired signer — post-revocation that's a real grant and trips the absence check. Now re-grants to the live signer (`GRANTEE_SERVICE_3D0C`, a genuine no-op again).
- `20260723-provision-additional-service-signer` drift test: drift is now created by revoking the Safe's `DEPOSIT` row instead of the departed retired-signer row.

## Verification

Against live forks on all three chains: the full previously-red set passes — provisioning prod tests (`AlreadyProvisioned` refusals restored), timelock-migration surface, cross-chain parity, Base prod-state, Ethereum clone pin. `slither .` 0, fmt/reuse clean.

## Merge order

This merges **first**; the orchestrator stack (#314#316#317) restacks on it and its inherited test failures disappear.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01KbsbYN4C4YDa8pu9DdudoX


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Updates**
  * Retired service signer access has been removed from the expected authorization configuration.
  * Authorization grants now reflect six operational grants, with Safe grants listed before service grants.
  * Governance validation now uses the active service signer for `DEPOSIT`.
  * Added checks to prevent accidental regranting of roles to the retired signer.

* **Maintenance**
  * Removed the retired signer revocation workflow and its associated validation tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
20260818-deploy-orchestrator (manual-broadcast, per network): one
deploy-key broadcast that calls the 0.1.8 beacon-set deployer's
deploy(owner) with the chain's token-owner Safe, so the Safe holds
DEFAULT_ADMIN_ROLE from initialisation and the deploy key never holds
anything (asserted). Unlike the token deploys there is no owner-gated
wiring step, so no transient deploy-key ownership window exists.

The whole surface is deterministic and pinned UP FRONT in the new
LibOrchestratorInvariants: the beacon is the set deployer's CREATE at
nonce 1 (0xbA0d89f6, matches live Base) and the first deploy()'s
BeaconProxy is its CREATE at nonce 2 (0x21372a9D), identical on every
chain. Pre-flight enforces the audited 0.1.8 closure by codehash,
refuses a live pinned instance, and refuses a set-deployer nonce that
shows an unpinned earlier deploy().

Prod fork test walks the rollout's three states per chain with a
2026-10-01 deadline: closure pending (Ethereum/HyperEVM — pins the
ClosureNotDeployed refusal), instance pending (Base — drives run() end
to end on the fork and asserts the pinned end state incl. the ERC-1967
beacon slot), executed (steady-state instance asserts, the
orchestrator analogue of the token-instance prod asserts, plus the
re-dispatch refusal). Guard coverage without a fork in the unit test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbsbYN4C4YDa8pu9DdudoX
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