Docs/Engineering/Deployment Ceremony & Multisig Hygiene
accepted

Deployment Ceremony & Multisig Hygiene

Step-by-step ceremony for deploying ARU contracts, bootstrapping minter timelock, onboarding APs, and operating the multisigs. Written to be followed cold.

Deployment Ceremony & Multisig Hygiene

Audience: protocol operators, multisig signers. Cadence: deployment (once), plus AP onboarding and settlement (recurring).

Why a ceremony

The contracts assume a disciplined multisig. Every scenario in the Threat Model that isn't mitigated on-chain is mitigated here. If this doc isn't followed, the audit findings don't matter.

Multisig composition

Multisig Suggested threshold Signers Key storage
Owner (cold) — owns ARU, rarely used 2-of-3 Founding team + legal counsel Air-gapped hardware (Ledger/Trezor), recovery phrases in geographically-separated sealed envelopes
Manager Admin (operational) — settles epochs, pauses, approves per-request settlements 3-of-5 (4 humans + AutoSigner) Ops + eng + eng backup + counsel + AutoSigner service Hardware wallets for humans; HSM-backed cloud key for AutoSigner
Oracle Admin — sets safety params 2-of-3 Same as Manager Admin OR separate if org permits Hardware
Treasury — holds USDC 2-of-3 Finance + ops + backup Hardware, Gnosis Safe
Redstone Price Updater Single key (hot) Automated relayer Cloud HSM or equivalent; rotated quarterly

Never share signers between Owner and Manager Admin multisigs. Any single compromise should not bridge both.

AutoSigner participation (recommended, see Auto-Signer for full spec):

  • Manager Admin becomes 3-of-5 instead of 2-of-3
  • 4 humans + 1 AutoSigner service slot
  • Routine flows: AutoSigner signs within policy → 2 humans countersign → 3-of-5 met
  • Override flows: 3 humans without AutoSigner → 3-of-5 met
  • AutoSigner is never required — humans always retain full quorum capability
  • AutoSigner signs only specific function selectors on specific contracts (approval functions only; refuses all admin config changes)

Pre-deployment checklist

  • foundry.toml pins solc = "0.8.24" (current)
  • lib/openzeppelin-contracts submodule pinned to a specific tag, not tracking main. Verify with git submodule status; record the commit hash in the deployment log.
  • All tests pass: forge test -vvv
  • Invariant suite passes with 1024 runs: forge test --match-path 'test/invariant/*.t.sol' --invariant-runs 1024
  • Slither run has no unreviewed findings: slither . --filter-paths lib/
  • Deployment bytecode matches the audited source — capture forge inspect <contract> bytecode and diff against the audited commit 1153227 (Nethermind NM-0902; see Audit History)
  • Gas snapshot captured: forge snapshot
  • script/Deploy.s.sol reviewed by at least 2 engineers against the constructor argument plan below

Price ceremony

The ARUPriceOracle.initialPrice bypasses the rate-of-change guard (constructor-set). Getting it right is non-negotiable.

Process:

  1. Two signers independently compute NAV using the same published methodology — in writing, from the same source data timestamp.
  2. Both values submitted to a shared doc.
  3. Deploy only proceeds if they agree to within the equivalent of 10 bps.
  4. Chosen value is the mean, rounded to 8 decimals (int256 scaled).
  5. The value, source data, and both signers' worksheets are attached to the deployment log.

Who can be signer #1 and #2: any two members of the Owner or Manager Admin multisig, but not the same human using two keys. Real separation.

Deployment order

  1. Deploy ARU with the cold owner multisig as initialOwner. Initial minter = owner (self).

  2. Deploy ARUMintRedeemManager with:

    • usdc = canonical chain USDC (e.g., 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 on Arbitrum)
    • aru = deployed ARU address
    • treasury = treasury Safe address
    • managerAdmin = operational multisig
    • minimumMintAmount = 10,000 USDC (6-dec)
    • minimumRedeemAmount = 10,000 ARU (18-dec)
  3. Deploy ARUPriceOracle with:

    • admin = oracle admin multisig
    • priceUpdater = Redstone relayer hot key
    • initialPrice = output of the price ceremony
    • maxPriceChangeBps = 300 (3%)
    • minUpdateInterval = 3,600 (1h)
    • stalenessThreshold = 90,000 (25h)
    • feedId = Redstone feed identifier
  4. Post-deploy verification (same tx batch or immediately after):

    • Read back all constructor values from each deployed contract
    • Confirm DEFAULT_ADMIN_ROLE is the intended multisig on Manager and Oracle
    • Confirm PRICE_UPDATER is set to relayer only (no extras)
    • Confirm deployer EOA has no residual admin roles

Bootstrap: granting minter rights

ARU uses a multi-minter design (v2). Both the v1 epoch-batched manager and the v2 per-request manager need mint/burn rights. Each addition is subject to a 48h owner timelock.

