# A+B Upgrade / Redeploy Report — Premium Funding Index + Penalty→Insurance

**Date:** 2026-05-26. **Scope:** Part A (premium-based funding + cumulative index) + Part B (liquidation penalty → insurance fund). ADL (Part C) is a separate follow-up.

## 1. Status
- **Tests:** 882 pass, 0 fail (incl. `FundingIndexTest` 10/10 with an exact-match fuzz + the divergence regression test, and Monad `PerpEngineTest` premium tests).
- **Sizes:** `PositionHandler` 20,637 B (<24KB), `PerpEngine` 61,678 B (<128KB), all others within limits.
- **Audits:** Pashov (8 agents) + Nemesis (Feynman + State) — both run; all actionable findings fixed (see §6). Audit-clean for A+B.

## 2. Changed contracts
**Arc (modular):** `PositionHandler.sol` (Position struct `+entryFundingIndex`; `updateFees` index settle; `isPosisitionLiquidatable` index + hour-gate fix), `MarketHandler.sol` (premium `currentFundingRate`/`accrueFunding`/`pokeFunding`/`pendingFundingFromIndex`; `orderHandler`/`interestRate`/`premiumClamp` storage + `setOrderHandler`/`setFundingParams`; stale-oracle try/catch), `DataStore.sol` (`lastFundingUpdate` mapping + get/set), `PositionLens.sol` (caller→index), `BalanceManagerVault.sol` (caller→index).
**Monad (consolidated):** `PerpEngine.sol` (premium funding mirror + index settle + liquidation-health index + hour-gate fix + `setFundingParams`), `PerpEngineStorage.sol` (`interestRate`/`premiumClamp`).
**Part B:** no contract change — already implemented + deployed (vault `_handleRelease` → InsuranceFund; verified by tests).

## 3. Proxy status of the changed contracts (the deciding factor)
From the deploy scripts + live `deployments/{5042002,10143}.json`:

| Contract | Deploy style | Upgradeable in place? |
|---|---|---|
| `DataStore` | **direct `new`** (Arc L381 / Monad L239) | ❌ no |
| `PositionHandler` | **direct `new`** (Arc L418) | ❌ no |
| `MarketHandler` | **direct `new`** (Arc L416) | ❌ no |
| `OrderHandler` | **direct `new`** (Arc L419) | ❌ no |
| `BalanceManagerVault` | BeaconProxy (`PERP_BEACON_VAULT`) | ✅ yes |
| `PositionLens` | BeaconProxy (`PERP_BEACON_POSITION_LENS`) | ✅ yes |
| `PerpEngine` (Monad) | BeaconProxy (`PERP_BEACON_ENGINE`) | ✅ yes |

## 4. Conclusion: **a fresh redeploy is required** (both chains)
All storage changes are **append-only** (Position `entryFundingIndex` at a new slot; `lastFundingUpdate` appended; `interestRate`/`premiumClamp` appended; `cumulativeFundingFee` repurposed as the index F, currently 0 on-chain) — so in *principle* they're upgrade-safe. **But `DataStore` is a direct (non-proxy) deploy**, holds every position in `mapping(bytes32=>Position) positions`, gained a new mapping + `getLastFundingUpdate`/`setLastFundingUpdate` functions, and is referenced **by immutable address** by `PositionHandler`/`MarketHandler`/`OrderHandler`/`CLOBPerpetualRouter`/`PerpEngine`. There is no proxy to `upgradeTo`, and the upgraded `PerpEngine`/`PositionHandler` would call `getLastFundingUpdate` on the *old* DataStore (selector absent → revert). Therefore:
- **`DataStore`, `PositionHandler`, `MarketHandler` cannot be upgraded in place → they must be redeployed.**
- Redeploying `DataStore` orphans all positions and forces re-wiring of everything that points at it → effectively a **full perp-system redeploy** (the same operation run on 2026-05-25).
- `BalanceManagerVault`, `PositionLens`, `PerpEngine` *are* beacon-upgradeable, but that doesn't help while `DataStore` underneath is non-upgradeable and changed.

