CROWNRIDGE
--:--:--Z
CROWNRIDGE/DOCS/INCIDENT RESPONSE
[INCI]Incident response← ALL DOCS

Crownridge Incident Response

Status: pre-mainnet procedure document (spec §87, §165). Crownridge is not deployed to mainnet and has not received an external audit. This document must be finalized — with real deployed addresses from deployments/<chainId>-latest.json, confirmed multisig signer sets, and a completed incident drill — before any mainnet launch (spec §88, §164). Nothing here is a guarantee of loss prevention; it is the operating procedure for limiting and disclosing damage.

This document covers: who acts, required response time, the exact contract and function to call, the multisig/timelock procedure, the communication process, and the postmortem process — for every material incident scenario in V1.


1. Actors and authorities

Actor On-chain identity Powers (exact)
Guardian (security multisig) SECURITY_MULTISIG PAUSER_ROLE on CrownridgeGenesis, CrownridgeBuyback, CrownridgeMintController (pause() / unpause()); GUARDIAN_ROLE on CrownridgeTreasury (setFounderWithdrawPaused(bool)); CANCELLER_ROLE on the TimelockController (cancel(bytes32 id)). All immediate — no delay.
Timelock (OZ TimelockController, 48h min delay — TIMELOCK_MIN_DELAY) deployed per manifest DEFAULT_ADMIN_ROLE on every protocol contract (role grants/revocations), PARAMETER_ADMIN_ROLE (Genesis/Buyback params, Buyback.setEnabled), RESERVE_MANAGER_ROLE on Treasury (writeDownRecognized, sweepExcess, rescueToken) and LiquidityManager (windDown). Everything it does is visible on-chain for 48h before execution.
Protocol multisig PROTOCOL_MULTISIG Sole PROPOSER_ROLE on the Timelock — the only account that can queue timelocked operations. Has no direct power over any protocol contract.
Buyback executor BUYBACK_EXECUTOR (ops address) BUYBACK_EXECUTOR_ROLE: may call Buyback.executeBuyback only; every safety check binds on-chain regardless of this caller.
Founder immutable founder in FounderTreasuryController May call FounderTreasuryController.withdraw(recipient, amount), which calls Treasury.founderWithdraw. The only human-directed reserve outflow. Disclosed trust assumption (see §7 and docs/RISK.md).
Anyone The Timelock's executor role is open (address(0)): once a queued operation's 48h delay elapses and it has not been cancelled, anyone may call execute. Useful in an emergency — execution cannot be bottlenecked on one key.

Verified in contracts/script/Deploy.s.sol (_wire, _handOff, _postAssert): the deployer retains no role anywhere post-deploy; the Timelock is self-administered; the guardian holds CANCELLER.

The two speeds — the core principle

Pauses are immediate. Everything else is timelocked (48h).

The guardian can stop any subsystem now; it cannot move funds, change parameters, or grant/revoke roles. Role revocation, write-downs, parameter changes, and unwinding all route through the 48h Timelock proposed by the protocol multisig. In every scenario below, the pause is the immediate lever and the timelocked action is the durable follow-up. Plan on that 48h gap: what is the state of the system while the follow-up is queued, and what do we tell the public during it?

                       IMMEDIATE (T+0)                      DELAYED (T+48h)
                 ┌────────────────────────┐          ┌─────────────────────────┐
  Guardian ─────►│ Genesis.pause()        │          │ revokeRole(...)         │
  (security      │ Buyback.pause()        │          │ writeDownRecognized(...)│
   multisig)     │ MintController.pause() │          │ setRiskParams / caps    │
                 │ Treasury.setFounder-   │          │ Buyback.setEnabled(f)   │
                 │   WithdrawPaused(true) │          │ LiquidityMgr.windDown() │
                 │ Timelock.cancel(id)    │          └─────────▲───────────────┘
                 └────────────────────────┘                    │ schedule → 48h → execute
                                                               │
  Protocol multisig (sole proposer) ───────────────────────────┘
  Guardian CANCELLER can veto any queued operation at any point before execution.

