# ADL Upgrade / Redeploy Plan (Arc → Monad)

Status: **DEPLOYED + LIVE-TESTED on Arc (chain 5042002) 2026-05-26.** Beacons re-pointed to new
impls (DATASTORE 0x9Be6BD32, POSITION_HANDLER 0x377a1346, ORDER_HANDLER 0x123cf574, ROUTER
0x438ce552). Live ADL smoke test passed (script/ArcADLSmoke.s.sol). Monad still pending.
Migration DONE: seedMarketPositions backfilled the full pre-upgrade book — 2 EUR keys
(tx 0x4794788e) + 3 ETH keys (tx 0x4d9d86c9); registry counts EUR=2, ETH=3 verified. Holders
were found by scanning PositionIncreased logs since the deploy block + getUserPositions, and the
seeded sizes reconcile with on-chain OI (EUR 50/50, ETH 3e15/3e15). The whole live book is now
ADL-eligible. Seeding required NO reindex (no event emitted; only the internal registry changed).
Commits: `d2e3ffb` (Pashov remediation) + `7c46c4a` (Nemesis report + comment precision),
on top of the ADL implementation (`f7d33bc` C.1, `a7cee4d` C.2, `7897243` C.3, `941c45c` IOC+tests).

## 0. What ships in this upgrade

These four modular contracts (Arc) changed. Because the funding-index (task 41) and
liquidation-penalty→insurance (task 43) work lives in the **same** impl files, this single
beacon upgrade ships all three features together — they cannot be separated (one bytecode):

| Contract | What changed | Storage delta | Upgrade type |
|---|---|---|---|
| `DataStore` | per-market position registry (C.1) + `seedMarketPositions`; funding-index fields | **append-only** (new mappings/fields after existing layout) | in-place beacon `upgradeTo` |
| `PositionHandler` | ADL engine (C.2/C.3) + bankruptcy-price eligibility + adapter gate + scan bound | none (logic only) | in-place beacon `upgradeTo` |
| `OrderHandler` | LIQUIDATION orders partial-fill as IOC (no longer all-or-revert) | none | in-place beacon `upgradeTo` |
| `CLOBPerpetualRouter` | liquidation calls ADL absorb, best-effort try/catch | none | in-place beacon `upgradeTo` |

Monad `PerpEngine` mirrors all of the above (parity) — deployed **later**, separately.

## 1. Why it is in-place (no redeploy, no migration of positions)

- All four are `BeaconProxy` over `UpgradeableBeacon` (DeployAll.s.sol:404/471/etc.).
- The only storage change is in `DataStore`, and it is **append-only** (new `marketPositions`,
  `marketPositionIndex`, and funding fields are appended after the live layout). Existing slots
  are untouched → existing positions, balances, OI, markets all preserved.
- `PositionHandler`/`OrderHandler`/`CLOBPerpetualRouter` are pure logic upgrades (no new storage).
- The router is already registered as an authorized adapter on PositionHandler
  (`positionHandler.setAuthorizedAdapter(perpRouterProxy, true)` ran at deploy, DeployAll.s.sol:670),
  and that mapping lives in PositionHandler storage which the upgrade does not touch — so the new
  `authorizedAdapters[msg.sender]` gate on `absorbLiquidationResidualViaADL` works **without re-wiring**.

## 2. Deploy steps (Arc, in order)

1. Deploy new implementations: `DataStore`, `PositionHandler`, `OrderHandler`, `CLOBPerpetualRouter`.
   Use the standard Arc broadcast (forge script, `--slow`, arctestnet alias from clob-dex/).
2. `beacon.upgradeTo(newImpl)` for each of the four beacons (a script like
   `script/UpgradePerpHandlers.s.sol` already exists for the handler beacons — extend it to include
   DataStore + CLOBPerpetualRouter beacons).
3. **MANDATORY one-time migration:** `DataStore.seedMarketPositions(market, openPositionKeys[])`
   for each perp market (BTC-PERP, ETH-PERP, EUR-PERP). Without this, positions opened **before**
   the upgrade are invisible to the ADL counterparty search and their profit can't absorb bad debt
   (it would socialize instead). Build the key list off-chain from the indexer
   (`keccak256(account, market, usdc, isLong, marginMode)` for every open position) and batch it.
   - Idempotent (skips already-tracked keys); safe to re-run / chunk.
   - **Operational care:** seed each key only under the market it encodes (the key→index map is global;
     a wrong market pairing would corrupt removal). New positions self-register, so this is needed
     once, only for the pre-upgrade book.
4. Smoke test on Arc against live contracts (not a fork):
   - Open a matched long/short, drop oracle below the bankruptcy price, `liquidatePosition` →
     verify the bankrupt closes AND the counterparty is ADL-reduced and its balance increases.
   - Verify a liquidatable-but-solvent residual does **not** ADL the counterparty.
   - Verify a direct (non-router) `absorbLiquidationResidualViaADL` reverts `UnauthorizedAdapter`.

## 3. Audit / test state (gate for deploy)

- 897 tests pass, 0 failures (incl. 6 ADL/registry tests in `ExactLiquidationPriceTest.t.sol`).
- Sizes: PositionHandler 24,241 B (335 B under 24 KB); OrderHandler 23,631; CLOBPerpetualRouter 19,936;
  DataStore 19,123; PerpEngine 72,301 (under 128 KB).
- Pashov: 8-agent pass → 5 actionable findings (1 HIGH health, 1 HIGH migration, 3 MEDIUM) **all fixed**;
  remaining items are documented LOW/by-design (ADL haircut vs mark, counterparty close fee, OI
  imbalance on partial absorb, dust OI leak).
- Nemesis (Feynman+State loop): **0 C/H/M** introduced by the remediation; 1 LOW (seedMarketPositions
  key↔market trust, owner-only) noted. Report: `.audit/findings/nemesis-adl-remediation-verified.md`.

## 4. Monad (later, separate)

`PerpEngine` carries the parity mirror (ADL inline + `absorbLiquidationResidualViaADLSelf` self-call,
bankruptcy-price eligibility, scan bound, same DataStore registry). Deploy via the monad profile
(`FOUNDRY_PROFILE=monad ... --code-size-limit 131072 --slow --gas-estimate-multiplier 150`). Apply the
same `seedMarketPositions` backfill on the Monad DataStore. **Reminder (from prior live test):** the
WirePerpAuth Phase-5 wiring (setUnifiedVault + vault setters) must be run on any fresh Monad deploy.

## 5. Rollback

Each beacon `upgradeTo` is reversible: re-point the beacon to the prior implementation. Storage is
append-only so a rollback leaves the appended DataStore fields dormant (the old impl never reads them).
The `seedMarketPositions` data is inert without the new logic. No state corruption on rollback.