**Redeploy is the established pattern** (the live Arc + Monad perp systems were freshly redeployed on 2026-05-25). For testnet this is acceptable: it orphans current test positions and the funding index starts clean at 0.

## 5. Redeploy procedure (per chain)
The deploy scripts are already updated for A+B + the prior cross-margin wiring fix:
- **Arc:** `forge script script/DeployAll.s.sol --broadcast --rpc-url arctestnet --slow`. Now includes `marketHandler.setOrderHandler(orderHandler)` (premium funding reads the perp book) + the Phase-5 cross-margin wiring. Constructor/inline-init sets `interestRate=1e14`, `premiumClamp=5e14`.
- **Monad:** `FOUNDRY_PROFILE=monad ~/.foundry-monad/bin/forge script script/DeployAllMonad.s.sol --broadcast --rpc-url monadtestnet --code-size-limit 131072 --slow --gas-estimate-multiplier 150`. `PerpEngine` reads its own book (no `setOrderHandler`); inline-init sets the funding params. USDC = 6-dec on Monad.
- **Post-deploy (both):** markets + oracle seed + insurance fund + builder registry (the 2026-05-25 Phase-6 steps), then **re-point all services** to the new addresses: indexer-clob-rs, oracle-updater, perpetual-backend, perpetual-frontend (Vercel redeploy), marginx-liquidator, streamer-engine, perp-market-maker bots.
- **Verify:** the exact-liquidation-price boundary script + a premium-funding live check (rest a book off-oracle, `pokeFunding`, confirm F accrues + a position's funding settles on close), and the penalty→insurance check (liquidate → InsuranceFund balance grows).

## 6. Audit findings (A+B) — resolution
- **Funding-model divergence (Pashov, 7/8 agents — the regression I introduced):** liquidation health used the legacy OI estimate while the charge moved to the index. **Fixed** in all 4 sites (`isPosisitionLiquidatable`, `PerpEngine._isPositionLiquidatable`, `getLiquidationPrice`, `PositionLens.getLiquidationPrice`) → all use the index. Regression test added.
- **Hour-gated funding in liquidation health (Nemesis cross-feed):** the funding deduction was inside `if (hoursElapsed>0)` while premium funding is time-continuous. **Fixed** in all 3 readers (borrowing stays hour-gated; funding moved out). Bounded by the 0.1%/hr cap (Medium).
- **Nemesis confirmed clean:** `entryFundingIndex` is refreshed on all 8 position-mutation paths (incl. netting + `closePositionDirect`), sign-correctness, accrue-before-read, overflow/migration/stale-oracle/empty-book boundaries.
- **Dismissed/known:** health readers deduct-only (never credit) funding = deliberate conservative design (safer given non-zero-sum lazy funding); premium from instantaneous orderbook mid (no TWAP/impact) = bounded by the 0.1%/hr cap, **documented pre-mainnet hardening**; dead OI `MarketHandler.estimatePendingFundingFee` (no on-chain caller, kept for ABI/SDK) = deprecated, remove in a future ABI-breaking cleanup.

## 7. Notes
- `setFundingParams(interestRate, premiumClamp)` was added to `MarketHandler` + `PerpEngine` (owner-only) for *future* in-place upgrades; **not needed for this fresh redeploy** (constructor/initialize set the defaults). Useful once `DataStore`/handlers are migrated behind proxies.
- SDK/keepers should read funding via the index path (`pendingFundingFromIndex` / `PerpEngine.estimatePendingFundingFee(positionKey)`), not the deprecated OI `MarketHandler.estimatePendingFundingFee`.

## 8. Recommendation
Proceed with the **fresh redeploy** (Arc → Monad) using the updated scripts, then re-point services and run the live verifications. Because this orphans current test positions and re-points production services (incl. the Vercel frontend), confirm before broadcasting.
</content>