Note the deliberate asymmetry: a pause is reversible and moves no funds, so it is safe to execute on suspicion. A wrong pause costs hours of downtime; a slow pause can cost the reserve. When in doubt, pause. Investigate second.


2. Detection and response-time targets

Detection sources (spec §86; implemented as indexer alert rules): FounderTreasuryWithdrawal, BuybackFunded / BuybackExecuted, ReserveWrittenDown, Issued (unexpected mint), Paused / Unpaused, RoleGranted / RoleRevoked, Timelock CallScheduled, supply-vs-issuance mismatch, oracle deviation, conservation check (Treasury.conservationHolds() polled), large transfers.

Milestone Target Owner
Alert acknowledged ≤ 15 minutes, 24/7 On-call (rotating; both multisig teams)
Incident confirmed or dismissed ≤ 30 minutes from alert On-call + one guardian signer
Guardian pause executed ≤ 1 hour from confirmation (severity-1: as fast as quorum allows) Guardian
Initial public notice ≤ 4 hours from confirmation Comms owner (see §8)
Timelocked follow-up queued ≤ 24 hours from confirmation Protocol multisig
Full postmortem published ≤ 7 days from resolution Incident commander

Guardian signers must hold a standing pre-agreement: any signer may initiate, and all signers sign a pause transaction on a confirmed severity-1 alert without debate — pausing is non-destructive and reversible. Signers should be geographically distributed with tested hardware-wallet access. These targets are commitments of process, not guarantees of outcome; a single-transaction exploit can complete before any human response (see §7 for the worst case).

Severity levels

  • SEV-1 — active or imminent loss of reserve, unauthorized mint, founder-key compromise, malicious queued timelock op. Pause first, confirm in parallel.
  • SEV-2 — vulnerability discovered but not exploited; oracle degradation; reserve-asset stress. Confirm, then pause the affected subsystem.
  • SEV-3 — monitoring anomaly, infra outage (indexer/website), no on-chain risk. No pause; investigate and disclose if user-visible.

3. Standing procedures

3.1 Guardian pause (immediate)

One Safe transaction from the security multisig, calling the target directly:

Lever Target contract Call
Stop Genesis deposits CrownridgeGenesis pause()
Stop buybacks CrownridgeBuyback pause()
Stop all CRWN issuance CrownridgeMintController pause()
Stop founder withdrawals CrownridgeTreasury setFounderWithdrawPaused(true)
Veto a queued admin op TimelockController cancel(bytes32 id)

Effects: Genesis.pause() blocks deposit (whenNotPaused); MintController.pause() blocks issue — no CRWN can be minted by anyone; Buyback.pause() blocks executeBuyback; setFounderWithdrawPaused(true) makes Treasury.founderWithdraw revert with FounderWithdrawIsPaused. CRWN transfers are never pausable (by design — no pause-on-transfer), and the Uniswap pool is external and cannot be paused by the protocol.

Unpausing uses the same functions/roles and is also immediate, but policy requires a written resolution and sign-off from both multisigs before any unpause.

3.2 Timelocked operation (48h)

  1. Queue — protocol multisig calls TimelockController.schedule(target, 0, data, predecessor, salt, delay) (or scheduleBatch) with delay ≥ 48h. Emits CallScheduled — monitoring alerts on every schedule, so an unauthorized queue is itself a detected incident.
  2. Wait — 48h minimum. The guardian may cancel(id) at any time before execution (id = hashOperation(target, 0, data, predecessor, salt)).
  3. Execute — anyone calls TimelockController.execute(target, 0, data, predecessor, salt).

During incidents, publish the queued operation (target, calldata, id, ETA) in the incident notice so the public can verify exactly what will change and when.


4. Scenario runbooks

Each runbook: detection → who acts → immediate action (exact call) → timelocked follow-up → communication → recovery → postmortem trigger.

IR-1 · Oracle failure or manipulation (Uniswap v3 TWAP)

The buyback's only oracle is the CRWN/USDG pool TWAP (UniV3TwapLib.consult via pool.observe).

