Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -2371,8 +2371,8 @@ st0x.deploy audit report covering the orchestrator.
**pre-burn pointer value** (0 on a fresh orchestrator, where receipt ids start
at 1), consistent with the pointer-movement reading above.
- Roles: `MINT_ROLE` and `BURN_ROLE` (held by the bot wallet, gating the two
entry points above), `EMERGENCY_ROLE` (manual recovery: moving receipts
between wallets, adjusting the burn pointer).
entry points above), `EMERGENCY_ROLE` (manual recovery: moving receipts or
shares out of the orchestrator, adjusting the burn pointer).
- Decodable revert reasons: `NonceReplayed`, `BadRecipientSignature`,
`RecipientCallbackRejected`, `InsufficientReceipts`, `VaultLogicMismatch`,
`ReceiptLogicMismatch`, `VaultAmountMismatch`. See "Failure States" below for
Expand Down Expand Up @@ -2689,12 +2689,12 @@ runbook is authored and executed for the pilot in RAI-1222). Subsequent assets
follow the same per-asset procedure (RAI-1246); the end state flips
`[orchestrator].default_vault_mode` to `"orchestrator"` and drops the per-asset
overrides. Rollback is the same procedure in reverse for just the affected
asset: freeze, flip its `vault_mode` back to `"vault_direct"`, return that
token's receipts to the bot wallet via `EMERGENCY_ROLE`, check on-chain that the
orchestrator holds none of the vault's receipt ids, redeploy (startup
rediscovers the returned receipts — see "Receipt custody"), unfreeze — no other
asset is touched. Vault-direct mode's flows, aggregate states, and events are
completely unchanged by this migration.
asset: freeze, return that token's receipts to the bot wallet via
`EMERGENCY_ROLE`, check on-chain that the orchestrator holds none of the vault's
receipt ids, flip its `vault_mode` back to `"vault_direct"` and redeploy
(startup rediscovers the returned receipts — see "Receipt custody"), unfreeze —
no other asset is touched. Vault-direct mode's flows, aggregate states, and
events are completely unchanged by this migration.

**Both modes run side by side for the whole rollout.** While any asset remains
vault-direct, `ReceiptInventory` and the receipt-monitoring/backfill machinery
Expand Down Expand Up @@ -2860,10 +2860,12 @@ the pool is drained between the simulation and the mined transaction.
startup, and via the manual admin recovery endpoint) does not resubmit a
deterministically-reverting burn — only the manual re-drive below does, once
the underlying shortfall is fixed.
- Recovery is a manual `EMERGENCY_ROLE` action (moving receipts back in, or
adjusting the burn pointer). Once the operator fixes the shortfall on-chain,
the existing admin path — `POST /admin/recover/redemption/<id>` ->
`ResumeBurn` — resumes the redemption. No new recovery machinery.
- Recovery is manual. The holder of the missing receipts transfers them back to
the orchestrator with a plain ERC-1155 transfer, or the `EMERGENCY_ROLE`
holder lowers the burn pointer (`setBurnIndex`); no `EMERGENCY_ROLE` action
moves receipts in. Once the operator fixes the shortfall on-chain, the
existing admin path — `POST /admin/recover/redemption/<id>` -> `ResumeBurn` —
resumes the redemption. No new recovery machinery.
- Affected redemptions are individually visible today via the existing
`GET /admin/stuck` (`Failed` state) and are identifiable by their
Comment thread
rain-marvin[bot] marked this conversation as resolved.
`classification: BurnFailureClassification::InsufficientReceipts` field. A
Expand Down
61 changes: 55 additions & 6 deletions config.prod.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,69 @@
# mint/burn path.
default_vault_mode = "vault_direct"

# ST0xOrchestrator, one deployment per chain at the same pinned address. Only
# Robinhood Chain (4663) is recorded so far; the other chains' entries land
# with their own cutovers.
# ST0xOrchestrator, one deployment per chain at the same pinned address.
#
# Verified live on Robinhood: eip712Domain() answers name "ST0xOrchestrator",
# version "1", chainId 4663, verifyingContract equal to this address, and the
# deployed code hashes equal to Base's instance.
# EVERY supported network is listed, not just the ones a cutover is imminent
# for. `vault_mode` is keyed by SYMBOL, so the first asset to resolve to
# orchestrator mode does so on every chain this deployment runs, and startup
# then demands an entry for each of them (`Env::into_config` iterates the
# running chain set from the CHAIN_<NETWORK>_* groups, not the asset's
# listings — see the deliberately over-strict block in src/config.rs). A
# missing entry is a refusal to start, never a silent vault-direct fallback,
# so listing every network is what keeps enabling a chain group from
# bricking startup.
#
# Verified live on EVERY network listed below: eip712Domain() answers name
# "ST0xOrchestrator", version "1", the chain's own chainId, and
# verifyingContract equal to this address; the deployed proxy code hashes
# match across instances. One deployment per chain at the same pinned
# address, as the heading says.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#
# Each address is a BeaconProxy, so equal proxy code hashes do NOT prove
# equal logic: the proxy stub is the same on every chain whatever its beacon
# points to, and the beacons have different owners. Before the chain's asset
# flips, re-run the domain read and read the beacon's implementation hash per
# chain, and record the outputs in the prod-facts table
# (docs/runbooks/orchestrator-onboarding.md, step 7's per-chain
# `eip712Domain()` / implementation-hash check) — this comment is the claim,
# the table is the evidence. The preflight and
# `approve-orchestrator` both refuse an address that does not answer as an
# orchestrator, so a wrong entry here fails those gates rather than reaching
# the mint/burn path.
#
# Recording the address stays inert until an asset resolves to orchestrator
# mode (`VaultModeConfig::mode_for` is the only reader), so this is the
# onboarding tooling's source of truth ahead of the first cutover, not a live
# dependency of the mint/burn path.
[orchestrator.addresses]
base = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"
Comment thread
rouzwelt marked this conversation as resolved.
ethereum = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"
hyperevm = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"
robinhood = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"
binance = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"