For each manager to grant minter role:

  1. Cold owner multisig calls ARU.proposeAddMinter(managerAddress).
  2. Wait 48 hours. During this window, owner can cancelMinterChange(managerAddress) if anything looks wrong.
  3. Anyone calls ARU.executeMinterChange(managerAddress) — public by design, provides transparency after timelock elapses.
  4. Confirm with ARU.isMinter(managerAddress) read.

Repeat for both v1 and v2 manager addresses.

After both managers have mint rights, remove the bootstrap owner-as-minter:

  1. Cold owner multisig calls ARU.proposeRemoveMinter(ownerAddress).
  2. Wait 48 hours.
  3. Anyone calls ARU.executeMinterChange(ownerAddress).
  4. Confirm with ARU.isMinter(ownerAddress) returning false.

Do not skip the wait. Do not combine propose + execute. Do not skip the bootstrap-minter removal — leaving the owner as a minter preserves a centralization vector.

Bootstrap: onboarding APs

For each AP:

  1. AP completes off-chain KYC and signs AP agreement.
  2. Operational multisig calls Manager.addAP(apAddress).
  3. Confirm via Manager.isAP(apAddress) read.
  4. Notify AP. Their dashboard now shows "Verified AP".

Record every AP onboarding in a standing registry doc (address, date, legal entity, deactivation date if applicable).

Ongoing: settlement runbook

Per the idempotent batching in Settlement Correctness:

  1. Before completeMints / completeRedemptions:

    • Pull pending requests list from the subgraph; cross-check against on-chain epochTotalMintCollateral[epoch] and epochTotalRedeemARU[epoch].
    • Compute settlement price against published NAV methodology.
    • Split AP list into batches of ≤ MAX_BATCH_SIZE (start at 50, tune based on gas).
    • Simulate every batch tx on a fork: forge script Simulate --fork-url $RPC --sender $ADMIN. Attach output to the signing request.
  2. During drain:

    • Execute batches sequentially. Each call emits SettlementBatchCompleted.
    • If a batch reverts, do not keep submitting — diagnose before proceeding.
    • Monitor on-chain epochTotalMintCollateral[epoch] drain to zero.
  3. Close:

    • Once epochTotalMintCollateral[epoch] == 0, call closeEpochMints(epoch).
    • Same for redeem side.
    • Record close block and settlement IDs in ops log.
  4. Exception path: if an AP is missed mid-drain (e.g., hardware wallet issue), that's fine under the new design — schedule another drain batch. The AP's request is still claimable because epochMintSettled is not yet flipped.

Ongoing: oracle heartbeat

Relayer configuration:

  • Write at least every stalenessThreshold / 2 (12.5h under defaults), even if price unchanged.
  • setPrice with the same value is allowed (rate-of-change = 0 bps < 300).
  • Alert oncall if relayer gap exceeds stalenessThreshold * 0.5.

Key rotation

Event Action
Signer leaves the org Rotate that role's multisig within 48h. Cold multisig uses proposeOwnershipTransfer → 48h wait → execute. Manager/Oracle multisigs can swap signers via Safe UI.
Hardware wallet lost Revoke signer, add replacement
Suspected compromise Immediately pause (pauseSubscriptions + pauseRedemptions), rotate keys, investigate
Relayer key every 90 days Generate new key, grant PRICE_UPDATER to new key, revoke old, update relayer config

Incident response

Compromised MANAGER_ADMIN:

  1. Other multisig members attempt to revoke the compromised signer via Safe.
  2. If quorum is intact, call pauseSubscriptions + pauseRedemptions.
  3. If quorum is lost (attacker has majority), ARU.owner calls proposeMinterChange(newManager) — 48h window lets you deploy a fresh Manager and migrate.
  4. Communicate outage to APs via the dashboard status banner.

Compromised oracle:

  1. Pause subscriptions and redemptions (mint/redeem depend on price).
  2. Rotate PRICE_UPDATER via ADMIN_ROLE.revokeRole + grantRole.
  3. Investigate data path; resume only after root cause identified.

Compromised treasury Safe:

  1. Admin calls Manager.setTreasuryAddress(newSafe).
  2. Transfer USDC holdings to new Safe (requires old Safe still quorate; otherwise this is a legal/recovery situation outside the contract).
  3. Re-approve new Safe → Manager for refund/redemption safeTransferFrom.

Deployment log template

For every deployment, archive (git-committed or stored with legal):

  • Commit hash of aru-evm deployed
  • Commit hash of lib/openzeppelin-contracts submodule
  • Solc version + compiler flags
  • Constructor args for each contract (in hex and decoded)
  • Deployed addresses
  • Transaction hashes
  • Price ceremony worksheets
  • Signatures of: 2 engineers, 1 legal, 1 ops