First line of defense is the contract itselfBuyback.executeBuyback fails closed without any human action: cardinality floor (CardinalityTooLow), pool-liquidity floor (PoolLiquidityTooLow), zero/invalid price (OracleInvalid), spot-vs-TWAP deviation bound (DeviationTooHigh), and the post-swap measured-delta accretion check (NotAccretive). A manipulated pool makes buybacks revert, not misfire.

  • Detection: oracle-deviation alert; repeated executeBuyback reverts; abnormal pool activity; Buyback.preflight() returning ok=false persistently.
  • Who acts: Guardian. Response time: ≤ 1 hour (SEV-2; SEV-1 if manipulation is active).
  • Immediate: CrownridgeBuyback.pause() — belt over the contract's own braces, and it stops executor attempts from spending gas into a manipulated market.
  • Timelocked follow-up (as needed): Buyback.setEnabled(false) (durable disable, PARAMETER_ADMIN_ROLE); or Buyback.setRiskParams(...) to raise twapWindow, minCardinality, minPoolLiquidity, or tighten maxDeviationBps before re-enabling.
  • Communication: notice that buybacks are suspended pending oracle review; NAV and Genesis are unaffected (the oracle is used only by the buyback path — Genesis pricing is a fixed rate).
  • Recovery: verify pool health (cardinality, liquidity, price behavior) over ≥ 48h of observation, then unpause() after two-multisig sign-off.
  • Postmortem: required if paused > 24h or if any executed buyback is suspected non-accretive.

IR-2 · Reserve depeg or freeze (USDG)

NAV is recognizedReserve in USDG, and USDG≈$1 is a stated assumption, not a guarantee (docs/ECONOMICS.md §1). If USDG depegs, is frozen, or is otherwise impaired, NAV is overstated in USD terms and issuance pricing is wrong.

  • Detection: USDG market-price deviation on major venues; official issuer communications; transfer failures on fork/monitoring probes.
  • Who acts: Guardian immediately; protocol multisig for the write-down. Response time: pause ≤ 1 hour of confirmation (SEV-1 if severe).
  • Immediate (guardian, one Safe batch):
    1. CrownridgeGenesis.pause() — stop minting against an impaired reserve.
    2. CrownridgeMintController.pause() — defense in depth: no issuance path at all.
    3. CrownridgeBuyback.pause() — the accretion check is denominated in USDG; with USDG impaired, "accretive in USDG" may be dilutive in real terms. Suspend until re-marked.
    4. Treasury.setFounderWithdrawPaused(true) — optional but recommended in a severe depeg, so the impaired-reserve allocation question is resolved deliberately, not by withdrawal order.
  • Timelocked follow-up: queue Treasury.writeDownRecognized(newRecognized, reason) (RESERVE_MANAGER_ROLE = Timelock) marking the reserve down to its impaired value. The function can only decrease recognizedReserve (CannotIncrease) — it can never flatter NAV. Note the consequence of the 48h delay: dashboards show pre-write-down NAV until execution. The incident notice must state the queued write-down amount and ETA so no one trades on a stale NAV. If the depeg later fully reverses before execution, the guardian can cancel(id); a write-down already executed is not reversed by governance fiat — recovery value re-enters recognition only via the audited sweepExcess path with public explanation.
  • Communication: immediate notice: what is paused, current recognizedReserve, the queued write-down, explicit reminder that CRWN has no redemption mechanism and no guaranteed floor.
  • Recovery: unpause Genesis only if USDG is re-verified sound; if permanently impaired, Genesis stays paused and V2 reserve migration is designed in the open (there is no in-place reserve-swap in V1).
  • Postmortem: always.

IR-3 · Buyback exploit