# RKLB pilot switch. It stays commented out until the cutover reaches step 12
# of docs/runbooks/orchestrator-onboarding.md.
#
# Switch on (step 12): after the receipts are moved and verified, remove the
Comment thread
rain-marvin[bot] marked this conversation as resolved.
# "# " from the two lines below. With the deploy hold still armed, deploy the
# system profile (`prodDeployNixos`): only it changes the unit's `CONFIG` path.
# Then release the hold and deploy the service. `vault_mode` is keyed by
# symbol, so RKLB switches on every chain it is listed on, each with its
# address above. Make the switch a reviewed commit on main that also lets the
# dark test (`deploy_config_files_parse_and_stay_dark` in src/config.rs) accept
# it. A switch that is not on main is undone by the next release from main.
#
# Switch off (step 14, rollback): comment the two lines out again (the default
# above is "vault_direct"; an explicit `vault_mode = "vault_direct"` is still an
# override, which the restored dark test refuses). Prepare it as a reviewed
# commit on main that also restores the dark test, but merge and
# deploy it only after the receipts are back at the bot wallet (rollback step
# 6). Deploy it the same way, with the system profile before the hold is
# released.
#
# [assets.RKLB]
Comment thread
rouzwelt marked this conversation as resolved.
# vault_mode = "orchestrator"

# Wrapped-token (ERC-4626 wrapper) contracts watched for inbound transfers to
# the issuer wallet, which cannot be redeemed and so are alerted on instead
Expand Down
44 changes: 38 additions & 6 deletions config.staging.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,52 @@
# mint/burn path.
default_vault_mode = "vault_direct"

# ST0xOrchestrator, one deployment per chain at the same pinned address. Only
# Robinhood Chain (4663) is recorded so far; the other chains' entries land
# with their own cutovers.
# ST0xOrchestrator, one deployment per chain at the same pinned address.
#
# Verified live on Robinhood: eip712Domain() answers name "ST0xOrchestrator",
# version "1", chainId 4663, verifyingContract equal to this address, and the
# deployed code hashes equal to Base's instance.
# EVERY supported network is listed, not just the ones a cutover is imminent
# for. `vault_mode` is keyed by SYMBOL, so the first asset to resolve to
# orchestrator mode does so on every chain this deployment runs, and startup
# then demands an entry for each of them (`Env::into_config` iterates the
# running chain set from the CHAIN_<NETWORK>_* groups, not the asset's
# listings — see the deliberately over-strict block in src/config.rs). A
# missing entry is a refusal to start, never a silent vault-direct fallback,
# so listing every network is what keeps enabling a chain group from
# bricking startup.
#
# Verified live on EVERY network listed below: eip712Domain() answers name
# "ST0xOrchestrator", version "1", the chain's own chainId, and
# verifyingContract equal to this address; the deployed proxy code hashes
# match across instances. One deployment per chain at the same pinned
# address, as the heading says.
#
# Each address is a BeaconProxy, so equal proxy code hashes do NOT prove
# equal logic: the proxy stub is the same on every chain whatever its beacon
# points to, and the beacons have different owners. Before the chain's asset
# flips, re-run the domain read and read the beacon's implementation hash per
# chain, and record the outputs in the prod-facts table
# (docs/runbooks/orchestrator-onboarding.md, step 7's per-chain
# `eip712Domain()` / implementation-hash check) — this comment is the claim,
# the table is the evidence. The preflight and
# `approve-orchestrator` both refuse an address that does not answer as an
# orchestrator, so a wrong entry here fails those gates rather than reaching
# the mint/burn path.
#
# Recording the address stays inert until an asset resolves to orchestrator
# mode (`VaultModeConfig::mode_for` is the only reader), so this is the
# onboarding tooling's source of truth ahead of the first cutover, not a live
# dependency of the mint/burn path.
[orchestrator.addresses]
base = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"
ethereum = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"
hyperevm = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"
robinhood = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"
binance = "0x3A7387a484d87Aa8bBA45E98AAB401Ce4FBF03E2"

# The RKLB pilot (docs/runbooks/orchestrator-onboarding.md) is prod only, so
# staging stays vault-direct, with no asset override. A merge to main deploys
# this file to the GCP staging VM at once (`roll-staging` in build-oci.yml),
# with no deploy hold, no freeze and no receipt move, so an override here would
# switch staging without a cutover.

# Wrapped-token (ERC-4626 wrapper) contracts watched for inbound transfers to
# the issuer wallet, which cannot be redeemed and so are alerted on instead
Expand Down
14 changes: 13 additions & 1 deletion docs/runbooks/deploy-hold.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,19 @@ hold and starting the service.
## Release the hold

Release the hold only after the maintenance procedure and post-operation checks
are complete. Remove the hold on the target host:
are complete.

If the maintenance changed the TOML config (for example a `vault_mode` flip),
deploy the system profile first, while the hold is still armed. The service
deployment below restarts the existing unit, and the unit's `CONFIG` path
changes only with the system profile:

```sh
nix run .#prodDeployNixos -- -i "$SSH_IDENTITY"
# Staging: nix run .#stagingDeployNixos -- -i "$SSH_IDENTITY"
```

Remove the hold on the target host:

```sh
rm /run/st0x/st0x-issuance.hold
Expand Down
Loading
Loading