# Quantum Risk Engine (QRE) — Portfolio-Margin Cross-Collateral Spec

**Goal:** World-grade cross-collateral **portfolio margin** for marginX perps: any spot/yield/vault token as
collateral, valued by risk-based shock pricing, with **cross-product netting** (spot + perp + lending in one
risk-based valuation), USDC-settled, auto-borrow backstop. Keep marginX's safer partial-liquidation + ADL +
insurance (NOT World's full-liq/no-ADL). Branch: `feat/quantum-risk-engine` (worktree `clob-dex-qre`).

> Naming: our engine is the **Quantum Risk Engine (QRE)**. The new RBV contract is `QuantumRiskEngine.sol`.
> "ATLAS" is *World's* product name and appears here only as a competitor reference, never in our code.

---

## 1. Ground truth (from code deep-dive)

**Two disjoint margin authorities exist today and don't know about each other:**
- **Perp margin** = `src/perpetual/BalanceManagerVault.sol` (LIVE). Locks USDC in `BalanceManager` under itself
  as locker; cross health = `collateral + uPnL − pendingFees` vs maintenance (`_crossMarginEquity18` 659,
  `getCrossMarginPortfolioHealth` 555, `beforeWithdraw` 697). USDC-only (`$.usdc` at init).
- **Portfolio margin** = `src/margin/UnifiedMarginAccount.sol` (UMA) + `ShockPricing` + `RiskOracle` +
  `LiquidationEngine` (UNDEPLOYED). `_availableMargin` (542) iterates tracked currencies → `ShockPricing.shockValue`
  → `_lendingAdjustment` (676) → `_perpMarginContribution` (857). Perp read-side scaffolding exists
  (`registerPerpPosition` 465, `_perpMarginContribution` 857) but **is never fed** — no router calls it.

**Reusable risk primitives (already built + audited, marginX's QRE foundation):**
- `ShockPricing.shockValue(signedBal, mark, riskPrice, riskSlippage)` (25) and `shockPerpValue(...)` (77):
  up/down shock, take min (worst-case). Built for netting (long spot + short perp at same shocked price cancel).
- `RiskOracle.RiskParams{riskPrice, riskSlippage, lendingHaircut, basisHaircut, maxLeverage, liquidationIncentive,
  maxBorrowUtilization, tier}` + maturity schedule + global limits (R1/R3/R7/R10/R12).
- `LendingOrderBook`/`LoanManager`: fixed-rate fixed-10-day under-collateralized loans → the auto-borrow backbone.

**The hard problem — netting identity gap:** perp markets are SYNTHETIC. `MarketFactory.Market` =
`{marketToken, priceIdentifier, priceSymbol, settlementToken, status}` — a perp resolves price by **string**
`priceSymbol` ("BTC") via `Oracle.getPrice(string)`. Spot keys by `currencyId=uint160(token)` and prices via
`getPriceById(bytes32)`. **There is no on-chain link from a perp market to a spot ERC20.** Netting "BTC-PERP
short vs WBTC spot long" has no primitive — same *price*, different *identity*. This is THE feature; everything
else is plumbing.

**Double-count hazards latent in UMA:** `_perpMarginContribution` credits `pos.collateralAmount` (906-918) AND
the same USDC is locked in BalanceManagerVault — must count once. PnL is valued by both UMA and
BalanceManagerVault — must value once.

**Size reality (default profile, measured):** PositionHandler **335 B** free, OrderHandler **945 B** free
(effectively FULL — no new logic). CLOBPerpetualRouter 4.6 KB, BalanceManagerVault 10.3 KB, UMA 6.9 KB,
RiskOracle 22 KB free. Monad PerpEngine ~55 KB free. **New logic must live in a new contract / library, never in
PositionHandler/OrderHandler.**

---

## 2. Architecture — Option B (chosen)

**The Quantum Risk Engine is a shared VALUATION + LIQUIDATION-TRIGGER authority, not a new custodian.**
`BalanceManagerVault` keeps perp custody, the lock ledger, and the proven settlement/insurance path. Its health
function is replaced by a call into the QRE, which values the user's full basket (spot + perp + lending) with
netting. Rejected Option A (UMA becomes sole custodian) — it requires migrating in-flight perp collateral and
forking the size-full PositionHandler's close/liquidation graph.

```
            ┌──────────────────────────────────────────────┐
            │ RiskOracle (params: riskPrice, riskSlippage,  │
            │   haircuts, maxLev, basisHaircut, caps)       │
            └───────────────┬──────────────────────────────┘
                            │ getRiskParams / getPerpRiskParams
        ┌───────────────────▼───────────────────────────────────┐
        │ QuantumRiskEngine.sol  (NEW — the QRE)                 │
        │  • equity18(user), isLiquidatable(user)                │
        │  • netted basket valuation via ShockPricing            │
        │  • nettingCurrencyId[market] registry (+ enabled flag) │
        │  • shortfall → auto-borrow USDC via LendingOrderBook   │
        └───────┬───────────────────────────┬───────────────────┘
                │ equity18(user)             │ reads
   ┌────────────▼─────────┐      ┌───────────▼─────────────────────┐
   │ BalanceManagerVault   │      │ BalanceManager (spot custody)    │
   │ (modular, perp custody│      │ DataStore (perp positions)       │
   │  + settlement/insur.) │      │ LoanManager (lending positions)  │
   │ PerpEngine (Monad)    │      └──────────────────────────────────┘
   │  → call QRE for health│
   └───────────────────────┘
```

Both `BalanceManagerVault` (modular) and `PerpEngine` (Monad) call the SAME `QuantumRiskEngine` → satisfies the
parity rule without duplicating logic in two size budgets.

---

## 2b. Product registry + adapter pattern (future prediction markets / options)

The QRE is built to extend beyond spot+perp+lending. A **product registry** lets the admin enable/disable
each product family in the portfolio valuation by `bytes32 productId`:
- `ProductKind {SPOT, PERP, LENDING, PREDICTION, OPTION}` (append-only).
- **Built-in** SPOT/PERP/LENDING are valued inline by the QRE (adapter `address(0)`), auto-registered + enabled on init.
- **External** PREDICTION/OPTION register an `IProductValuationAdapter` (`riskContribution18(user)` +
  `maintenanceMargin18(user)`). `equity18`/maintenance iterate enabled products and sum each adapter's
  contribution — so adding prediction markets or an options protocol on top is a *new adapter + admin enable*,
  with **zero change to the QRE core or the perp/spot/lending code**.
- Owner-gated `registerProduct` / `setProductEnabled` / `setProductAdapter`; adapters must be conservative
  (unpriceable liability reverts, never reads as profit — same M-2 rule as the built-in legs).

This complements the collateral registry (asset side) and netting registry (hedge side) — together they give
the admin full control over collateral, LTVs, lending, markets, and which products feed portfolio margin.

## 3. Netting design (the core feature)

Decompose every position to a **signed underlying-token exposure** + price-independent cash/funding legs:
1. **Netting registry** in QRE: `nettingCurrencyId[market]` (BTC-PERP → `uint160(WBTC)`), `nettingEnabled[market]`,
   governance-set. Synthetic-only markets (TSLA-PERP) → `nettingEnabled=false`, valued standalone.
2. **Per-underlying signed exposure:** spot signed balance (18-dec) + Σ perp token deltas
   (`isLong ? +sizeInTokens : −sizeInTokens`) for markets mapped to that currency.
3. **Shock once on the NET exposure:** `shockValue(netExposure_c, markPrice_c, riskPrice_c, riskSlippage_c)` —
   the short perp's negative delta cancels the long spot, so the volatility shock applies only to the *residual*.
4. **Add price-independent legs separately:** perp entry-price basis (`±entryPrice·sizeInTokens`) + funding
   (`pendingFundingFromIndex`). (perp = underlying-delta-at-mark + fixed cash + funding.)
5. **`basisHaircut` floor:** fully-hedged book still posts `max(nettedShockMargin, basisHaircut·grossNotional)` —
   prevents netting to zero (covers funding drift + spot/perp basis).
6. **Oracle-identity guard (CRITICAL):** at registry-set time assert `Oracle.getPrice(priceSymbol(market))` and
   `getPriceById(nettingCurrencyId)` resolve to the SAME feed. A mismatched mapping = free margin = exploit.

**Double-count rule:** QRE reads vault-locked USDC via `bm.getLockedBalance(user, balanceManagerVault, usdc)` and
counts it ONCE in the USDC bucket; the perp term contributes ONLY PnL/basis/funding + token delta, never
`pos.collateralAmount` again (drop UMA lines 906-918's collateral credit).

---

## 4. Settlement — USDC base + auto-borrow (Model 3, NOT World's USDM)

Keep USDC as the single settlement asset; perp PnL/fees/insurance/backstop unchanged. Portfolio-margin value-add:
hold a non-USDC basket while perps settle USDC. On a USDC shortfall with sufficient basket RBV, QRE
**auto-borrows USDC** on the existing USDC `LendingOrderBook` (`placeOrder`→`LoanManager.createLoan`), registers
the loan in UMA (`addLendingPosition(..., isLend=false)` → already a negative `_lendingAdjustment` + concentration
caps). Basket stays put; user carries a USDC debt leg the RBV understands. Feature-flagged + per-account borrow cap.

---

## 5. Liquidation — ONE trigger, keep partial-liq + ADL + insurance

- **Trigger:** `QRE.equity18(user) < maintenance`. Repoint `unifiedVault.canLiquidateCrossMargin` (PositionHandler:696,
  PerpEngine:2535) and `LiquidationEngine.isLiquidatable` to the SAME QRE number. No account liquidatable under one
  regime and solvent under the other.
- **Perp leg:** keeps native reduce-only IOC partial-liq → `absorbLiquidationResidualViaADL` (ADL) →
  InsuranceFund/BackstopVault. `LiquidationEngine` Phase 1 DELEGATES to `CLOBPerpetualRouter._liquidatePosition`
  (NOT the reverting modular `PositionHandler.liquidatePosition`) to get ADL for free.
- **Spot/lending legs:** UMA `LiquidationEngine` Phases 2-4 (force-repay borrows → auction lender loans → socialize).
- **One backstop sink:** route UMA `BadDebtSocialized` to the same `BackstopVault`.
- **Portfolio liquidation lock:** acquire `BalanceManagerVault.liquidationLock` for the WHOLE portfolio liq so spot
  can't be withdrawn mid-liquidation.
- **Do NOT adopt** World's full-liquidation / no-ADL / bilateral-bankruptcy.

---

## 6. World use-case coverage matrix

| World capability (docs) | marginX plan | Stage |
|---|---|---|
| Any spot/yield/stable token as collateral | enabled-collateral set in spot listing; QRE values via RiskOracle | 1-2 |
| Vault tokens (WLP) as collateral | MarketMakingVault share token listed as collateral asset | 2 (later) |
| Risk-based shock valuation (risk_price/risk_slippage) | reuse `ShockPricing` + `RiskOracle` (same two params) | 1 |
| Cross-product netting (spot−perp hedge → ~0) | netting registry + netted shock pass + basisHaircut floor | 4 |
| Under-collateralized lending, fixed 10-day term | existing `LendingOrderBook`/`LoanManager` | (live) |
| Auto-borrow base asset to settle | QRE shortfall→USDC borrow | 6 |
| Lent capital counts 98% to margin | `lendingHaircut`/`_lendingAdjustment` (already) | (live) |
| Unrealized PnL 100% to margin | `_perpMarginContribution` PnL term | 1 |
| Single portfolio health number | `QRE.equity18` | 1 |
| Liquidation on portfolio health | repoint trigger to QRE | 3 |
| Bad-debt handling | KEEP ADL+insurance+backstop (better than World's bilateral) | 5 |
| MON/own-token collateral | **Monad only**, low weight + tight cap (reflexivity) | 2 |

---

## 7. Staged plan (each stage = full audit workflow + Monad/modular parity)

- **Stage 0 — Identity & registry (prereq, undeployed = free).** `IQuantumRiskEngine`; `nettingCurrencyId`/
  `nettingEnabled` registry; oracle-feed-equality assertion at set-time. No behavior change.
- **Stage 1 — Read-only RBV.** `QuantumRiskEngine.equity18(user)` = netted spot + perp(PnL+basis+funding, NO
  collateral double-count) + lending. Deploy alongside live, wired into NO gate; compare vs
  `getCrossMarginPortfolioHealth` off-chain. Zero fund risk.
- **Stage 2 — Multi-asset deposit + withdraw gate.** Enable non-USDC collateral deposits; append `quantumRiskEngine`
  to `BalanceManagerVaultStorage`; `beforeWithdraw` consults QRE. Strictly safer (liquidation unchanged). Beacon
  upgrade, reversible (pointer→0 falls back to legacy).
- **Stage 3 — Liquidation trigger → QRE.** Repoint `canLiquidateCrossMargin` to QRE equity. Keep perp
  partial-liq+ADL+insurance. Heavy fuzz/invariant: "RBV liquidatable ⇔ truly underwater."
- **Stage 4 — Netting live.** Flip `nettingEnabled` per market behind governance + `basisHaircut` floor.
- **Stage 5 — Unified liquidation path.** `LiquidationEngine` delegates perp leg to router (ADL), spot/lending
  Phases 2-4, one backstop sink, portfolio liquidation lock.
- **Stage 6 — Auto-borrow USDC settlement.** Shortfall→`LendingOrderBook` borrow trigger, feature-flagged + cap.

---

## 8. Test / fuzz / edge-case matrix (mandatory before each stage ships)

**Unit/integration:** multi-asset deposit/withdraw; basket valuation with 6/8/18-dec tokens; non-USD collateral
priced via oracle×haircut; disabled-asset rejected; per-asset & per-user caps; lent/borrowed legs in RBV;
hedged long-spot/short-perp → reduced margin; basisHaircut floor holds.

**Fuzz/invariant (Foundry):**
- `equity18` monotonic in price/balance; never reverts on stale oracle (degrade, don't brick — ties to the
  open getPnl-oracle follow-up).
- Netting invariant: `equity(hedged) ≥ equity(unhedged legs valued independently)` and never exceeds true USD.
- No double-count: `equity` with perp = spot-USDC-once + PnL, fuzzed across collateral/PnL splits.
- Liquidation trigger parity: QRE-liquidatable ⇔ account truly insolvent at shocked prices (fuzz price paths).
- Conservation: Σ(user equity) + insurance + backstop = custodied value − socialized bad debt (invariant test).

**Edge cases — defaulting & bad debt (explicit):**
- Collateral asset crashes below borrow value → liquidation seizes basket, residual → ADL → insurance → backstop.
- Auto-borrow path: USDC pool utilization at cap → borrow blocked → fall back to collateral sale, not brick.
- Reflexive collateral (MON) crash while backing a MON-correlated position → caps + low weight contain it.
- Oracle stale on ONE collateral asset → that asset excluded conservatively, account still liquidatable.
- Netting mapping mis-set (wrong currency) → oracle-equality guard rejects; if bypassed, basisHaircut floor limits damage.
- Funding spike + collateral depeg simultaneously (correlation stress).
- Full-portfolio liquidation across spot+perp+lending in one tx; partial-liq stops as soon as RBV ≥ maintenance.
- Socialized loss path when insurance + backstop exhausted (bounded, no underflow).

---

## 9. Cross-repo plan (worktrees created when each stage needs them)

| Repo | Change | Worktree |
|---|---|---|
| **clob-dex** | the contracts (this branch) | `clob-dex-qre` (active) |
| **marginx-liquidator** | LARGE — drop off-chain ShockPricing port; gate ALL liq on on-chain QRE view; ingest per-asset collateral/risk | dedicated, Stage 3 |
| **perpetual-backend** | MODERATE — replace `risk.service` hand-rolled aggregation with QRE/PositionLens; de-hardcode USDC 6-dec; per-asset RBV in `collaterals[]` | dedicated, Stage 2-3 |
| **perpetual-frontend** | MODERATE — show risk-adjusted (RBV) collateral value, per-asset haircut, netted equity; regen codegen types | dedicated, Stage 2+ |
| **indexer-clob-rs** | SMALL-MOD — new events (per-asset collateral, risk-param updates, RBV/liq phases); port Ponder schema (`crossMarginAccount`, `riskParameterUpdate`, `adlRecord`, `debtSocialization`) | small, when contracts emit |
| **perpetual-indexer (Ponder)** | NONE (not live; schema reference) | no |
| **streamer-engine** | NONE (content-agnostic relay) | no |

Land the new on-chain QRE lens views in clob-dex FIRST; backend inherits via PositionLens; liquidator redesigns to
gate on the QRE view.

---

## 10. Key file references
- RBV math to build/reuse: `src/margin/UnifiedMarginAccount.sol` `_availableMargin`(542), `_perpMarginContribution`
  (857; drop collateral credit 906-918), `_lendingAdjustment`(676); `src/margin/libraries/ShockPricing.sol`(25,77).
- Option-B target (call QRE): `src/perpetual/BalanceManagerVault.sol` `_calculateCrossMarginState`(581),
  `getCrossMarginPortfolioHealth`(555), `beforeWithdraw`(697); storage `BalanceManagerVaultStorage.sol`.
- Liquidation hooks (do NOT bloat): `PositionHandler.sol` `isPositionLiquidatable`(689, CROSS→unifiedVault 693-697),
  `absorbLiquidationResidualViaADL`(1040), `positionAdapter`(854-867), `authorizedAdapters`(1048);
  `CLOBPerpetualRouter._liquidatePosition`(325); `LiquidationEngine.liquidatePortfolio`(69, Phase1 132).
- Netting identity gap: `MarketFactory.sol` Market(29-35); UMA price resolution(1164-1205) vs spot `getPriceById`.
- Settlement anchor: `BalanceManagerVault.$.usdc`(76-86), PnL decimals(220); auto-borrow `LendingOrderBook.placeOrder`(73)
  / `LoanManager.createLoan`(64) / UMA `addLendingPosition`(324).
- Monad parity: `src/monad/PerpEngineStorage.sol` PerpStorage(154, append tail 217-219); `src/monad/PerpEngine.sol`
  `_isPositionLiquidatable`(2528, CROSS→unifiedVault 2534-2535).

**Single most important caveat:** netting is impossible until (a) a governed `market→currencyId` registry and
(b) a proof the perp `priceSymbol` feed and spot `getPriceById` feed are the same oracle source. That identity
link is the actual feature; everything else is plumbing.