Suspected: buybacks executing at manipulated prices, draining more than intended, or any BuybackExecuted where burn/spend accounting looks wrong. On-chain caps bound the blast radius: maxSpendPerTx, maxSpendPerDay, maxTreasuryBps (≤ 2% of recognized reserve per rolling day at proposed params), cooldown, plus the measured-delta accretion revert.

  • Detection: BuybackExecuted anomaly alerts; spentInWindow vs expectations; totalUsdgSpent growth rate; accretion monitoring (navPerToken must not fall on a buyback).
  • Who acts: Guardian. Response time: SEV-1 — pause as fast as quorum allows.
  • Immediate: CrownridgeBuyback.pause(). This stops executeBuyback outright. The Buyback holds no standing USDG balance or allowance (funds are pulled just-in-time and leftovers returned), so pausing it leaves no stranded value beyond dust recoverable via sweepStrayUsdg.
  • Timelocked follow-up: Buyback.setEnabled(false); if the executor key is implicated, Buyback.revokeRole(BUYBACK_EXECUTOR_ROLE, <executor>) (DEFAULT_ADMIN via Timelock). Remember: the executor can only trigger a fully-checked buyback — a compromised executor alone cannot bypass price, cap, or accretion checks; treat executor compromise as SEV-2 unless checks failed.
  • Communication: notice with the affected transaction hashes, USDG spent, CRWN burned, and the measured NAV impact. If checks were bypassed, this is also IR-5 (vulnerability) — apply its disclosure rules.
  • Recovery: root-cause the bypass, fix/re-review, re-run the economic attack suite (contracts/test/ fork + invariant tests) before any re-enable.
  • Postmortem: always.

IR-4 · Unauthorized admin action (compromised proposer / malicious queued op)

