# QRE Stage 5 — Model-2 multi-asset liquidation (implementation plan)

Decision (user, 2026-05-27): **Model 2 auto-convert** — on cross-margin liquidation, after the perp
positions close (existing machinery untouched), market-sell the account's seized NON-USDC collateral
(BTC/ETH/MON) on our OWN spot CLOB at oracle×liqFactor to realize USDC to cover the perp loss; residual
shortfall → insurance fund → bad-debt socialization (existing ADL/BackstopVault).

## Where the deficit is covered today (the insertion point)
`BalanceManagerVault._settleCrossAccountDeficit` (src/perpetual/BalanceManagerVault.sol) debits ONLY the
user's FREE USDC and silently drops any residual above it. Both arches settle through the SAME BMV callback
(`releaseFromPosition` ← modular `CLOBPerpetualRouter.onPositionLiquidated`; Monad
`PerpEngine._handlePositionCallback`), so the conversion logic added to BMV is **automatically shared** — no
PerpEngine code change for the conversion itself (parity win).

## Core: `_coverDeficitWithCollateral($, user, remainingDeficitUsdc)` in BMV
For each `qre.getEnabledCollaterals()` cid != usdc, cfg.enabled:
- token = Currency.fromId(cid); pool = PoolManager.getPool(createPoolKey(token, usdc)); skip if no orderbook.
- free base = bm.getBalance(user, token) (+ unlock BMV-locked base if any; UMA-locked is out-of-scope v1).
- price floor = oraclePrice(getCollateralPriceSymbol(cid)) × liqFactorBps / 1e4.
- PRE-SIMULATE with OrderBook.calculateOrderAmounts; only sell the qty whose proceeds clear the floor
  (never dump into a thin book). Snapshot usdcBefore; placeMarketOrder(qty, SELL, user); received = delta.
- transferOut(min(received, remainingDeficit)) → insuranceFund.depositFunding; decrement deficit.
- **Each collateral sell wrapped in try/catch** — a single sell reverting must NOT brick the liquidation
  (Security Rule 17). Bound the loop (Rule 8). CEI-strict; proceeds measured purely by USDC balance delta.
Residual after collateral exhausted → record into insuranceProfitDeficit / socialization event (existing).

## Wiring (Stage 5a — no behavior change)
- Append to BalanceManagerVaultStorage (after quantumRiskEngine): `poolManager`, `priceOracle`,
  `maxCollateralSellSlippageBps`; onlyOwner setters (mirror setQuantumRiskEngine).
- Register BMV as an authorized ROUTER on each enabled-collateral/USDC OrderBook (PoolManager.setAuthorizedRouter);
  BMV is already a BalanceManager operator. Respect Security Rule 7 (PoolManager authorizes, never de-auths).
- Gate the whole conversion on poolManager != 0 && quantumRiskEngine != 0 (off ⇒ legacy free-USDC-only behavior).

## QRE read surface (already exists, non-custodial)
getEnabledCollaterals(), getCollateralConfig(cid).liqFactorBps, getCollateralPriceSymbol(cid),
isCollateralEnabled(cid). QRE never moves tokens; BMV (operator) does.

## Hard cases
thin/empty book → pre-simulate, sell only what clears floor, residual socialized; partial fills → loop;
price floor oracle×liqFactor; reentrancy → **the riskiest part**: liquidation frame (inside BMV nonReentrant)
calls OUT to the spot OrderBook whose hooks can re-enter — design CEI-strict, best-effort try/catch per
collateral, measure by balance delta, never optimistic. Monad spot venue (OrderBook proxy vs CLOBEngine) is
deployment-specific — abstract the sell target.

## Critical files
BalanceManagerVault.sol (core + storage), OrderBook.sol (placeMarketOrder + calculateOrderAmounts + router
auth), PoolManager.sol (getPool/createPoolKey/setAuthorizedRouter), QuantumRiskEngine.sol (read surface,
no change), PerpEngine.sol (parity check at settlement callback / Monad spot venue), BalanceManager.sol
(operator primitives).

## Tests (both arches per parity rule)
full sale covers deficit; partial fill → residual→insurance→ADL; empty/thin book → no dump, residual socialized;
price floor respected; reentrancy (malicious maker/hook on the collateral book); legacy when poolManager unset.