The protocol multisig is the sole Timelock proposer. A compromised proposer cannot act instantly — every operation it queues is public for 48h. This is exactly what the guardian's CANCELLER_ROLE exists for (P2 review finding C1).

  • Detection: CallScheduled alert for any operation not matching the published change calendar. Every queued op is assumed hostile until matched to an announced intent.
  • Who acts: Guardian. Response time: cancellation any time inside the 48h window; target ≤ 4 hours from detection (SEV-1).
  • Immediate: TimelockController.cancel(id) for each hostile operation. If the queued op touches funds or issuance, also pause the targeted subsystem (§3.1) while investigating.
  • Follow-up: depends on what was compromised:
    • Some protocol-multisig signers: rotate signers inside the Safe itself (a Safe owner change needs only the multisig's own threshold — no timelock involved).
    • The protocol multisig wholly compromised: honest statement of the V1 design — the timelock is self-administered, so replacing the proposer requires a timelocked op that only the compromised proposer can queue. The system enters a stalemate, not a drain: the guardian cancels everything the attacker queues (including any attempt to revoke the guardian's own CANCELLER role) indefinitely, while a recovery plan (signer rotation, negotiated re-proposal, or as a last resort a publicly coordinated V2 migration) is executed. Funds cannot move during the stalemate; governance is frozen. Document this trade-off; do not overclaim a unilateral recovery path.
    • A granular role on a protocol contract (e.g. executor): queue revokeRole(<ROLE>, <account>) on the affected contract via the Timelock; pause that subsystem for the 48h in between.
  • Communication: publish the cancelled operation's id, decoded calldata, and why it was hostile; state what is paused and the recovery plan.
  • Postmortem: always, including how the key/quorum was compromised.

IR-5 · Contract vulnerability (reported or discovered)

  • Detection: security report (per SECURITY.md — verify a disclosure channel is live before mainnet), internal discovery, anomaly alerts.
  • Who acts: Guardian for containment; incident commander for coordination. Response time: triage ≤ 24h for reports; if exploitable-now, treat as SEV-1 and pause ≤ 1 hour.
  • Immediate: pause the affected subsystem only — the levers are per-subsystem by design (spec §149): Genesis.pause(), Buyback.pause(), MintController.pause(), Treasury.setFounderWithdrawPaused(true). Not everything has a pause: the Token (transfers are never pausable), the Treasury's holdings themselves, the LiquidityManager (no Pausable; its mutating paths are timelocked RESERVE_MANAGER_ROLE calls plus permissionless collectFees, which only moves value into the protocol), and the external Uniswap pool. If the vulnerable surface is unpausable, containment = revoking the roles that reach it (timelocked) plus, for LP exposure, queueing LiquidityManager.windDown(...).
  • Coordinated disclosure: do not publish exploit details before mitigation. Acknowledge publicly that a report is being investigated once user action could be needed; publish full details only after fix/containment. Core is immutable — there are no upgrades; a code-level fix means deploying a replacement module and re-wiring roles through the 48h Timelock, in public.
  • Communication: initial notice (what is paused, what users should/should not do) without exploit mechanics; full technical disclosure with the postmortem.
  • Postmortem: always; include whether the 73+ test suite / invariants should have caught it, and add the regression test.

IR-6 · FOUNDER-KEY COMPROMISE — critical (spec §165)

The FounderTreasuryController intentionally has direct withdrawal authority over the recognized reserve (up to its configured mode). Compromise of the founder key is therefore the single most dangerous key event in the protocol, and this trade-off is disclosed, not hidden (spec §165: "the unavoidable trade-off of direct founder withdrawal authority").

Exposure by mode (set immutably at deploy, FOUNDER_MODE):

  • CAPPED: drain rate bounded on-chain by maxPerWithdrawal, rollingLimit/rollingWindow, cooldown, and minTreasuryFloor (FounderTreasuryController.withdraw checks). The response window is real: worst-case loss before the pause lands ≈ one maxPerWithdrawal per cooldown.
  • UNCAPPED: withdrawableNow() = the entire recognizedReserve. A single transaction can empty the recognized reserve before any human can respond. The pause helps only with whatever has not yet been withdrawn. State this plainly in all disclosures.

Runbook:

  1. Detect — any FounderTreasuryWithdrawal event pages the guardian immediately (every founder withdrawal is treated as an incident trigger until the founder confirms it out-of-band via a pre-established authenticated channel). Also: founder self-reports key loss; anomalous FounderWithdrawExecuted patterns.
  2. T+0 — Guardian, one Safe batch (SEV-1, sign-first-ask-later policy):
    • CrownridgeTreasury.setFounderWithdrawPaused(true)the immediate lever. Every further founderWithdraw reverts. This is why the flag exists (P2 review finding C2).
    • CrownridgeGenesis.pause() — stop new deposits flowing into a treasury under attack (spec §165.4 "Stop Genesis").
    • CrownridgeMintController.pause() — precautionary: freeze all issuance.
    • CrownridgeBuyback.pause() — precautionary: no reserve leaves for any reason during triage.
  3. ≤ 24h — Protocol multisig queues the durable revocation: Treasury.revokeRole(FOUNDER_CONTROLLER_ROLE, <FounderTreasuryController>) via TimelockController.schedule. Note the role is held by the controller contract, not the founder EOA — revoking it severs the entire path no matter who holds the founder key. Executes at T+48h (anyone can call execute). Role revocation is timelocked; the pause is what protects the reserve at T+0. The guardian keeps the pause set for the full window and cancels any hostile counter-proposals.
  4. "Move remaining reserve to a safe address" (spec §165.3) — the architecture does not permit it, deliberately. CrownridgeTreasury has no withdrawAll, no arbitrary transfer/call path, and rescueToken explicitly refuses USDG and CRWN. Any function able to move the reserve to a "safe address" would itself be the drain an attacker wants. The equivalent protection is pause (step 2) + revocation (step 3): once both land, no path exists by which any key — founder, guardian, or multisig — can move the reserve out, other than the buyback's capped, accretion-checked market purchases. Say exactly this in the public notice; do not imply a rescue capability that does not exist.
  5. Publish (≤ 4h) — incident notice: withdrawals observed (tx hashes, amounts from FounderTreasuryWithdrawal events), current recognizedReserve, cumulativeFounderWithdrawn, the pause state, the queued revocation id and ETA.
  6. Reconcile — verify conservationHolds(); sum all FounderTreasuryWithdrawal events against cumulativeFounderWithdrawn; recompute NAV and navPerToken (they already reflect the loss — founderWithdraw decrements recognizedReserve at withdrawal time, so no write-down is needed for stolen-via-withdrawal funds; writeDownRecognized applies only if some additional impairment is discovered). Publish the reconciliation.
  7. Re-establish founder authority (if and only if desired) — the founder address is immutable in the controller, so rotation = deploy a new FounderTreasuryController with the new founder address (and mode/caps re-decided — a compromise is a strong argument for CAPPED with tighter limits), then grant FOUNDER_CONTROLLER_ROLE to it via the 48h Timelock, in public, with the parameters disclosed before execution. Only then setFounderWithdrawPaused(false). Leaving founder authority permanently revoked is also a legitimate outcome and must be considered in the postmortem.
  8. Postmortem (≤ 7 days) — mandatory, public: key-management failure analysis, exact timeline (detection → pause → revocation), total loss, mode/parameter re-evaluation (CAPPED limits, whether UNCAPPED remains defensible), and monitoring gaps.

Out-of-scope for V1

Spec §87 also lists strategy exploit and bridge exploit: V1 has no yield strategies and no bridge/cross-chain issuance, so there is nothing to freeze. These runbooks must be written before any V2 feature ships (spec §79–§85).


5. Emergency lever quick reference

Scenario Immediate (guardian, T+0) Timelocked follow-up (T+48h)
Oracle failure Buyback.pause() Buyback.setEnabled(false) / setRiskParams
USDG depeg/freeze Genesis.pause() + MintController.pause() + Buyback.pause() (+ founder-pause) Treasury.writeDownRecognized(newAmount, reason)
Buyback exploit Buyback.pause() setEnabled(false), revoke BUYBACK_EXECUTOR_ROLE
Unauthorized admin op TimelockController.cancel(id) (+ subsystem pause) revokeRole(...) / Safe signer rotation
Contract vulnerability pause the affected subsystem role revocation, LiquidityManager.windDown, module replacement
Founder-key compromise Treasury.setFounderWithdrawPaused(true) + Genesis.pause() + MintController.pause() + Buyback.pause() Treasury.revokeRole(FOUNDER_CONTROLLER_ROLE, controller); new controller if rotating

6. Communication process

  • Channels: the protocol website status page and the official X account (handles pending — founder action item; wire real channels here before mainnet). On-chain events are themselves the primary record — notices always cite tx hashes and event names so anyone can verify.
  • Initial notice (≤ 4h from confirmation): what happened (facts only), what is paused, what users should or should not do, what is queued in the timelock (with id and ETA), when the next update comes. Never speculate on cause, never promise recovery of funds, never state amounts not yet verified on-chain.
  • Update cadence: at least every 24h while any pause is active, even if the update is "no change."
  • Honesty rules bind under pressure: no "funds are safe" unless provably true at that moment from on-chain state; NAV/backing/market-price terms used precisely; no implication that CRWN is redeemable (it is not, in V1); "Built on Robinhood Chain" phrasing only.

7. Standing disclosure

The founder withdrawal authority is a real trust assumption in every incident model above. The guardian pause and the 48h-timelocked revocation are mitigations, not eliminations of that trust — in UNCAPPED mode in particular, response time bounds nothing about a first-strike withdrawal. Users must weigh this before depositing. See docs/RISK.md and the FounderController disclosure on the public site.

8. Postmortem process

Every SEV-1, every SEV-2 that triggered a pause, and every founder-withdrawal false alarm gets a postmortem, published within 7 days of resolution:

  1. Timeline — detection, confirmation, each action with tx hash, resolution (UTC).
  2. Impact — reserve delta, recognizedReserve before/after, NAV/navPerToken effect, affected users, downtime per subsystem.
  3. Root cause — technical and procedural; no blameless-ness theater but no scapegoating.
  4. Detection review — which alert fired (or should have); update indexer alert rules.
  5. Action items — each with an owner and a deadline; parameter changes go through the public timelock; test-suite additions land as regression tests in contracts/test/.
  6. Drill feedback — pre-mainnet, each runbook above must be executed at least once as a drill on testnet/fork (spec: "incident drills succeed"), and this document updated with what the drill taught.

Incident commander: the guardian signer who acknowledged the alert, unless explicitly handed off. One commander per incident; the commander owns the timeline, the comms, and the postmortem.

F2TREASURYF3TRANSPARENCYF4DOCSF5WHITEPAPERF6STATUS