repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | # Risk Engine Spec (Source of Truth) — v11.31 |
| #2 | |
| #3 | **Combined Single-Document Native 128-bit Revision |
| #4 | (Off-Chain Shortlist Keeper / Flat-Only Auto-Conversion / Full-Local-PnL Maintenance / Explicit Zero-Rate Funding Core Profile / Immutable Configuration / Unencumbered-Flat Deposit Sweep / Mandatory Post-Partial Local Health Check Edition)** |
| #5 | |
| #6 | **Design:** Protected Principal + Junior Profit Claims + Lazy A/K Side Indices (Native 128-bit Base-10 Scaling) |
| #7 | **Status:** implementation source-of-truth (normative language: MUST / MUST NOT / SHOULD / MAY) |
| #8 | **Scope:** perpetual DEX risk engine for a single quote-token vault. |
| #9 | **Goal:** preserve conservation, bounded insolvency handling, oracle-manipulation resistance, and liveness while supporting lazy ADL across the opposing open-interest side without global scans, canonical-order dependencies, or sequential prefix requirements for user settlement. |
| #10 | |
| #11 | This is a single combined spec. It supersedes prior delta-style revisions by restating the full current design in one document and replacing the earlier integrated on-chain barrier-scan keeper mode with a minimal on-chain exact-revalidation crank that assumes candidate discovery is performed off chain by permissionless keepers. |
| #12 | |
| #13 | ## Change summary from v11.30 |
| #14 | |
| #15 | This revision makes two substantive clarifications and one test-suite correction. |
| #16 | |
| #17 | 1. **Partial-liquidation local maintenance restoration is now mandatory even when `enqueue_adl` schedules a pending reset.** §9.4 now requires the post-step local maintenance-health check to run unconditionally on the current post-partial state. Once a reset is scheduled, the instruction still skips any *further live-OI-dependent* work, but a successful partial liquidation may no longer bypass its own local health-restoration check. |
| #18 | 2. **Exact-drain reset semantics are clarified without changing the reachable ADL state machine.** §5.6 now states explicitly that, under the maintained `OI_eff_long == OI_eff_short` invariant, the nested liq-side reset guards inside the opposing-zero branches are currently tautological and are retained only as defensive structure. |
| #19 | 3. **Test Property 54 is rephrased to match reachable states.** The impossible unilateral-drain scenario is replaced with a symmetric exact-drain reset test that verifies the required pending resets are scheduled whenever `enqueue_adl` reaches an opposing-zero branch and that subsequent operations cannot underflow against zero authoritative OI. |
| #20 | |
| #21 | ## 0. Security goals (normative) |
| #22 | |
| #23 | The engine MUST provide the following properties. |
| #24 | |
| #25 | 1. **Protected principal for flat accounts:** An account with effective position `0` MUST NOT have its protected principal directly reduced by another account's insolvency. |
| #26 | |
| #27 | 2. **Explicit open-position ADL eligibility:** Accounts with open positions MAY be subject to deterministic protocol ADL if they are on the eligible opposing side of a bankrupt liquidation. ADL MUST operate through explicit protocol state, not hidden execution. |
| #28 | |
| #29 | 3. **Oracle manipulation safety:** Profits created by short-lived oracle distortion MUST NOT immediately dilute the live haircut denominator, immediately become withdrawable principal, or immediately satisfy initial-margin / withdrawal checks. Fresh positive PnL MUST first enter reserved warmup state and only become matured according to §6. On the touched generating account, positive local PnL MAY support only that account's own maintenance equity. If `T == 0`, this time-gate is intentionally disabled. |
| #30 | |
| #31 | 4. **Profit-first haircuts:** When the system is undercollateralized, haircuts MUST apply to junior matured profit claims before any protected principal of flat accounts is impacted. |
| #32 | |
| #33 | 5. **Conservation:** The engine MUST NOT create withdrawable claims exceeding vault tokens, except for explicitly bounded rounding slack. |
| #34 | |
| #35 | 6. **Liveness:** The engine MUST NOT require `OI == 0`, manual admin recovery, a global scan, or reconciliation of an unrelated prefix of accounts before a user can safely settle, deposit, withdraw, trade, or liquidate. |
| #36 | |
| #37 | 7. **No zombie poisoning:** Non-interacting accounts MUST NOT indefinitely pin the matured-profit haircut denominator with fresh, unwarmed PnL. Touched accounts MUST make warmup progress. |
| #38 | |
| #39 | 8. **Funding / mark / ADL exactness under laziness:** Any economic quantity whose correct value depends on the position held over an interval MUST be represented through the A/K side-index mechanism or a formally equivalent event-segmented method. Integer rounding MUST NOT mint positive aggregate claims. |
| #40 | |
| #41 | 9. **No hidden protocol MM:** The protocol MUST NOT secretly internalize user flow against an undisclosed residual inventory. |
| #42 | |
| #43 | 10. **Defined recovery from precision stress:** The engine MUST define deterministic recovery when side precision is exhausted. It MUST NOT rely on assertion failure, silent overflow, or permanent `DrainOnly` states. |
| #44 | |
| #45 | 11. **No sequential quantity dependency:** Same-epoch account settlement MUST be fully local. It MAY depend on the account's own stored basis and current global side state, but MUST NOT require a canonical-order prefix or global carry cursor. |
| #46 | |
| #47 | 12. **Protocol-fee neutrality:** Explicit protocol fees MUST either be collected into `I` immediately or tracked as account-local fee debt. They MUST NOT be socialized through `h`, and unpaid explicit fees MUST NOT inflate bankruptcy deficit `D`. A voluntary organic exit to flat MUST NOT be able to leave a reclaimable account with negative exact `Eq_maint_raw_i` solely because protocol fee debt was left behind. |
| #48 | |
| #49 | 13. **Synthetic liquidation price integrity:** A synthetic liquidation close MUST execute at the current oracle mark with zero execution-price slippage. Any liquidation penalty MUST be represented only by explicit fee state. |
| #50 | |
| #51 | 14. **Loss seniority over protocol fees:** When a trade, deposit, or non-bankruptcy liquidation realizes trading losses for an account, those losses are senior to protocol fee collection from that same local capital state. |
| #52 | |
| #53 | 15. **Instruction-final funding anti-retroactivity:** If an instruction mutates any funding-rate input, the stored next-interval `r_last` MUST correspond to the instruction's final post-reset state, not any intermediate state. |
| #54 | |
| #55 | 16. **Deterministic overflow handling:** Any arithmetic condition that is not proven unreachable by the spec's numeric bounds MUST have a deterministic fail-safe or bounded fallback path. Silent wrap, unchecked panic, or undefined truncation are forbidden. |
| #56 | |
| #57 | 17. **Finite-capacity liveness:** Because account capacity is finite, the engine MUST provide permissionless dead-account reclamation or equivalent slot reuse so abandoned empty accounts and flat dust accounts below the live-balance floor cannot permanently exhaust capacity. |
| #58 | |
| #59 | 18. **Permissionless off-chain keeper compatibility:** Candidate discovery MAY be performed entirely off chain. The engine MUST expose exact current-state shortlist processing and targeted per-account settle / liquidate / reclaim paths so any permissionless keeper can make liquidation and reset progress without any required on-chain phase-1 scan or trusted off-chain classification. |
| #60 | |
| #61 | 19. **No pure-capital insurance draw without accrual:** A pure capital-only instruction that does not call `accrue_market_to` MUST NOT decrement `I` or record uninsured protocol loss. Such an instruction MAY increase `I` through explicit fee collection, direct fee-credit repayment, or an insurance top-up, and it MAY settle negative PnL from local principal, but any remaining flat negative PnL MUST wait for a later full accrued touch. |
| #62 | |
| #63 | 20. **Configuration immutability within a market instance:** The warmup, fee, margin, liquidation, insurance-floor, and live-balance-floor parameters that define a market instance MUST remain fixed for the lifetime of that instance unless a future revision defines an explicit safe update procedure. |
| #64 | |
| #65 | **Atomic execution model (normative):** Every top-level external instruction defined in §10 MUST be atomic. If any required precondition, checked-arithmetic guard, or conservative-failure condition fails, the instruction MUST roll back all state mutations performed since that instruction began. |
| #66 | |
| #67 | --- |
| #68 | |
| #69 | ## 1. Types, units, scaling, and arithmetic requirements |
| #70 | |
| #71 | ### 1.1 Amounts |
| #72 | |
| #73 | - `u128` unsigned amounts are denominated in quote-token atomic units, positive-PnL aggregates, OI, fixed-point position magnitudes, and bounded fee amounts. |
| #74 | - `i128` signed amounts represent realized PnL, K-space liabilities, and fee-credit balances. |
| #75 | - `wide_signed` in formula definitions means any transient exact signed intermediate domain wider than `i128` (for example `i256`) or an equivalent exact comparison-preserving construction. |
| #76 | - All persistent state MUST fit natively into 128-bit boundaries. Emulated wide multi-limb integers (for example `u256` / `i256`) are permitted only within transient intermediate math steps. |
| #77 | |
| #78 | ### 1.2 Prices and internal positions |
| #79 | |
| #80 | - `POS_SCALE = 1_000_000` (6 decimal places of position precision). |
| #81 | - `price: u64` is quote-token atomic units per `1` base. There is no separate `PRICE_SCALE`. |
| #82 | - All external price inputs, including `oracle_price`, `exec_price`, and any stored funding price sample, MUST satisfy `0 < price <= MAX_ORACLE_PRICE`. |
| #83 | - Internally the engine stores position bases as signed fixed-point base quantities: |
| #84 | - `basis_pos_q_i: i128`, with units `(base * POS_SCALE)`. |
| #85 | - Effective notional at oracle is: |
| #86 | - `Notional_i = mul_div_floor_u128(abs(effective_pos_q(i)), oracle_price, POS_SCALE)`. |
| #87 | - Trade fees MUST use executed trade size, not account notional: |
| #88 | - `trade_notional = mul_div_floor_u128(size_q, exec_price, POS_SCALE)`. |
| #89 | |
| #90 | ### 1.3 A/K scale |
| #91 | |
| #92 | - `ADL_ONE = 1_000_000` (6 decimal places of fractional decay accuracy). |
| #93 | - `A_side` is dimensionless and scaled by `ADL_ONE`. |
| #94 | - `K_side` has units `(ADL scale) * (quote atomic units per 1 base)`. |
| #95 | |
| #96 | ### 1.4 Concrete normative bounds |
| #97 | |
| #98 | The following bounds are normative and MUST be enforced. |
| #99 | |
| #100 | - `MAX_VAULT_TVL = 10_000_000_000_000_000` |
| #101 | - `MAX_ORACLE_PRICE = 1_000_000_000_000` |
| #102 | - `MAX_POSITION_ABS_Q = 100_000_000_000_000` |
| #103 | - `MAX_TRADE_SIZE_Q = MAX_POSITION_ABS_Q` |
| #104 | - `MAX_OI_SIDE_Q = 100_000_000_000_000` |
| #105 | - `MAX_ACCOUNT_NOTIONAL = 100_000_000_000_000_000_000` |
| #106 | - `MAX_PROTOCOL_FEE_ABS = 100_000_000_000_000_000_000` |
| #107 | - configured `MIN_INITIAL_DEPOSIT` MUST satisfy `0 < MIN_INITIAL_DEPOSIT <= MAX_VAULT_TVL` |
| #108 | - configured `MIN_NONZERO_MM_REQ` and `MIN_NONZERO_IM_REQ` MUST satisfy `0 < MIN_NONZERO_MM_REQ < MIN_NONZERO_IM_REQ <= MIN_INITIAL_DEPOSIT` |
| #109 | - deployment configuration of `MIN_INITIAL_DEPOSIT`, `MIN_NONZERO_MM_REQ`, and `MIN_NONZERO_IM_REQ` MUST be economically non-trivial for the quote token and MUST NOT be set below the deployment's tolerated slot-pinning dust threshold |
| #110 | - `MAX_ABS_FUNDING_BPS_PER_SLOT = 10_000` |
| #111 | - `|r_last| <= MAX_ABS_FUNDING_BPS_PER_SLOT` |
| #112 | - `MAX_TRADING_FEE_BPS = 10_000` |
| #113 | - `MAX_INITIAL_BPS = 10_000` |
| #114 | - `MAX_MAINTENANCE_BPS = 10_000` |
| #115 | - `MAX_LIQUIDATION_FEE_BPS = 10_000` |
| #116 | - configured margin parameters MUST satisfy `0 <= maintenance_bps <= initial_bps <= MAX_INITIAL_BPS` |
| #117 | - `MAX_FUNDING_DT = 65_535` |
| #118 | - `MAX_MATERIALIZED_ACCOUNTS = 1_000_000` |
| #119 | - `MAX_ACTIVE_POSITIONS_PER_SIDE` MUST be finite and MUST NOT exceed `MAX_MATERIALIZED_ACCOUNTS` |
| #120 | - `MAX_ACCOUNT_POSITIVE_PNL = 100_000_000_000_000_000_000_000_000_000_000` |
| #121 | - `MAX_PNL_POS_TOT = MAX_MATERIALIZED_ACCOUNTS * MAX_ACCOUNT_POSITIVE_PNL = 100_000_000_000_000_000_000_000_000_000_000_000_000` |
| #122 | - `MIN_A_SIDE = 1_000` |
| #123 | - `0 <= I_floor <= MAX_VAULT_TVL` |
| #124 | - `0 <= min_liquidation_abs <= liquidation_fee_cap <= MAX_PROTOCOL_FEE_ABS` |
| #125 | - `A_side > 0` whenever `OI_eff_side > 0` and the side is still representable. |
| #126 | |
| #127 | The following interpretation is normative for dust accounting: |
| #128 | |
| #129 | - `stored_pos_count_side` MAY be used as a q-unit conservative term in phantom-dust accounting because each live stored position can contribute at most one additional q-unit from threshold crossing when a global `A_side` truncation occurs. |
| #130 | |
| #131 | ### 1.5 Trusted time / oracle requirements |
| #132 | |
| #133 | - `now_slot` in all top-level instructions MUST come from trusted runtime slot metadata or a formally equivalent trusted source. Production entrypoints MUST NOT accept an arbitrary user-specified substitute. |
| #134 | - `oracle_price` MUST come from a validated configured oracle feed. Stale, invalid, or out-of-range oracle reads MUST fail conservatively before state mutation. |
| #135 | - Any helper or instruction that accepts `now_slot` MUST require `now_slot >= current_slot`. |
| #136 | - Any call to `accrue_market_to(now_slot, oracle_price)` MUST require `now_slot >= slot_last`. |
| #137 | - `current_slot` and `slot_last` MUST be monotonically nondecreasing. |
| #138 | |
| #139 | ### 1.6 Arithmetic requirements |
| #140 | |
| #141 | The engine MUST satisfy all of the following. |
| #142 | |
| #143 | 1. All products involving `A_side`, `K_side`, `k_snap_i`, `basis_pos_q_i`, `effective_pos_q(i)`, `price`, funding deltas, or ADL deltas MUST use checked arithmetic. |
| #144 | 2. This revision has no live funding-transfer loop because `r_last` is permanently zero under §4.12. Implementations MAY therefore omit interval sub-stepping inside `accrue_market_to`. Any future revision that re-enables nonzero funding MUST split `dt` into internal sub-steps with `dt <= MAX_FUNDING_DT`. |
| #145 | 3. The conservation check `V >= C_tot + I` and any Residual computation MUST use checked `u128` addition for `C_tot + I`. Overflow is an invariant violation. |
| #146 | 4. Signed division with positive denominator MUST use the exact helper in §4.8. |
| #147 | 5. Positive ceiling division MUST use the exact helper in §4.8. |
| #148 | 6. Warmup-cap computation `w_slope_i * elapsed` MUST use `saturating_mul_u128_u64` or a formally equivalent min-preserving construction. |
| #149 | 7. Every decrement of `stored_pos_count_*`, `stale_account_count_*`, or `phantom_dust_bound_*_q` MUST use checked subtraction. Underflow indicates corruption and MUST fail conservatively. |
| #150 | 8. Every increment of `stored_pos_count_*`, `phantom_dust_bound_*_q`, `C_tot`, `PNL_pos_tot`, or `PNL_matured_pos_tot` MUST use checked addition and MUST enforce the relevant configured bound. |
| #151 | 9. This revision has no live funding-transfer branch. Any future revision that re-enables nonzero funding MUST derive funding from the payer side first so that rounding cannot mint positive aggregate claims. |
| #152 | 10. `K_side` is cumulative across epochs. Under the 128-bit limits here, K-side overflow is practically impossible within realistic lifetimes, but implementations MUST still use checked arithmetic and revert on `i128` overflow. |
| #153 | 11. Same-epoch or epoch-mismatch `pnl_delta` MUST evaluate the signed numerator `(abs(basis_pos) * K_diff)` in an exact wide intermediate before division by `(a_basis * POS_SCALE)` and MUST use `wide_signed_mul_div_floor_from_k_pair` from §4.8. |
| #154 | 12. Any exact helper of the form `floor(a * b / d)` or `ceil(a * b / d)` required by this spec MUST return the exact quotient even when the exact product `a * b` exceeds native `u128`, provided the exact final quotient fits in the destination type. |
| #155 | 13. Haircut paths `floor(released_pos_i * h_num / h_den)` and `floor(x * h_num / h_den)` MUST use the exact multiply-divide helpers of §4.8. The final quotient MUST fit in `u128`; the intermediate product need not. |
| #156 | 14. The ADL quote-deficit path MUST compute `delta_K_abs = ceil(D_rem * A_old * POS_SCALE / OI)` using exact wide arithmetic. If the exact quotient is not representable as an `i128` magnitude, the engine MUST route `D_rem` through `record_uninsured_protocol_loss` while still continuing quantity socialization. |
| #157 | 15. If a K-space K-index delta is representable as a magnitude but the signed addition `K_opp + delta_K_exact` overflows `i128`, the engine MUST route `D_rem` through `record_uninsured_protocol_loss` while still continuing quantity socialization. |
| #158 | 16. `PNL_i` MUST be maintained in the closed interval `[i128::MIN + 1, i128::MAX]`, and `fee_credits_i` MUST be maintained in `[i128::MIN + 1, 0]`. Any operation that would set either value to exactly `i128::MIN` is non-compliant and MUST fail conservatively. |
| #159 | 17. Global A-truncation dust added in `enqueue_adl` MUST be accounted using checked arithmetic and the exact conservative bound from §5.6. |
| #160 | 18. `trade_notional <= MAX_ACCOUNT_NOTIONAL` MUST be enforced before charging trade fees. |
| #161 | 19. Any out-of-bound external price input, any invalid oracle read, or any non-monotonic slot input MUST fail conservatively before state mutation. |
| #162 | 20. Any direct fee-credit repayment path MUST cap its applied amount at the exact current `FeeDebt_i`; it MUST never set `fee_credits_i > 0`. |
| #163 | 21. Any direct insurance top-up or direct fee-credit repayment path that increases `V` or `I` MUST use checked addition and MUST enforce `MAX_VAULT_TVL`. |
| #164 | |
| #165 | ### 1.7 Reference 128-bit boundary proof |
| #166 | |
| #167 | By clamping constants to base-10 metrics, on-chain persistent state fits natively in 128-bit registers without truncation. |
| #168 | |
| #169 | Under the zero-rate core profile, the funding-specific bounds below are retained only to justify the future-compatible state shape; no funding transfer occurs in this revision. |
| #170 | |
| #171 | - Effective-position numerator: `MAX_POSITION_ABS_Q * ADL_ONE = 10^14 * 10^6 = 10^20` |
| #172 | - Notional / trade-notional numerator: `MAX_POSITION_ABS_Q * MAX_ORACLE_PRICE = 10^14 * 10^12 = 10^26` |
| #173 | - Trade slippage numerator: `MAX_TRADE_SIZE_Q * MAX_ORACLE_PRICE = 10^26`, which fits inside signed 128-bit |
| #174 | - Mark term max step: `ADL_ONE * MAX_ORACLE_PRICE = 10^18` |
| #175 | - Funding payer max step: `ADL_ONE * (MAX_ORACLE_PRICE * MAX_ABS_FUNDING_BPS_PER_SLOT * MAX_FUNDING_DT / 10_000) ≈ 6.55 × 10^22` |
| #176 | - Funding receiver numerator: `6.55 × 10^22 * ADL_ONE ≈ 6.55 × 10^28` |
| #177 | - `A_old * OI_post`: `10^6 * 10^14 = 10^20` |
| #178 | - `PNL_pos_tot` hard cap: `10^38 < u128::MAX ≈ 3.4 × 10^38` |
| #179 | - Absolute nonzero-position margin floors: `MIN_NONZERO_MM_REQ` and `MIN_NONZERO_IM_REQ` are bounded by `MIN_INITIAL_DEPOSIT <= 10^16`, so they fit natively in `u128` |
| #180 | - `K_side` overflow under max-step accumulation requires on the order of `10^12` years |
| #181 | - The three always-wide paths remain: |
| #182 | 1. exact `pnl_delta` |
| #183 | 2. exact haircut multiply-divides |
| #184 | 3. exact ADL `delta_K_abs` |
| #185 | |
| #186 | --- |
| #187 | |
| #188 | ## 2. State model |
| #189 | |
| #190 | ### 2.1 Account state |
| #191 | |
| #192 | For each materialized account `i`, the engine stores at least: |
| #193 | |
| #194 | - `C_i: u128` — protected principal. |
| #195 | - `PNL_i: i128` — realized PnL claim. |
| #196 | - `R_i: u128` — reserved positive PnL that has not yet matured through warmup, with `0 <= R_i <= max(PNL_i, 0)`. |
| #197 | - `basis_pos_q_i: i128` — signed fixed-point base basis at the last explicit position mutation or forced zeroing. |
| #198 | - `a_basis_i: u128` — side multiplier in effect when `basis_pos_q_i` was last explicitly attached. |
| #199 | - `k_snap_i: i128` — last realized `K_side` snapshot. |
| #200 | - `epoch_snap_i: u64` — side epoch in which the basis is defined. |
| #201 | - `fee_credits_i: i128`. |
| #202 | - `last_fee_slot_i: u64` — deterministic metadata anchor reserved for the disabled recurring maintenance-fee model of this revision. |
| #203 | - `w_start_i: u64`. |
| #204 | - `w_slope_i: u128`. |
| #205 | |
| #206 | Derived local quantities on a touched state: |
| #207 | |
| #208 | - `ReleasedPos_i = max(PNL_i, 0) - R_i` |
| #209 | - `FeeDebt_i = fee_debt_u128_checked(fee_credits_i)` |
| #210 | |
| #211 | Fee-credit bounds: |
| #212 | |
| #213 | - `fee_credits_i` MUST be initialized to `0`. |
| #214 | - The engine MUST maintain `-(i128::MAX) <= fee_credits_i <= 0` at all times. `fee_credits_i == i128::MIN` is forbidden. |
| #215 | |
| #216 | ### 2.2 Global engine state |
| #217 | |
| #218 | The engine stores at least: |
| #219 | |
| #220 | - `V: u128` |
| #221 | - `I: u128` |
| #222 | - `I_floor: u128` |
| #223 | - `current_slot: u64` |
| #224 | - `P_last: u64` |
| #225 | - `slot_last: u64` |
| #226 | - `r_last: i64` |
| #227 | - `fund_px_last: u64` |
| #228 | - `A_long: u128` |
| #229 | - `A_short: u128` |
| #230 | - `K_long: i128` |
| #231 | - `K_short: i128` |
| #232 | - `epoch_long: u64` |
| #233 | - `epoch_short: u64` |
| #234 | - `K_epoch_start_long: i128` |
| #235 | - `K_epoch_start_short: i128` |
| #236 | - `OI_eff_long: u128` |
| #237 | - `OI_eff_short: u128` |
| #238 | - `mode_long ∈ {Normal, DrainOnly, ResetPending}` |
| #239 | - `mode_short ∈ {Normal, DrainOnly, ResetPending}` |
| #240 | - `stored_pos_count_long: u64` |
| #241 | - `stored_pos_count_short: u64` |
| #242 | - `stale_account_count_long: u64` |
| #243 | - `stale_account_count_short: u64` |
| #244 | - `phantom_dust_bound_long_q: u128` |
| #245 | - `phantom_dust_bound_short_q: u128` |
| #246 | - `C_tot: u128 = Σ C_i` |
| #247 | - `PNL_pos_tot: u128 = Σ max(PNL_i, 0)` |
| #248 | - `PNL_matured_pos_tot: u128 = Σ(max(PNL_i, 0) - R_i)` |
| #249 | |
| #250 | The engine MUST also store, or deterministically derive from immutable configuration, at least: |
| #251 | |
| #252 | - `T = warmup_period_slots` |
| #253 | - `trading_fee_bps` |
| #254 | - `maintenance_bps` |
| #255 | - `initial_bps` |
| #256 | - `liquidation_fee_bps` |
| #257 | - `liquidation_fee_cap` |
| #258 | - `min_liquidation_abs` |
| #259 | - `MIN_INITIAL_DEPOSIT` |
| #260 | - `MIN_NONZERO_MM_REQ` |
| #261 | - `MIN_NONZERO_IM_REQ` |
| #262 | |
| #263 | This revision has **no separate `fee_revenue` state** and **no live recurring maintenance-fee accumulator**. All explicit fee proceeds and direct fee-credit repayments accrue into `I`, and §4.12 fully determines `r_last` without any additional funding-rate parameters. |
| #264 | |
| #265 | Global invariants: |
| #266 | |
| #267 | - `PNL_matured_pos_tot <= PNL_pos_tot <= MAX_PNL_POS_TOT` |
| #268 | - `C_tot <= V <= MAX_VAULT_TVL` |
| #269 | - `I <= V` |
| #270 | - `|r_last| <= MAX_ABS_FUNDING_BPS_PER_SLOT` |
| #271 | |
| #272 | ### 2.2.1 Configuration immutability |
| #273 | |
| #274 | All configuration values that affect economics or liveness are immutable for the lifetime of a market instance in this revision. |
| #275 | |
| #276 | No external instruction in this revision may change `T`, `trading_fee_bps`, `maintenance_bps`, `initial_bps`, `liquidation_fee_bps`, `liquidation_fee_cap`, `min_liquidation_abs`, `MIN_INITIAL_DEPOSIT`, `MIN_NONZERO_MM_REQ`, `MIN_NONZERO_IM_REQ`, `I_floor`, or any other parameter fixed by §§1.4, 2.2, and 4.12. |
| #277 | |
| #278 | A deployment that wishes to change any such value MUST migrate to a new market instance or future revision that defines an explicit safe update procedure. In particular, this revision has no runtime parameter-update instruction. |
| #279 | |
| #280 | ### 2.3 Materialized-account capacity |
| #281 | |
| #282 | The engine MUST track the number of currently materialized account slots. That count MUST NOT exceed `MAX_MATERIALIZED_ACCOUNTS`. |
| #283 | |
| #284 | A missing account is one whose slot is not currently materialized. Missing accounts MUST NOT be auto-materialized by `settle_account`, `withdraw`, `execute_trade`, `liquidate`, or `keeper_crank`. |
| #285 | |
| #286 | Only the following path MAY materialize a missing account in this specification: |
| #287 | |
| #288 | - a `deposit(i, amount, now_slot)` with `amount >= MIN_INITIAL_DEPOSIT` |
| #289 | |
| #290 | Any implementation-defined alternative creation path is non-compliant unless it enforces an economically equivalent anti-spam threshold and preserves all account-initialization invariants of §2.5. |
| #291 | |
| #292 | ### 2.4 Canonical zero-position defaults |
| #293 | |
| #294 | The canonical zero-position account defaults are: |
| #295 | |
| #296 | - `basis_pos_q_i = 0` |
| #297 | - `a_basis_i = ADL_ONE` |
| #298 | - `k_snap_i = 0` |
| #299 | - `epoch_snap_i = 0` |
| #300 | |
| #301 | These defaults are valid because all helpers that use side-attached snapshots MUST first require `basis_pos_q_i != 0`. |
| #302 | |
| #303 | ### 2.5 Account materialization |
| #304 | |
| #305 | `materialize_account(i, slot_anchor)` MAY succeed only if the account is currently missing and materialized-account capacity remains below `MAX_MATERIALIZED_ACCOUNTS`. |
| #306 | |
| #307 | On success, it MUST increment the materialized-account count and set: |
| #308 | |
| #309 | - `C_i = 0` |
| #310 | - `PNL_i = 0` |
| #311 | - `R_i = 0` |
| #312 | - canonical zero-position defaults from §2.4 |
| #313 | - `fee_credits_i = 0` |
| #314 | - `w_start_i = slot_anchor` |
| #315 | - `w_slope_i = 0` |
| #316 | - `last_fee_slot_i = slot_anchor` |
| #317 | |
| #318 | ### 2.6 Permissionless empty- or flat-dust-account reclamation |
| #319 | |
| #320 | The engine MUST provide a permissionless reclamation path `reclaim_empty_account(i)`. |
| #321 | |
| #322 | It MAY succeed only if all of the following hold: |
| #323 | |
| #324 | - account `i` is materialized |
| #325 | - `0 <= C_i < MIN_INITIAL_DEPOSIT` |
| #326 | - `PNL_i == 0` |
| #327 | - `R_i == 0` |
| #328 | - `basis_pos_q_i == 0` |
| #329 | - `fee_credits_i <= 0` |
| #330 | |
| #331 | On success, it MUST: |
| #332 | |
| #333 | - if `C_i > 0`: |
| #334 | - let `dust = C_i` |
| #335 | - `set_capital(i, 0)` |
| #336 | - `I = checked_add_u128(I, dust)` |
| #337 | - forgive any negative `fee_credits_i` by setting `fee_credits_i = 0` |
| #338 | - reset all local fields to canonical zero / anchored defaults |
| #339 | - mark the slot missing / reusable |
| #340 | - decrement the materialized-account count |
| #341 | |
| #342 | This forgiveness is safe only because voluntary organic paths that would leave a flat account with negative exact `Eq_maint_raw_i` are forbidden by §10.5. Reclamation is therefore reserved for genuinely empty or economically dust-flat accounts whose remaining fee debt is uncollectible. A user who wishes to preserve a flat balance below `MIN_INITIAL_DEPOSIT` MUST withdraw it to zero or top it back up above the live-balance floor before a permissionless reclaim occurs. |
| #343 | |
| #344 | A reclaimed empty or flat-dust account MUST contribute nothing to `C_tot`, `PNL_pos_tot`, `PNL_matured_pos_tot`, side counts, stale counts, or OI. Any swept dust capital becomes part of `I` and leaves `V` unchanged, so `C_tot + I` is conserved. |
| #345 | |
| #346 | ### 2.7 Initial market state |
| #347 | |
| #348 | Market initialization MUST take, at minimum, `init_slot`, `init_oracle_price`, and configured fee / margin / insurance / materialization parameters. |
| #349 | |
| #350 | At market initialization, the engine MUST set: |
| #351 | |
| #352 | - `V = 0` |
| #353 | - `I = 0` |
| #354 | - `I_floor = configured I_floor` |
| #355 | - `C_tot = 0` |
| #356 | - `PNL_pos_tot = 0` |
| #357 | - `PNL_matured_pos_tot = 0` |
| #358 | - `current_slot = init_slot` |
| #359 | - `slot_last = init_slot` |
| #360 | - `P_last = init_oracle_price` |
| #361 | - `fund_px_last = init_oracle_price` |
| #362 | - `r_last = 0` |
| #363 | - `A_long = ADL_ONE`, `A_short = ADL_ONE` |
| #364 | - `K_long = 0`, `K_short = 0` |
| #365 | - `epoch_long = 0`, `epoch_short = 0` |
| #366 | - `K_epoch_start_long = 0`, `K_epoch_start_short = 0` |
| #367 | - `OI_eff_long = 0`, `OI_eff_short = 0` |
| #368 | - `mode_long = Normal`, `mode_short = Normal` |
| #369 | - `stored_pos_count_long = 0`, `stored_pos_count_short = 0` |
| #370 | - `stale_account_count_long = 0`, `stale_account_count_short = 0` |
| #371 | - `phantom_dust_bound_long_q = 0`, `phantom_dust_bound_short_q = 0` |
| #372 | |
| #373 | ### 2.8 Side modes and reset lifecycle |
| #374 | |
| #375 | A side may be in one of three modes: |
| #376 | |
| #377 | - `Normal`: ordinary operation |
| #378 | - `DrainOnly`: the side is live but has decayed below the safe precision threshold; OI on that side may decrease but MUST NOT increase |
| #379 | - `ResetPending`: the side has been fully drained and its prior epoch is awaiting stale-account reconciliation; no operation may increase OI on that side |
| #380 | |
| #381 | `begin_full_drain_reset(side)` MAY succeed only if `OI_eff_side == 0`. It MUST: |
| #382 | |
| #383 | 1. set `K_epoch_start_side = K_side` |
| #384 | 2. increment `epoch_side` by exactly `1` |
| #385 | 3. set `A_side = ADL_ONE` |
| #386 | 4. set `stale_account_count_side = stored_pos_count_side` |
| #387 | 5. set `phantom_dust_bound_side_q = 0` |
| #388 | 6. set `mode_side = ResetPending` |
| #389 | |
| #390 | `finalize_side_reset(side)` MAY succeed only if all of the following hold: |
| #391 | |
| #392 | - `mode_side == ResetPending` |
| #393 | - `OI_eff_side == 0` |
| #394 | - `stale_account_count_side == 0` |
| #395 | - `stored_pos_count_side == 0` |
| #396 | |
| #397 | On success, it MUST set `mode_side = Normal`. |
| #398 | |
| #399 | `maybe_finalize_ready_reset_sides_before_oi_increase()` MUST check each side independently and, if the `finalize_side_reset(side)` preconditions already hold, immediately finalize that side. It MUST NOT begin a new reset or mutate OI. |
| #400 | |
| #401 | ### 2.8.1 Epoch-gap invariant |
| #402 | |
| #403 | For every materialized account with `basis_pos_q_i != 0` on side `s`, the engine MUST maintain exactly one of the following states: |
| #404 | |
| #405 | - **current attachment:** `epoch_snap_i == epoch_s`, or |
| #406 | - **stale one-epoch lag:** `mode_s == ResetPending` and `epoch_snap_i + 1 == epoch_s`. |
| #407 | |
| #408 | Epoch gaps larger than `1` are forbidden. |
| #409 | |
| #410 | Informative preservation note: `begin_full_drain_reset(side)` increments the side epoch once and snapshots the still-stored positions as stale, while `finalize_side_reset(side)` is impossible until both `stale_account_count_side == 0` and `stored_pos_count_side == 0`. Because no OI-increasing path may attach a new nonzero basis on a `ResetPending` side, a second epoch increment cannot occur while an older stale basis from the previous epoch still exists. |
| #411 | |
| #412 | --- |
| #413 | |
| #414 | ## 3. Solvency, matured-profit haircut, and live equity |
| #415 | |
| #416 | ### 3.1 Residual backing available to matured junior profits |
| #417 | |
| #418 | Define: |
| #419 | |
| #420 | - `senior_sum = checked_add_u128(C_tot, I)` |
| #421 | - `Residual = max(0, V - senior_sum)` |
| #422 | |
| #423 | Invariant: the engine MUST maintain `V >= senior_sum` at all times. |
| #424 | |
| #425 | ### 3.2 Matured positive-PnL aggregate |
| #426 | |
| #427 | Define: |
| #428 | |
| #429 | - `ReleasedPos_i = max(PNL_i, 0) - R_i` |
| #430 | - `PNL_matured_pos_tot = Σ ReleasedPos_i` |
| #431 | |
| #432 | Fresh positive PnL that has not yet warmed up MUST contribute to `R_i` first and therefore MUST NOT immediately increase `PNL_matured_pos_tot`. |
| #433 | |
| #434 | ### 3.3 Global haircut ratio `h` |
| #435 | |
| #436 | Let: |
| #437 | |
| #438 | - if `PNL_matured_pos_tot == 0`, define `h = 1` |
| #439 | - else: |
| #440 | - `h_num = min(Residual, PNL_matured_pos_tot)` |
| #441 | - `h_den = PNL_matured_pos_tot` |
| #442 | |
| #443 | For account `i` on a touched state: |
| #444 | |
| #445 | - if `PNL_matured_pos_tot == 0`, `PNL_eff_matured_i = ReleasedPos_i` |
| #446 | - else `PNL_eff_matured_i = mul_div_floor_u128(ReleasedPos_i, h_num, h_den)` |
| #447 | |
| #448 | Because each account is floored independently: |
| #449 | |
| #450 | - `Σ PNL_eff_matured_i <= h_num <= Residual` |
| #451 | |
| #452 | ### 3.4 Live equity used by margin and liquidation |
| #453 | |
| #454 | For account `i` on a touched state, first define the exact signed quantity used for initial-margin, withdrawal, and principal-conversion style checks in a transient widened signed domain: |
| #455 | |
| #456 | - `Eq_init_base_i = (C_i as wide_signed) + min(PNL_i, 0) + (PNL_eff_matured_i as wide_signed)` |
| #457 | |
| #458 | Then define: |
| #459 | |
| #460 | - `Eq_init_raw_i = Eq_init_base_i - (FeeDebt_i as wide_signed)` |
| #461 | - `Eq_init_net_i = max(0, Eq_init_raw_i)` |
| #462 | - `Eq_maint_raw_i = (C_i as wide_signed) + (PNL_i as wide_signed) - (FeeDebt_i as wide_signed)` |
| #463 | - `Eq_net_i = max(0, Eq_maint_raw_i)` |
| #464 | |
| #465 | Interpretation: |
| #466 | |
| #467 | - `Eq_init_raw_i` is the exact widened signed quantity used for initial-margin and withdrawal-style approval checks. Fresh reserved PnL in `R_i` does **not** count here, and matured junior profit counts only through the global haircut of §3.3. |
| #468 | - `Eq_init_net_i` is a clamped nonnegative convenience quantity derived from `Eq_init_raw_i`. It MAY be exposed for reporting, but it MUST NOT be used where negative raw equity must be distinguished from zero, including risk-increasing trade approval and open-position withdrawal approval. |
| #469 | - `Eq_net_i` / `Eq_maint_raw_i` are the quantities used for maintenance-margin and liquidation checks. On a touched generating account, full local mark-to-market `PNL_i` counts here, whether currently released or still reserved. |
| #470 | - The global haircut remains a claim-conversion / initial-margin / withdrawal construct. It MUST NOT directly reduce another account's maintenance equity, and pure warmup release on unchanged `C_i`, `PNL_i`, and `fee_credits_i` MUST NOT by itself reduce `Eq_maint_raw_i`. |
| #471 | - strict risk-reducing buffer comparisons MUST use `Eq_maint_raw_i` (not `Eq_net_i`) so negative raw equity cannot be hidden by the outer `max(0, ·)` floor. |
| #472 | |
| #473 | The signed quantities `Eq_init_base_i`, `Eq_init_raw_i`, and `Eq_maint_raw_i` MUST be computed in a transient widened signed type or an equivalent exact checked construction that preserves full mathematical ordering. |
| #474 | |
| #475 | - Positive overflow of these exact widened intermediates is unreachable under the configured bounds and MUST fail conservatively if encountered. |
| #476 | - An implementation MAY project an exact negative value below `i128::MIN + 1` to `i128::MIN + 1` only for one-sided health checks that compare against `0` or another nonnegative threshold after the exact sign is already known. |
| #477 | - Such projection MUST NOT be used in any strict before/after raw maintenance-buffer comparison, including §10.5 step 29. Those comparisons MUST use the exact widened signed values without saturation or clamping. |
| #478 | |
| #479 | ### 3.5 Conservatism under pending A/K side effects and warmup |
| #480 | |
| #481 | Because live haircut uses only matured positive PnL: |
| #482 | |
| #483 | - pending positive mark / funding / ADL effects MUST NOT become initial-margin or withdrawal collateral until they are touched, reserved, and later warmed up according to §6 |
| #484 | - on the touched generating account, local maintenance checks MAY use full local `PNL_i`, but only matured released positive PnL enters the global haircut denominator and only matured released positive PnL may be converted into principal via §7.4 |
| #485 | - reserved fresh positive PnL MUST NOT enter another account's equity, the global haircut denominator, or any principal-conversion path before warmup release |
| #486 | - pending lazy ADL obligations MUST NOT be counted as backing in `Residual` |
| #487 | |
| #488 | --- |
| #489 | |
| #490 | ## 4. Canonical helpers |
| #491 | |
| #492 | ### 4.1 Checked scalar helpers |
| #493 | |
| #494 | `checked_add_u128`, `checked_sub_u128`, `checked_add_i128`, `checked_sub_i128`, `checked_mul_u128`, `checked_mul_i128`, `checked_cast_i128`, and any equivalent low-level helper MUST either return the exact value or fail conservatively on overflow / underflow. |
| #495 | |
| #496 | `checked_cast_i128(x)` means an exact cast from a bounded nonnegative integer to `i128`, or conservative failure if the cast would not fit. |
| #497 | |
| #498 | ### 4.2 `set_capital(i, new_C)` |
| #499 | |
| #500 | When changing `C_i` from `old_C` to `new_C`, the engine MUST update `C_tot` by the signed delta in checked arithmetic and then set `C_i = new_C`. |
| #501 | |
| #502 | ### 4.3 `set_reserved_pnl(i, new_R)` |
| #503 | |
| #504 | Preconditions: |
| #505 | |
| #506 | - `new_R <= max(PNL_i, 0)` |
| #507 | |
| #508 | Effects: |
| #509 | |
| #510 | 1. `old_pos = max(PNL_i, 0) as u128` |
| #511 | 2. `old_rel = old_pos - R_i` |
| #512 | 3. `new_rel = old_pos - new_R` |
| #513 | 4. update `PNL_matured_pos_tot` by the exact delta from `old_rel` to `new_rel` using checked arithmetic |
| #514 | 5. require resulting `PNL_matured_pos_tot <= PNL_pos_tot` |
| #515 | 6. set `R_i = new_R` |
| #516 | |
| #517 | ### 4.4 `set_pnl(i, new_PNL)` |
| #518 | |
| #519 | When changing `PNL_i` from `old` to `new`, the engine MUST: |
| #520 | |
| #521 | 1. require `new != i128::MIN` |
| #522 | 2. let `old_pos = max(old, 0) as u128` |
| #523 | 3. let `old_R = R_i` |
| #524 | 4. let `old_rel = old_pos - old_R` |
| #525 | 5. let `new_pos = max(new, 0) as u128` |
| #526 | 6. require `new_pos <= MAX_ACCOUNT_POSITIVE_PNL` |
| #527 | 7. if `new_pos > old_pos`: |
| #528 | - `reserve_add = new_pos - old_pos` |
| #529 | - `new_R = checked_add_u128(old_R, reserve_add)` |
| #530 | - require `new_R <= new_pos` |
| #531 | 8. else: |
| #532 | - `pos_loss = old_pos - new_pos` |
| #533 | - `new_R = old_R.saturating_sub(pos_loss)` |
| #534 | - require `new_R <= new_pos` |
| #535 | 9. let `new_rel = new_pos - new_R` |
| #536 | 10. update `PNL_pos_tot` by the exact delta from `old_pos` to `new_pos` using checked arithmetic |
| #537 | 11. require resulting `PNL_pos_tot <= MAX_PNL_POS_TOT` |
| #538 | 12. update `PNL_matured_pos_tot` by the exact delta from `old_rel` to `new_rel` using checked arithmetic |
| #539 | 13. require resulting `PNL_matured_pos_tot <= PNL_pos_tot` |
| #540 | 14. set `PNL_i = new` |
| #541 | 15. set `R_i = new_R` |
| #542 | |
| #543 | **Caller obligation:** if `new_R > old_R`, the caller MUST invoke `restart_warmup_after_reserve_increase(i)` before returning from the routine that caused the positive-PnL increase. |
| #544 | |
| #545 | ### 4.4.1 `consume_released_pnl(i, x)` |
| #546 | |
| #547 | This helper removes only matured released positive PnL and MUST leave `R_i` unchanged. |
| #548 | |
| #549 | Preconditions: |
| #550 | |
| #551 | - `x > 0` |
| #552 | - `x <= ReleasedPos_i` |
| #553 | |
| #554 | Effects: |
| #555 | |
| #556 | 1. `old_pos = max(PNL_i, 0) as u128` |
| #557 | 2. `old_R = R_i` |
| #558 | 3. `old_rel = old_pos - old_R` |
| #559 | 4. `new_pos = old_pos - x` |
| #560 | 5. `new_rel = old_rel - x` |
| #561 | 6. require `new_pos >= old_R` |
| #562 | 7. update `PNL_pos_tot` by the exact delta from `old_pos` to `new_pos` using checked arithmetic |
| #563 | 8. update `PNL_matured_pos_tot` by the exact delta from `old_rel` to `new_rel` using checked arithmetic |
| #564 | 9. `PNL_i = checked_sub_i128(PNL_i, checked_cast_i128(x))` |
| #565 | 10. require resulting `PNL_matured_pos_tot <= PNL_pos_tot` |
| #566 | 11. leave `R_i` unchanged |
| #567 | |
| #568 | This helper MUST be used for profit conversion. `set_pnl(i, PNL_i - x)` is non-compliant for that purpose because generic reserve-first loss ordering is intentionally reserved for market losses and other true PnL decreases, not for removing already-matured released profit. |
| #569 | |
| #570 | ### 4.5 `set_position_basis_q(i, new_basis_pos_q)` |
| #571 | |
| #572 | When changing stored `basis_pos_q_i` from `old` to `new`, the engine MUST update `stored_pos_count_long` and `stored_pos_count_short` exactly once using the sign flags of `old` and `new`, then write `basis_pos_q_i = new`. |
| #573 | |
| #574 | For a single logical position change, `set_position_basis_q` MUST be called exactly once with the final target. Passing through an intermediate zero value is not permitted. |
| #575 | |
| #576 | ### 4.6 `attach_effective_position(i, new_eff_pos_q)` |
| #577 | |
| #578 | This helper MUST convert a current effective quantity into a new position basis at the current side state. |
| #579 | |
| #580 | If the account currently has a nonzero same-epoch basis and this helper is about to discard that basis (by writing either `0` or a different nonzero basis), then the engine MUST first account for any orphaned unresolved same-epoch quantity remainder: |
| #581 | |
| #582 | - let `s = side(basis_pos_q_i)` |
| #583 | - if `epoch_snap_i == epoch_s`, compute `rem = (abs(basis_pos_q_i) * A_s) mod a_basis_i` in exact arithmetic |
| #584 | - if `rem != 0`, invoke `inc_phantom_dust_bound(s)` |
| #585 | |
| #586 | If `new_eff_pos_q == 0`, it MUST: |
| #587 | |
| #588 | - `set_position_basis_q(i, 0)` |
| #589 | - reset snapshots to canonical zero-position defaults |
| #590 | |
| #591 | If `new_eff_pos_q != 0`, it MUST: |
| #592 | |
| #593 | - require `abs(new_eff_pos_q) <= MAX_POSITION_ABS_Q` |
| #594 | - `set_position_basis_q(i, new_eff_pos_q)` |
| #595 | - `a_basis_i = A_side(new_eff_pos_q)` |
| #596 | - `k_snap_i = K_side(new_eff_pos_q)` |
| #597 | - `epoch_snap_i = epoch_side(new_eff_pos_q)` |
| #598 | |
| #599 | ### 4.7 Phantom-dust helpers |
| #600 | |
| #601 | - `inc_phantom_dust_bound(side)` increments `phantom_dust_bound_side_q` by exactly `1` q-unit using checked addition. |
| #602 | - `inc_phantom_dust_bound_by(side, amount_q)` increments `phantom_dust_bound_side_q` by exactly `amount_q` q-units using checked addition. |
| #603 | |
| #604 | ### 4.8 Exact math helpers (normative) |
| #605 | |
| #606 | The engine MUST use the following exact helpers. |
| #607 | |
| #608 | **Signed conservative floor division** |
| #609 | |
| #610 | `floor_div_signed_conservative(n, d)`: |
| #611 | |
| #612 | - require `d > 0` |
| #613 | - `q = trunc_toward_zero(n / d)` |
| #614 | - `r = n % d` |
| #615 | - if `n < 0` and `r != 0`, return `q - 1` |
| #616 | - else return `q` |
| #617 | |
| #618 | **Positive checked ceiling division** |
| #619 | |
| #620 | `ceil_div_positive_checked(n, d)`: |
| #621 | |
| #622 | - require `d > 0` |
| #623 | - `q = n / d` |
| #624 | - `r = n % d` |
| #625 | - if `r != 0`, return checked(`q + 1`) |
| #626 | - else return `q` |
| #627 | |
| #628 | **Exact multiply-divide floor for nonnegative inputs** |
| #629 | |
| #630 | `mul_div_floor_u128(a, b, d)`: |
| #631 | |
| #632 | - require `d > 0` |
| #633 | - compute the exact quotient `q = floor(a * b / d)` |
| #634 | - this MUST be exact even if the exact product `a * b` exceeds native `u128` |
| #635 | - require `q <= u128::MAX` |
| #636 | - return `q` |
| #637 | |
| #638 | **Exact multiply-divide ceil for nonnegative inputs** |
| #639 | |
| #640 | `mul_div_ceil_u128(a, b, d)`: |
| #641 | |
| #642 | - require `d > 0` |
| #643 | - compute the exact quotient `q = ceil(a * b / d)` |
| #644 | - this MUST be exact even if the exact product `a * b` exceeds native `u128` |
| #645 | - require `q <= u128::MAX` |
| #646 | - return `q` |
| #647 | |
| #648 | **Exact wide signed multiply-divide floor from K snapshots** |
| #649 | |
| #650 | `wide_signed_mul_div_floor_from_k_pair(abs_basis_u128, k_then_i128, k_now_i128, den_u128)`: |
| #651 | |
| #652 | - require `den_u128 > 0` |
| #653 | - compute the exact signed wide difference `k_diff = k_now_i128 - k_then_i128` in a transient wide signed type |
| #654 | - compute the exact wide magnitude `p = abs_basis_u128 * abs(k_diff)` |
| #655 | - let `q = floor(p / den_u128)` |
| #656 | - let `r = p mod den_u128` |
| #657 | - if `k_diff >= 0`, return `q` as positive `i128` (require representable) |
| #658 | - if `k_diff < 0`, return `-q` if `r == 0`, else return `-(q + 1)` to preserve mathematical floor semantics (require representable) |
| #659 | |
| #660 | **Checked fee-debt conversion** |
| #661 | |
| #662 | `fee_debt_u128_checked(fee_credits)`: |
| #663 | |
| #664 | - require `fee_credits != i128::MIN` |
| #665 | - if `fee_credits >= 0`, return `0` |
| #666 | - else return `(-fee_credits) as u128` |
| #667 | |
| #668 | **Saturating warmup multiply** |
| #669 | |
| #670 | `saturating_mul_u128_u64(a, b)`: |
| #671 | |
| #672 | - if `a == 0` or `b == 0`, return `0` |
| #673 | - if `a > u128::MAX / (b as u128)`, return `u128::MAX` |
| #674 | - else return `a * (b as u128)` |
| #675 | |
| #676 | **Wide ADL quotient helper** |
| #677 | |
| #678 | `wide_mul_div_ceil_u128_or_over_i128max(a, b, d)`: |
| #679 | |
| #680 | - require `d > 0` |
| #681 | - compute the exact quotient `q = ceil(a * b / d)` in a transient wide type |
| #682 | - if `q > i128::MAX as u128`, return the tagged result `OverI128Magnitude` |
| #683 | - else return `Ok(q as u128)` |
| #684 | |
| #685 | ### 4.9 Warmup helpers |
| #686 | |
| #687 | `restart_warmup_after_reserve_increase(i)` MUST: |
| #688 | |
| #689 | 1. if `T == 0`: |
| #690 | - `set_reserved_pnl(i, 0)` |
| #691 | - `w_slope_i = 0` |
| #692 | - `w_start_i = current_slot` |
| #693 | - return |
| #694 | 2. if `R_i == 0`: |
| #695 | - `w_slope_i = 0` |
| #696 | - `w_start_i = current_slot` |
| #697 | - return |
| #698 | 3. set `w_slope_i = max(1, floor(R_i / T))` |
| #699 | 4. set `w_start_i = current_slot` |
| #700 | |
| #701 | `advance_profit_warmup(i)` MUST: |
| #702 | |
| #703 | 1. if `R_i == 0`: |
| #704 | - `w_slope_i = 0` |
| #705 | - `w_start_i = current_slot` |
| #706 | - return |
| #707 | 2. if `T == 0`: |
| #708 | - `set_reserved_pnl(i, 0)` |
| #709 | - `w_slope_i = 0` |
| #710 | - `w_start_i = current_slot` |
| #711 | - return |
| #712 | 3. `elapsed = current_slot - w_start_i` |
| #713 | 4. `release = min(R_i, saturating_mul_u128_u64(w_slope_i, elapsed))` |
| #714 | 5. if `release > 0`, `set_reserved_pnl(i, R_i - release)` |
| #715 | 6. if `R_i == 0`, set `w_slope_i = 0` |
| #716 | 7. set `w_start_i = current_slot` |
| #717 | |
| #718 | ### 4.10 `charge_fee_to_insurance(i, fee_abs)` |
| #719 | |
| #720 | Preconditions: |
| #721 | |
| #722 | - `fee_abs <= MAX_PROTOCOL_FEE_ABS` |
| #723 | |
| #724 | Effects: |
| #725 | |
| #726 | 1. `fee_paid = min(fee_abs, C_i)` |
| #727 | 2. if `fee_paid > 0`: |
| #728 | - `set_capital(i, C_i - fee_paid)` |
| #729 | - `I = checked_add_u128(I, fee_paid)` |
| #730 | 3. `fee_shortfall = fee_abs - fee_paid` |
| #731 | 4. if `fee_shortfall > 0`: |
| #732 | - `fee_credits_i = checked_sub_i128(fee_credits_i, fee_shortfall as i128)` |
| #733 | |
| #734 | This helper MUST NOT mutate `PNL_i`, `PNL_pos_tot`, `PNL_matured_pos_tot`, or any `K_side`. |
| #735 | |
| #736 | ### 4.11 Insurance-loss helpers |
| #737 | |
| #738 | `use_insurance_buffer(loss_abs)`: |
| #739 | |
| #740 | 1. precondition: `loss_abs > 0` |
| #741 | 2. `available_I = I.saturating_sub(I_floor)` |
| #742 | 3. `pay_I = min(loss_abs, available_I)` |
| #743 | 4. `I = I - pay_I` |
| #744 | 5. return `loss_abs - pay_I` |
| #745 | |
| #746 | `record_uninsured_protocol_loss(loss_abs)`: |
| #747 | |
| #748 | - precondition: `loss_abs > 0` |
| #749 | - no additional decrement to `V` or `I` occurs |
| #750 | - the uncovered loss remains represented as junior undercollateralization through `Residual` and `h` |
| #751 | |
| #752 | `absorb_protocol_loss(loss_abs)`: |
| #753 | |
| #754 | 1. precondition: `loss_abs > 0` |
| #755 | 2. `loss_rem = use_insurance_buffer(loss_abs)` |
| #756 | 3. if `loss_rem > 0`, `record_uninsured_protocol_loss(loss_rem)` |
| #757 | |
| #758 | ### 4.12 Funding-rate recomputation helper |
| #759 | |
| #760 | This revision defines a fully explicit consensus funding profile: the **zero-rate core profile**. |
| #761 | |
| #762 | The engine MUST define the pure helper: |
| #763 | |
| #764 | - `recompute_r_last_from_final_state()` |
| #765 | |
| #766 | It MUST read only the final post-reset state of the current instruction and MUST store the next-interval rate for this revision as: |
| #767 | |
| #768 | 1. read the final post-reset state only; intermediate pre-reset state MUST be ignored |
| #769 | 2. store `r_last = 0` |
| #770 | |
| #771 | No other result is compliant in this revision. |
| #772 | |
| #773 | Consequences: |
| #774 | |
| #775 | - `|r_last| <= MAX_ABS_FUNDING_BPS_PER_SLOT` holds trivially |
| #776 | - repeated invocations are idempotent |
| #777 | - no compliant state transition in this revision can produce a nonzero `r_last` |
| #778 | - this revision has **no live funding-transfer branch**; any future nonzero-funding transfer arithmetic is outside the normative implementation surface of this document and MUST be introduced only by a future revision with a fully explicit formula and tests |
| #779 | - `fund_px_last` remains deterministic metadata and MUST equal the oracle price from the most recent successful `accrue_market_to` |
| #780 | --- |
| #781 | |
| #782 | ## 5. Unified A/K side-index mechanics |
| #783 | |
| #784 | ### 5.1 Eager-equivalent event law |
| #785 | |
| #786 | For one side of the book, a single eager global event on absolute fixed-point position `q_q >= 0` and realized PnL `p` has the form: |
| #787 | |
| #788 | - `q_q' = α q_q` |
| #789 | - `p' = p + β * q_q / POS_SCALE` |
| #790 | |
| #791 | where: |
| #792 | |
| #793 | - `α ∈ [0, 1]` is the surviving-position fraction |
| #794 | - `β` is quote PnL per unit pre-event base position |
| #795 | |
| #796 | The cumulative side indices compose as: |
| #797 | |
| #798 | - `A_new = A_old * α` |
| #799 | - `K_new = K_old + A_old * β` |
| #800 | |
| #801 | ### 5.2 `effective_pos_q(i)` |
| #802 | |
| #803 | For an account `i` with nonzero basis: |
| #804 | |
| #805 | - let `s = side(basis_pos_q_i)` |
| #806 | - if `epoch_snap_i != epoch_s`, then `effective_pos_q(i) = 0` for current-market risk purposes until the account is touched and zeroed |
| #807 | - else `effective_abs_pos_q(i) = mul_div_floor_u128(abs(basis_pos_q_i) as u128, A_s, a_basis_i)` |
| #808 | - `effective_pos_q(i) = sign(basis_pos_q_i) * effective_abs_pos_q(i)` |
| #809 | |
| #810 | If `basis_pos_q_i == 0`, define `effective_pos_q(i) = 0`. |
| #811 | |
| #812 | ### 5.2.1 Side-OI components of a signed effective position |
| #813 | |
| #814 | For any signed fixed-point position `q` in q-units: |
| #815 | |
| #816 | - `OI_long_component(q) = max(q, 0) as u128` |
| #817 | - `OI_short_component(q) = max(-q, 0) as u128` |
| #818 | |
| #819 | Because every reachable effective position satisfies `|q| <= MAX_POSITION_ABS_Q < i128::MAX`, both casts are exact. |
| #820 | |
| #821 | ### 5.2.2 Exact bilateral trade side-OI after-values |
| #822 | |
| #823 | For a bilateral trade with pre-trade effective positions `old_eff_pos_q_a`, `old_eff_pos_q_b` and candidate post-trade effective positions `new_eff_pos_q_a`, `new_eff_pos_q_b`, define: |
| #824 | |
| #825 | - `old_long_a = OI_long_component(old_eff_pos_q_a)` |
| #826 | - `old_short_a = OI_short_component(old_eff_pos_q_a)` |
| #827 | - `old_long_b = OI_long_component(old_eff_pos_q_b)` |
| #828 | - `old_short_b = OI_short_component(old_eff_pos_q_b)` |
| #829 | - `new_long_a = OI_long_component(new_eff_pos_q_a)` |
| #830 | - `new_short_a = OI_short_component(new_eff_pos_q_a)` |
| #831 | - `new_long_b = OI_long_component(new_eff_pos_q_b)` |
| #832 | - `new_short_b = OI_short_component(new_eff_pos_q_b)` |
| #833 | |
| #834 | Then the exact candidate side-OI after-values are: |
| #835 | |
| #836 | - `OI_long_after_trade = (((OI_eff_long - old_long_a) - old_long_b) + new_long_a) + new_long_b` |
| #837 | - `OI_short_after_trade = (((OI_eff_short - old_short_a) - old_short_b) + new_short_a) + new_short_b` |
| #838 | |
| #839 | All arithmetic above MUST use the checked helpers of §4.1. |
| #840 | |
| #841 | A trade would increase net side OI on the long side iff `OI_long_after_trade > OI_eff_long`, and analogously for the short side. |
| #842 | |
| #843 | When §10.5 uses these candidate after-values, the same exact `OI_long_after_trade` and `OI_short_after_trade` computed for constrained-side gating MUST later be written to `OI_eff_long` and `OI_eff_short`; heuristic reopen tests or alternate decompositions are non-compliant. |
| #844 | |
| #845 | ### 5.3 `settle_side_effects(i)` |
| #846 | |
| #847 | When touching account `i`: |
| #848 | |
| #849 | 1. if `basis_pos_q_i == 0`, return immediately |
| #850 | 2. let `s = side(basis_pos_q_i)` |
| #851 | 3. let `den = checked_mul_u128(a_basis_i, POS_SCALE)` |
| #852 | 4. if `epoch_snap_i == epoch_s` (same epoch): |
| #853 | - `q_eff_new = mul_div_floor_u128(abs(basis_pos_q_i) as u128, A_s, a_basis_i)` |
| #854 | - record `old_R = R_i` |
| #855 | - `pnl_delta = wide_signed_mul_div_floor_from_k_pair(abs(basis_pos_q_i) as u128, k_snap_i, K_s, den)` |
| #856 | - `set_pnl(i, checked_add_i128(PNL_i, pnl_delta))` |
| #857 | - if `R_i > old_R`, invoke `restart_warmup_after_reserve_increase(i)` |
| #858 | - if `q_eff_new == 0`: |
| #859 | - `inc_phantom_dust_bound(s)` |
| #860 | - `set_position_basis_q(i, 0)` |
| #861 | - reset snapshots to canonical zero-position defaults |
| #862 | - else: |
| #863 | - leave `basis_pos_q_i` and `a_basis_i` unchanged |
| #864 | - set `k_snap_i = K_s` |
| #865 | - set `epoch_snap_i = epoch_s` |
| #866 | 5. else (epoch mismatch): |
| #867 | - require `mode_s == ResetPending` |
| #868 | - require `epoch_snap_i + 1 == epoch_s` |
| #869 | - record `old_R = R_i` |
| #870 | - `pnl_delta = wide_signed_mul_div_floor_from_k_pair(abs(basis_pos_q_i) as u128, k_snap_i, K_epoch_start_s, den)` |
| #871 | - `set_pnl(i, checked_add_i128(PNL_i, pnl_delta))` |
| #872 | - if `R_i > old_R`, invoke `restart_warmup_after_reserve_increase(i)` |
| #873 | - `set_position_basis_q(i, 0)` |
| #874 | - decrement `stale_account_count_s` using checked subtraction |
| #875 | - reset snapshots to canonical zero-position defaults |
| #876 | |
| #877 | The `epoch_snap_i + 1 == epoch_s` precondition is justified by the invariant of §2.8.1; a larger gap is non-compliant state corruption. |
| #878 | |
| #879 | ### 5.4 `accrue_market_to(now_slot, oracle_price)` |
| #880 | |
| #881 | Before any operation that depends on current market state, the engine MUST call `accrue_market_to(now_slot, oracle_price)`. |
| #882 | |
| #883 | This helper intentionally uses the validated oracle-price sample as both: |
| #884 | |
| #885 | - the mark-to-market price sample (`P_last`), and |
| #886 | - the deterministic funding-basis metadata sample (`fund_px_last`). |
| #887 | |
| #888 | Under the zero-rate core profile of §4.12, `fund_px_last` is metadata only; no funding transfer is applied in this revision. |
| #889 | |
| #890 | This helper MUST: |
| #891 | |
| #892 | 1. require trusted `now_slot >= slot_last` |
| #893 | 2. require validated `0 < oracle_price <= MAX_ORACLE_PRICE` |
| #894 | 3. snapshot `OI_long_0 = OI_eff_long` and `OI_short_0 = OI_eff_short` |
| #895 | 4. compute signed one-shot `ΔP = (oracle_price as i128) - (P_last as i128)` |
| #896 | 5. apply mark-to-market exactly once using the snapped side state: |
| #897 | - if `OI_long_0 > 0`, `K_long = checked_add_i128(K_long, (A_long * ΔP))` |
| #898 | - if `OI_short_0 > 0`, `K_short = checked_sub_i128(K_short, (A_short * ΔP))` |
| #899 | 6. apply no funding transfer in this revision; `r_last` is always `0` under §4.12 |
| #900 | 7. update `slot_last = now_slot` |
| #901 | 8. update `P_last = oracle_price` |
| #902 | 9. update `fund_px_last = oracle_price` |
| #903 | |
| #904 | Any future nonzero-funding revision MUST replace step 6 with a fully explicit normative funding-transfer rule. |
| #905 | |
| #906 | ### 5.5 Funding anti-retroactivity |
| #907 | |
| #908 | Each standard-lifecycle instruction of §10 MUST invoke `recompute_r_last_from_final_state()` exactly once and only after any end-of-instruction reset handling specified by that instruction. |
| #909 | |
| #910 | In this revision the helper always stores `0`, so the ordering has no economic effect beyond deterministic state shape. Any future nonzero-funding revision MUST preserve the same post-reset recomputation ordering when it introduces live funding transfers. |
| #911 | |
| #912 | ### 5.6 `enqueue_adl(ctx, liq_side, q_close_q, D)` |
| #913 | |
| #914 | Suppose a bankrupt liquidation from side `liq_side` leaves an uncovered deficit `D >= 0` after the liquidated account's principal and realized PnL have been exhausted. `q_close_q` is the fixed-point base quantity removed from the liquidated side and MAY be zero. |
| #915 | |
| #916 | Let `opp = opposite(liq_side)`. |
| #917 | |
| #918 | This helper MUST perform the following in order: |
| #919 | |
| #920 | 1. if `q_close_q > 0`, decrement `OI_eff_liq_side` by `q_close_q` using checked subtraction |
| #921 | 2. if `D > 0`, set `D_rem = use_insurance_buffer(D)`; else define `D_rem = 0` |
| #922 | 3. read `OI = OI_eff_opp` |
| #923 | 4. if `OI == 0`: |
| #924 | - if `D_rem > 0`, `record_uninsured_protocol_loss(D_rem)` |
| #925 | - if `OI_eff_liq_side == 0`, set both `ctx.pending_reset_liq_side = true` and `ctx.pending_reset_opp = true` |
| #926 | - return |
| #927 | 5. if `OI > 0` and `stored_pos_count_opp == 0`: |
| #928 | - require `q_close_q <= OI` |
| #929 | - let `OI_post = OI - q_close_q` |
| #930 | - if `D_rem > 0`, `record_uninsured_protocol_loss(D_rem)` |
| #931 | - set `OI_eff_opp = OI_post` |
| #932 | - if `OI_post == 0`: |
| #933 | - set `ctx.pending_reset_opp = true` |
| #934 | - if `OI_eff_liq_side == 0`, set `ctx.pending_reset_liq_side = true` |
| #935 | - return |
| #936 | 6. otherwise (`OI > 0` and `stored_pos_count_opp > 0`): |
| #937 | - require `q_close_q <= OI` |
| #938 | - `A_old = A_opp` |
| #939 | - `OI_post = OI - q_close_q` |
| #940 | 7. if `D_rem > 0`: |
| #941 | - let `adl_scale = checked_mul_u128(A_old, POS_SCALE)` |
| #942 | - compute `delta_K_abs_result = wide_mul_div_ceil_u128_or_over_i128max(D_rem, adl_scale, OI)` |
| #943 | - if `delta_K_abs_result == OverI128Magnitude`, `record_uninsured_protocol_loss(D_rem)` |
| #944 | - else: |
| #945 | - `delta_K_abs = unwrap(delta_K_abs_result)` |
| #946 | - `delta_K_exact = -(delta_K_abs as i128)` |
| #947 | - if `checked_add_i128(K_opp, delta_K_exact)` fails, `record_uninsured_protocol_loss(D_rem)` |
| #948 | - else `K_opp = K_opp + delta_K_exact` |
| #949 | 8. if `OI_post == 0`: |
| #950 | - set `OI_eff_opp = 0` |
| #951 | - set `ctx.pending_reset_opp = true` |
| #952 | - if `OI_eff_liq_side == 0`, set `ctx.pending_reset_liq_side = true` |
| #953 | - return |
| #954 | 9. compute `A_prod_exact = checked_mul_u128(A_old, OI_post)` |
| #955 | 10. `A_candidate = floor(A_prod_exact / OI)` |
| #956 | 11. `A_trunc_rem = A_prod_exact mod OI` |
| #957 | 12. if `A_candidate > 0`: |
| #958 | - set `A_opp = A_candidate` |
| #959 | - set `OI_eff_opp = OI_post` |
| #960 | - if `A_trunc_rem != 0`: |
| #961 | - `N_opp = stored_pos_count_opp as u128` |
| #962 | - `global_a_dust_bound = checked_add_u128(N_opp, ceil_div_positive_checked(checked_add_u128(OI, N_opp), A_old))` |
| #963 | - `inc_phantom_dust_bound_by(opp, global_a_dust_bound)` |
| #964 | - if `A_opp < MIN_A_SIDE`, set `mode_opp = DrainOnly` |
| #965 | - return |
| #966 | 13. if `A_candidate == 0` while `OI_post > 0`, enter the precision-exhaustion terminal drain: |
| #967 | - set `OI_eff_opp = 0` |
| #968 | - set `OI_eff_liq_side = 0` |
| #969 | - set both pending-reset flags true |
| #970 | |
| #971 | Normative intent: |
| #972 | |
| #973 | - Real bankruptcy losses MUST first consume the Insurance Fund down to `I_floor`. |
| #974 | - Only the remaining `D_rem` MAY be socialized through `K_opp` or left as junior undercollateralization. |
| #975 | - Quantity socialization MUST never assert-fail due to `A_side` rounding to zero. |
| #976 | - If `enqueue_adl` drives a side's authoritative `OI_eff_side` to `0`, that side MUST enter the reset lifecycle before any further live-OI-dependent processing, even when the liquidated side remains live. |
| #977 | - Under the maintained invariant `OI_eff_long == OI_eff_short` at `enqueue_adl` entry, the nested `if OI_eff_liq_side == 0` guards in steps 4, 5, and 8 are currently tautological whenever the enclosing branch has already driven the opposing side to `0`. They are retained as defensive structure and do not change reachable behavior in this revision. |
| #978 | - Real quote deficits MUST NOT be written into `K_opp` when there are no opposing stored positions left to realize that K change. |
| #979 | |
| #980 | ### 5.7 End-of-instruction reset handling |
| #981 | |
| #982 | The engine MUST provide both: |
| #983 | |
| #984 | - `schedule_end_of_instruction_resets(ctx)` |
| #985 | - `finalize_end_of_instruction_resets(ctx)` |
| #986 | |
| #987 | `schedule_end_of_instruction_resets(ctx)` MUST be called exactly once at the end of each top-level instruction that can touch accounts, mutate side state, or liquidate. |
| #988 | |
| #989 | It MUST perform the following in order: |
| #990 | |
| #991 | 1. **Bilateral-empty dust clearance** |
| #992 | - if `stored_pos_count_long == 0` and `stored_pos_count_short == 0`: |
| #993 | - `clear_bound_q = checked_add_u128(phantom_dust_bound_long_q, phantom_dust_bound_short_q)` |
| #994 | - `has_residual_clear_work = (OI_eff_long > 0) or (OI_eff_short > 0) or (phantom_dust_bound_long_q > 0) or (phantom_dust_bound_short_q > 0)` |
| #995 | - if `has_residual_clear_work`: |
| #996 | - require `OI_eff_long == OI_eff_short` |
| #997 | - if `OI_eff_long <= clear_bound_q` and `OI_eff_short <= clear_bound_q`: |
| #998 | - set `OI_eff_long = 0` |
| #999 | - set `OI_eff_short = 0` |
| #1000 | - set both pending-reset flags true |
| #1001 | - else fail conservatively |
| #1002 | 2. **Unilateral-empty dust clearance (long empty)** |
| #1003 | - else if `stored_pos_count_long == 0` and `stored_pos_count_short > 0`: |
| #1004 | - `has_residual_clear_work = (OI_eff_long > 0) or (OI_eff_short > 0) or (phantom_dust_bound_long_q > 0)` |
| #1005 | - if `has_residual_clear_work`: |
| #1006 | - require `OI_eff_long == OI_eff_short` |
| #1007 | - if `OI_eff_long <= phantom_dust_bound_long_q`: |
| #1008 | - set `OI_eff_long = 0` |
| #1009 | - set `OI_eff_short = 0` |
| #1010 | - set both pending-reset flags true |
| #1011 | - else fail conservatively |
| #1012 | 3. **Unilateral-empty dust clearance (short empty)** |
| #1013 | - else if `stored_pos_count_short == 0` and `stored_pos_count_long > 0`: |
| #1014 | - `has_residual_clear_work = (OI_eff_long > 0) or (OI_eff_short > 0) or (phantom_dust_bound_short_q > 0)` |
| #1015 | - if `has_residual_clear_work`: |
| #1016 | - require `OI_eff_long == OI_eff_short` |
| #1017 | - if `OI_eff_short <= phantom_dust_bound_short_q`: |
| #1018 | - set `OI_eff_long = 0` |
| #1019 | - set `OI_eff_short = 0` |
| #1020 | - set both pending-reset flags true |
| #1021 | - else fail conservatively |
| #1022 | 4. **DrainOnly zero-OI reset scheduling** |
| #1023 | - if `mode_long == DrainOnly and OI_eff_long == 0`, set `ctx.pending_reset_long = true` |
| #1024 | - if `mode_short == DrainOnly and OI_eff_short == 0`, set `ctx.pending_reset_short = true` |
| #1025 | |
| #1026 | `finalize_end_of_instruction_resets(ctx)` MUST: |
| #1027 | |
| #1028 | 1. if `ctx.pending_reset_long` and `mode_long != ResetPending`, invoke `begin_full_drain_reset(long)` |
| #1029 | 2. if `ctx.pending_reset_short` and `mode_short != ResetPending`, invoke `begin_full_drain_reset(short)` |
| #1030 | 3. if `mode_long == ResetPending` and `OI_eff_long == 0` and `stale_account_count_long == 0` and `stored_pos_count_long == 0`, invoke `finalize_side_reset(long)` |
| #1031 | 4. if `mode_short == ResetPending` and `OI_eff_short == 0` and `stale_account_count_short == 0` and `stored_pos_count_short == 0`, invoke `finalize_side_reset(short)` |
| #1032 | |
| #1033 | Once either pending-reset flag becomes true during a top-level instruction, that instruction MUST NOT perform any additional account touches, liquidations, or explicit position mutations that rely on live authoritative OI. It MUST proceed directly to end-of-instruction reset handling after finishing any already-started local bookkeeping that does not read or mutate live side exposure. |
| #1034 | |
| #1035 | --- |
| #1036 | |
| #1037 | ## 6. Warmup and matured-profit release |
| #1038 | |
| #1039 | ### 6.1 Parameter |
| #1040 | |
| #1041 | - `T = warmup_period_slots` |
| #1042 | - if `T == 0`, warmup is instantaneous |
| #1043 | |
| #1044 | ### 6.2 Semantics of `R_i` |
| #1045 | |
| #1046 | `R_i` is the reserved portion of positive `PNL_i` that has not yet matured through warmup. |
| #1047 | |
| #1048 | - `ReleasedPos_i = max(PNL_i, 0) - R_i` |
| #1049 | - Only `ReleasedPos_i` contributes to `PNL_matured_pos_tot`, to live haircut, to `Eq_init_net_i`, and to profit conversion |
| #1050 | - Reserved fresh positive PnL in `R_i` MAY contribute only to the generating account's maintenance checks |
| #1051 | - `Eq_maint_raw_i` uses full local `PNL_i` on the touched generating account, so pure changes in composition between `ReleasedPos_i` and `R_i` do not by themselves change maintenance equity |
| #1052 | - Fresh positive PnL MUST enter `R_i` first by the automatic reserve-increase rule in `set_pnl` |
| #1053 | |
| #1054 | ### 6.3 Warmup progress |
| #1055 | |
| #1056 | Touched accounts MUST call `advance_profit_warmup(i)` before any logic that depends on current released positive PnL in that touch. |
| #1057 | |
| #1058 | This helper releases previously reserved positive PnL according to the current slope and elapsed slots but never grants newly added reserve any retroactive maturity. |
| #1059 | |
| #1060 | ### 6.4 Anti-retroactivity |
| #1061 | |
| #1062 | When `set_pnl` increases `R_i`, the caller MUST immediately invoke `restart_warmup_after_reserve_increase(i)`. This resets `w_start_i = current_slot` and recomputes `w_slope_i` from the new reserve, so newly generated profit cannot inherit old dormant maturity headroom. |
| #1063 | |
| #1064 | ### 6.5 Release slope preservation |
| #1065 | |
| #1066 | When reserve decreases only because of `advance_profit_warmup(i)`, the engine MUST preserve the existing `w_slope_i` for the remaining reserve (unless the reserve reaches zero). This prevents repeated touches from creating exponential-decay maturity. |
| #1067 | |
| #1068 | --- |
| #1069 | |
| #1070 | ## 7. Loss settlement, flat-loss resolution, profit conversion, and fee-debt sweep |
| #1071 | |
| #1072 | ### 7.1 `settle_losses_from_principal(i)` |
| #1073 | |
| #1074 | If `PNL_i < 0`, the engine MUST immediately attempt to settle from principal: |
| #1075 | |
| #1076 | 1. require `PNL_i != i128::MIN` |
| #1077 | 2. record `old_R = R_i` |
| #1078 | 3. `need = (-PNL_i) as u128` |
| #1079 | 4. `pay = min(need, C_i)` |
| #1080 | 5. apply: |
| #1081 | - `set_capital(i, C_i - pay)` |
| #1082 | - `set_pnl(i, checked_add_i128(PNL_i, pay as i128))` |
| #1083 | |
| #1084 | Because `pay <= need = -PNL_i_before`, the post-write `PNL_i_after = PNL_i_before + pay` lies in `[PNL_i_before, 0]`. Therefore `max(PNL_i_after, 0) = 0`, no reserve can be added, and the helper MUST leave `R_i` unchanged. Implementations SHOULD assert `R_i == old_R` after the helper. |
| #1085 | |
| #1086 | ### 7.2 Open-position negative remainder |
| #1087 | |
| #1088 | If after §7.1: |
| #1089 | |
| #1090 | - `PNL_i < 0`, and |
| #1091 | - `effective_pos_q(i) != 0` |
| #1092 | |
| #1093 | then the account MUST remain liquidatable. It MUST NOT be silently zeroed or routed through flat-account loss absorption. |
| #1094 | |
| #1095 | ### 7.3 Flat-account negative remainder |
| #1096 | |
| #1097 | If after §7.1: |
| #1098 | |
| #1099 | - `PNL_i < 0`, and |
| #1100 | - `effective_pos_q(i) == 0` |
| #1101 | |
| #1102 | then the engine MUST: |
| #1103 | |
| #1104 | 1. call `absorb_protocol_loss((-PNL_i) as u128)` |
| #1105 | 2. `set_pnl(i, 0)` |
| #1106 | |
| #1107 | This path is allowed only for truly flat accounts whose current-state side effects are already locally authoritative through `touch_account_full` or an equivalent already-touched liquidation subroutine. A pure `deposit` path that does not call `accrue_market_to` and does not make new current-state side effects authoritative MUST NOT invoke this path. |
| #1108 | |
| #1109 | ### 7.4 Profit conversion |
| #1110 | |
| #1111 | Profit conversion removes matured released profit and converts only its haircutted backed portion into protected principal. |
| #1112 | |
| #1113 | In this specification's automatic touch flow, this helper is invoked only on touched states with `basis_pos_q_i == 0`. Open-position accounts that want to voluntarily realize matured profit without closing may instead use the explicit `convert_released_pnl` instruction of §10.4.1. |
| #1114 | |
| #1115 | On an eligible touched state, define `x = ReleasedPos_i`. If `x == 0`, do nothing. |
| #1116 | |
| #1117 | Compute `y` using the pre-conversion haircut ratio from §3: |
| #1118 | |
| #1119 | - because `x > 0` implies `PNL_matured_pos_tot > 0`, define `y = mul_div_floor_u128(x, h_num, h_den)` |
| #1120 | |
| #1121 | Apply: |
| #1122 | |
| #1123 | 1. `consume_released_pnl(i, x)` |
| #1124 | 2. `set_capital(i, checked_add_u128(C_i, y))` |
| #1125 | 3. if `R_i == 0`: |
| #1126 | - `w_slope_i = 0` |
| #1127 | - `w_start_i = current_slot` |
| #1128 | 4. else leave the existing warmup schedule unchanged |
| #1129 | |
| #1130 | Profit conversion MUST NOT reduce `R_i`. Any still-reserved warmup balance remains reserved and continues to mature only through §6. |
| #1131 | |
| #1132 | ### 7.5 Fee-debt sweep |
| #1133 | |
| #1134 | After any operation that increases `C_i`, the engine MUST pay down fee debt as soon as that newly available capital is no longer senior-encumbered by all higher-seniority trading losses already attached to the account's locally authoritative state. |
| #1135 | |
| #1136 | This means: |
| #1137 | |
| #1138 | - sweep MUST occur immediately after profit conversion, because the conversion created new capital and the touched account's current-state trading losses have already been settled |
| #1139 | - sweep MUST occur in `deposit` only after `settle_losses_from_principal`, and only when `basis_pos_q_i == 0` and `PNL_i >= 0` |
| #1140 | - on a truly flat authoritative state, zero or positive `PNL_i` does not senior-encumber newly available capital; only a surviving negative `PNL_i` blocks the sweep |
| #1141 | - a pure `deposit` into an account with `basis_pos_q_i != 0` MUST defer fee-debt sweep until a later full current-state touch, because unresolved A/K side effects are still senior to protocol fee collection from that capital |
| #1142 | - sweep MUST NOT be deferred across instructions once capital is both present and no longer senior-encumbered |
| #1143 | - a direct external repayment through `deposit_fee_credits` (§10.3.1) is **not** a capital sweep and does not pass through `C_i`; it directly increases `I` and reduces `fee_credits_i` |
| #1144 | |
| #1145 | The sweep is: |
| #1146 | |
| #1147 | 1. `debt = fee_debt_u128_checked(fee_credits_i)` |
| #1148 | 2. `pay = min(debt, C_i)` |
| #1149 | 3. if `pay > 0`: |
| #1150 | - `set_capital(i, C_i - pay)` |
| #1151 | - `fee_credits_i = checked_add_i128(fee_credits_i, pay as i128)` |
| #1152 | - `I = checked_add_u128(I, pay)` |
| #1153 | |
| #1154 | --- |
| #1155 | |
| #1156 | ## 8. Fees |
| #1157 | |
| #1158 | This revision has no separate `fee_revenue` bucket. All explicit fee collections and direct fee-credit repayments accrue into `I`. |
| #1159 | |
| #1160 | ### 8.1 Trading fees |
| #1161 | |
| #1162 | Trading fees are explicit transfers to insurance and MUST NOT be socialized through `h` or `D`. |
| #1163 | |
| #1164 | Define: |
| #1165 | |
| #1166 | - `fee = mul_div_ceil_u128(trade_notional, trading_fee_bps, 10_000)` |
| #1167 | |
| #1168 | with `0 <= trading_fee_bps <= MAX_TRADING_FEE_BPS`. |
| #1169 | |
| #1170 | Rules: |
| #1171 | |
| #1172 | - if `trading_fee_bps == 0` or `trade_notional == 0`, then `fee = 0` |
| #1173 | - if `trading_fee_bps > 0` and `trade_notional > 0`, then `fee >= 1` |
| #1174 | |
| #1175 | The fee MUST be charged using `charge_fee_to_insurance(i, fee)`. |
| #1176 | |
| #1177 | Deployment guidance: even though the strict risk-reducing trade exemption of §10.5 now holds the explicit fee of the candidate trade constant for the before/after buffer comparison, high trading fees still worsen the actual post-trade state. Deployments that want voluntary partial de-risking to remain broadly usable SHOULD configure `trading_fee_bps` materially below `maintenance_bps`. |
| #1178 | |
| #1179 | ### 8.2 Account-local recurring maintenance fees (disabled in this revision) |
| #1180 | |
| #1181 | Recurring account-local maintenance fees are disabled in this revision. |
| #1182 | |
| #1183 | Normative consequences: |
| #1184 | |
| #1185 | 1. Implementations MUST NOT realize any recurring account-local maintenance fee. |
| #1186 | 2. No wall-clock passage may create fee debt except through explicitly specified protocol-fee entrypoints. |
| #1187 | 3. `last_fee_slot_i` remains deterministic metadata only. |
| #1188 | 4. `touch_account_full` MUST stamp `last_fee_slot_i = current_slot` so the reserved field remains monotonic and deterministic, but this stamp has no economic effect. |
| #1189 | 5. Any implementation-defined recurring maintenance-fee accumulator, event-segmentation method, or fee realization path is non-compliant with this revision. |
| #1190 | |
| #1191 | ### 8.3 Liquidation fees |
| #1192 | |
| #1193 | The protocol MUST define: |
| #1194 | |
| #1195 | - `liquidation_fee_bps` with `0 <= liquidation_fee_bps <= MAX_LIQUIDATION_FEE_BPS` |
| #1196 | - `liquidation_fee_cap` with `0 <= liquidation_fee_cap <= MAX_PROTOCOL_FEE_ABS` |
| #1197 | - `min_liquidation_abs` with `0 <= min_liquidation_abs <= liquidation_fee_cap` |
| #1198 | |
| #1199 | For a liquidation that closes `q_close_q` at `oracle_price`, define: |
| #1200 | |
| #1201 | - if `q_close_q == 0`, then `liq_fee = 0` |
| #1202 | - else: |
| #1203 | - `closed_notional = mul_div_floor_u128(q_close_q, oracle_price, POS_SCALE)` |
| #1204 | - `liq_fee_raw = mul_div_ceil_u128(closed_notional, liquidation_fee_bps, 10_000)` |
| #1205 | - `liq_fee = min(max(liq_fee_raw, min_liquidation_abs), liquidation_fee_cap)` |
| #1206 | |
| #1207 | The short-circuit is on `q_close_q`, not `closed_notional`. Therefore the minimum fee floor applies even when `closed_notional` floors to zero. |
| #1208 | |
| #1209 | ### 8.4 Fee debt as margin liability |
| #1210 | |
| #1211 | `FeeDebt_i = fee_debt_u128_checked(fee_credits_i)`: |
| #1212 | |
| #1213 | - MUST reduce `Eq_maint_raw_i`, `Eq_net_i`, `Eq_init_raw_i`, and therefore also the derived `Eq_init_net_i` |
| #1214 | - MUST be swept whenever principal becomes available and is no longer senior-encumbered by already-realized trading losses on the same local state |
| #1215 | - MUST NOT directly change `Residual`, `PNL_pos_tot`, or `PNL_matured_pos_tot` |
| #1216 | |
| #1217 | --- |
| #1218 | |
| #1219 | ## 9. Margin checks and liquidation |
| #1220 | |
| #1221 | ### 9.1 Margin requirements |
| #1222 | |
| #1223 | After `touch_account_full(i, oracle_price, now_slot)`, define: |
| #1224 | |
| #1225 | - `Notional_i = mul_div_floor_u128(abs(effective_pos_q(i)), oracle_price, POS_SCALE)` |
| #1226 | - if `effective_pos_q(i) == 0`: |
| #1227 | - `MM_req_i = 0` |
| #1228 | - `IM_req_i = 0` |
| #1229 | - else: |
| #1230 | - `MM_req_i = max(mul_div_floor_u128(Notional_i, maintenance_bps, 10_000), MIN_NONZERO_MM_REQ)` |
| #1231 | - `IM_req_i = max(mul_div_floor_u128(Notional_i, initial_bps, 10_000), MIN_NONZERO_IM_REQ)` |
| #1232 | |
| #1233 | Healthy conditions: |
| #1234 | |
| #1235 | - maintenance healthy if `Eq_net_i > MM_req_i as i128` |
| #1236 | - initial-margin healthy if exact `Eq_init_raw_i >= (IM_req_i as wide_signed)` in the widened signed domain of §3.4 |
| #1237 | |
| #1238 | These absolute nonzero-position floors are a finite-capacity liveness safeguard. A microscopic open position MUST NOT evade both initial-margin and maintenance enforcement solely because proportional notional floors to zero. |
| #1239 | |
| #1240 | ### 9.2 Risk-increasing and strict risk-reducing trades |
| #1241 | |
| #1242 | A trade for account `i` is **risk-increasing** when either: |
| #1243 | |
| #1244 | 1. `abs(new_eff_pos_q_i) > abs(old_eff_pos_q_i)`, or |
| #1245 | 2. the position sign flips across zero, or |
| #1246 | 3. `old_eff_pos_q_i == 0` and `new_eff_pos_q_i != 0` |
| #1247 | |
| #1248 | A trade is **strictly risk-reducing** when: |
| #1249 | |
| #1250 | - `old_eff_pos_q_i != 0` |
| #1251 | - `new_eff_pos_q_i != 0` |
| #1252 | - `sign(new_eff_pos_q_i) == sign(old_eff_pos_q_i)` |
| #1253 | - `abs(new_eff_pos_q_i) < abs(old_eff_pos_q_i)` |
| #1254 | |
| #1255 | ### 9.3 Liquidation eligibility |
| #1256 | |
| #1257 | An account is liquidatable when after a full `touch_account_full`: |
| #1258 | |
| #1259 | - `effective_pos_q(i) != 0`, and |
| #1260 | - `Eq_net_i <= MM_req_i as i128` |
| #1261 | |
| #1262 | ### 9.4 Partial liquidation |
| #1263 | |
| #1264 | A liquidation MAY be partial only if it closes a strictly positive quantity smaller than the full remaining effective position: |
| #1265 | |
| #1266 | - `0 < q_close_q < abs(old_eff_pos_q_i)` |
| #1267 | |
| #1268 | A successful partial liquidation MUST: |
| #1269 | |
| #1270 | 1. use the current touched state |
| #1271 | 2. let `old_eff_pos_q_i = effective_pos_q(i)` and require `old_eff_pos_q_i != 0` |
| #1272 | 3. determine `liq_side = side(old_eff_pos_q_i)` |
| #1273 | 4. define `new_eff_abs_q = checked_sub_u128(abs(old_eff_pos_q_i), q_close_q)` |
| #1274 | 5. require `new_eff_abs_q > 0` |
| #1275 | 6. define `new_eff_pos_q_i = sign(old_eff_pos_q_i) * (new_eff_abs_q as i128)` |
| #1276 | 7. close `q_close_q` synthetically at `oracle_price` with zero execution-price slippage |
| #1277 | 8. apply the resulting position using `attach_effective_position(i, new_eff_pos_q_i)` |
| #1278 | 9. settle realized losses from principal via §7.1 |
| #1279 | 10. compute `liq_fee` per §8.3 on the quantity actually closed |
| #1280 | 11. charge that fee using `charge_fee_to_insurance(i, liq_fee)` |
| #1281 | 12. invoke `enqueue_adl(ctx, liq_side, q_close_q, 0)` to decrease global OI and socialize quantity reduction |
| #1282 | 13. if either pending-reset flag becomes true in `ctx`, stop any further live-OI-dependent checks or mutations; only the remaining local post-step validation of step 14 may still run before end-of-instruction reset handling |
| #1283 | 14. require the resulting nonzero position to be maintenance healthy on the current post-step-12 state, i.e. recompute `Notional_i`, `MM_req_i`, `Eq_maint_raw_i`, and `Eq_net_i` from that current local state and require maintenance health under §9.1 |
| #1284 | |
| #1285 | The step-14 health check is a purely local post-partial validation and MUST still be evaluated even when step 13 has scheduled a pending reset. It uses only the post-step local maintenance quantities and oracle price; it does not depend on the matured-profit haircut ratio `h` or on any further live-OI mutation after `enqueue_adl`. |
| #1286 | |
| #1287 | ### 9.5 Full-close / bankruptcy liquidation |
| #1288 | |
| #1289 | The engine MUST be able to perform a deterministic full-close liquidation on an already-touched liquidatable account. When the resulting post-close state leaves uncovered negative `PNL_i` after principal exhaustion and liquidation fees, that uncovered amount is the bankruptcy deficit handled below. |
| #1290 | |
| #1291 | Full-close liquidation is a local subroutine on the current touched state. It MUST NOT call `touch_account_full` again. |
| #1292 | |
| #1293 | It MUST: |
| #1294 | |
| #1295 | 1. use the current touched state |
| #1296 | 2. let `old_eff_pos_q_i = effective_pos_q(i)` and require `old_eff_pos_q_i != 0` |
| #1297 | 3. set `q_close_q = abs(old_eff_pos_q_i)`; full-close liquidation MUST strictly close the full remaining effective position |
| #1298 | 4. let `liq_side = side(old_eff_pos_q_i)` |
| #1299 | 5. because the close is synthetic, it MUST execute exactly at `oracle_price` with zero execution-price slippage |
| #1300 | 6. `attach_effective_position(i, 0)` |
| #1301 | 7. `OI_eff_liq_side` MUST NOT be decremented anywhere except through `enqueue_adl` |
| #1302 | 8. `settle_losses_from_principal(i)` |
| #1303 | 9. compute `liq_fee` per §8.3 and charge it via `charge_fee_to_insurance(i, liq_fee)` |
| #1304 | 10. determine the uncovered bankruptcy deficit `D`: |
| #1305 | - if `PNL_i < 0`, let `D = (-PNL_i) as u128` |
| #1306 | - else `D = 0` |
| #1307 | 11. if `q_close_q > 0` or `D > 0`, invoke `enqueue_adl(ctx, liq_side, q_close_q, D)` |
| #1308 | 12. if `D > 0`, `set_pnl(i, 0)` |
| #1309 | |
| #1310 | ### 9.6 Side-mode gating |
| #1311 | |
| #1312 | Before any top-level instruction rejects an OI-increasing operation because a side is in `ResetPending`, it MUST first invoke `maybe_finalize_ready_reset_sides_before_oi_increase()`. |
| #1313 | |
| #1314 | Any operation that would increase net side OI on a side whose mode is `DrainOnly` or `ResetPending` MUST be rejected. |
| #1315 | |
| #1316 | For `execute_trade`, this prospective check MUST use the exact bilateral candidate after-values of §5.2.2 on both sides. Open-only heuristics, single-account approximations, or any decomposition other than §5.2.2 are non-compliant. |
| #1317 | |
| #1318 | --- |
| #1319 | |
| #1320 | ## 10. External operations |
| #1321 | |
| #1322 | ### 10.0 Standard instruction lifecycle |
| #1323 | |
| #1324 | Unless explicitly noted otherwise (for example `deposit`, `deposit_fee_credits`, `top_up_insurance_fund`, and `reclaim_empty_account`), an external state-mutating operation that accepts `oracle_price` and `now_slot` executes inside the same standard lifecycle: |
| #1325 | |
| #1326 | 1. validate trusted monotonic slot inputs and the validated oracle input required by that endpoint |
| #1327 | 2. initialize a fresh instruction context `ctx` |
| #1328 | 3. perform the endpoint's exact current-state inner execution |
| #1329 | 4. call `schedule_end_of_instruction_resets(ctx)` exactly once |
| #1330 | 5. call `finalize_end_of_instruction_resets(ctx)` exactly once |
| #1331 | 6. recompute `r_last` exactly once from the final post-reset state |
| #1332 | 7. if the instruction can mutate live side exposure, assert `OI_eff_long == OI_eff_short` at the end |
| #1333 | |
| #1334 | This subsection is a condensation aid only. The endpoint subsections below remain the normative source of truth for exact call ordering, including any endpoint-specific exceptions or additional guards. |
| #1335 | |
| #1336 | ### 10.1 `touch_account_full(i, oracle_price, now_slot)` |
| #1337 | |
| #1338 | Canonical settle routine for an existing materialized account. It MUST perform, in order: |
| #1339 | |
| #1340 | 1. require account `i` is materialized |
| #1341 | 2. require trusted `now_slot >= current_slot` |
| #1342 | 3. require trusted `now_slot >= slot_last` |
| #1343 | 4. require validated `0 < oracle_price <= MAX_ORACLE_PRICE` |
| #1344 | 5. set `current_slot = now_slot` |
| #1345 | 6. call `accrue_market_to(now_slot, oracle_price)` |
| #1346 | 7. call `advance_profit_warmup(i)` |
| #1347 | 8. call `settle_side_effects(i)` |
| #1348 | 9. call `settle_losses_from_principal(i)` |
| #1349 | 10. if `effective_pos_q(i) == 0` and `PNL_i < 0`, resolve uncovered flat loss via §7.3 |
| #1350 | 11. set `last_fee_slot_i = current_slot` |
| #1351 | 12. if `basis_pos_q_i == 0`, convert matured released profits via §7.4 |
| #1352 | 13. sweep fee debt per §7.5 |
| #1353 | |
| #1354 | `touch_account_full` MUST NOT itself begin a side reset. |
| #1355 | |
| #1356 | ### 10.2 `settle_account(i, oracle_price, now_slot)` |
| #1357 | |
| #1358 | Standalone settle wrapper for an existing account. |
| #1359 | |
| #1360 | Procedure: |
| #1361 | |
| #1362 | 1. initialize fresh instruction context `ctx` |
| #1363 | 2. `touch_account_full(i, oracle_price, now_slot)` |
| #1364 | 3. `schedule_end_of_instruction_resets(ctx)` |
| #1365 | 4. `finalize_end_of_instruction_resets(ctx)` |
| #1366 | 5. recompute `r_last` exactly once from the final post-reset state |
| #1367 | |
| #1368 | This wrapper MUST NOT materialize a missing account. |
| #1369 | |
| #1370 | ### 10.3 `deposit(i, amount, now_slot)` |
| #1371 | |
| #1372 | `deposit` is a pure capital-transfer instruction. It MUST NOT call `accrue_market_to`, MUST NOT mutate side state, and MUST NOT auto-touch unrelated accounts. |
| #1373 | |
| #1374 | A pure deposit does **not** make unresolved A/K side effects locally authoritative. Therefore, for an account with `basis_pos_q_i != 0`, the deposit path MUST NOT treat the account as truly flat and MUST NOT sweep fee debt, because unresolved current-side trading losses remain senior until a later full current-state touch. |
| #1375 | |
| #1376 | A pure deposit also MUST NOT decrement `I` or record uninsured protocol loss. Therefore, even on a currently flat stored state, if negative PnL remains after principal settlement the deposit path MUST leave that remainder in `PNL_i` for a later full accrued touch. |
| #1377 | |
| #1378 | Procedure: |
| #1379 | |
| #1380 | 1. require trusted `now_slot >= current_slot` |
| #1381 | 2. if account `i` is missing: |
| #1382 | - require `amount >= MIN_INITIAL_DEPOSIT` |
| #1383 | - `materialize_account(i, now_slot)` |
| #1384 | 3. set `current_slot = now_slot` |
| #1385 | 4. require `checked_add_u128(V, amount) <= MAX_VAULT_TVL` |
| #1386 | 5. set `V = V + amount` |
| #1387 | 6. `set_capital(i, checked_add_u128(C_i, amount))` |
| #1388 | 7. `settle_losses_from_principal(i)` |
| #1389 | 8. MUST NOT invoke §7.3 or otherwise decrement `I` |
| #1390 | 9. if `basis_pos_q_i == 0` and `PNL_i >= 0`, sweep fee debt via §7.5 |
| #1391 | |
| #1392 | Because `deposit` cannot mutate OI, stored positions, stale-account counts, phantom-dust bounds, or side modes, it MAY omit §§5.7 end-of-instruction reset handling. |
| #1393 | |
| #1394 | ### 10.3.1 `deposit_fee_credits(i, amount, now_slot)` |
| #1395 | |
| #1396 | `deposit_fee_credits` is a direct external repayment of account-local fee debt. It is **not** a capital deposit, does **not** pass through `C_i`, and therefore does not subordinate trading losses. |
| #1397 | |
| #1398 | Procedure: |
| #1399 | |
| #1400 | 1. require account `i` is materialized |
| #1401 | 2. require trusted `now_slot >= current_slot` |
| #1402 | 3. set `current_slot = now_slot` |
| #1403 | 4. let `debt = fee_debt_u128_checked(fee_credits_i)` |
| #1404 | 5. let `pay = min(amount, debt)` |
| #1405 | 6. if `pay == 0`, return |
| #1406 | 7. require `checked_add_u128(V, pay) <= MAX_VAULT_TVL` |
| #1407 | 8. set `V = V + pay` |
| #1408 | 9. set `I = checked_add_u128(I, pay)` |
| #1409 | 10. set `fee_credits_i = checked_add_i128(fee_credits_i, pay as i128)` |
| #1410 | 11. require `fee_credits_i <= 0` |
| #1411 | |
| #1412 | Normative consequences: |
| #1413 | |
| #1414 | - the externally accounted repayment amount is exactly `pay`, not the user-specified `amount` |
| #1415 | - any over-request above the outstanding debt is silently capped and MUST NOT create positive `fee_credits_i` |
| #1416 | - the instruction MUST NOT call `accrue_market_to` |
| #1417 | - the instruction MUST NOT mutate side state, `C_i`, `PNL_i`, `R_i`, or any aggregate other than `V` and `I` |
| #1418 | |
| #1419 | ### 10.3.2 `top_up_insurance_fund(amount, now_slot)` |
| #1420 | |
| #1421 | `top_up_insurance_fund` is a direct external addition to the Insurance Fund and the vault. It does not credit any account principal. |
| #1422 | |
| #1423 | Procedure: |
| #1424 | |
| #1425 | 1. require trusted `now_slot >= current_slot` |
| #1426 | 2. set `current_slot = now_slot` |
| #1427 | 3. require `checked_add_u128(V, amount) <= MAX_VAULT_TVL` |
| #1428 | 4. set `V = V + amount` |
| #1429 | 5. set `I = checked_add_u128(I, amount)` |
| #1430 | |
| #1431 | This instruction MUST NOT call `accrue_market_to`, MUST NOT mutate any account-local state, and MUST NOT mutate side state. |
| #1432 | |
| #1433 | ### 10.4 `withdraw(i, amount, oracle_price, now_slot)` |
| #1434 | |
| #1435 | The minimum live-balance dust floor applies to **all** withdrawals, not only truly flat ones. This is a finite-capacity liveness safeguard: a temporary dust position MUST NOT be able to bypass the floor and then return to a flat unreclaimable sub-`MIN_INITIAL_DEPOSIT` account. |
| #1436 | |
| #1437 | Procedure: |
| #1438 | |
| #1439 | 1. require account `i` is materialized |
| #1440 | 2. initialize fresh instruction context `ctx` |
| #1441 | 3. `touch_account_full(i, oracle_price, now_slot)` |
| #1442 | 4. require `amount <= C_i` |
| #1443 | 5. require the post-withdraw capital `C_i - amount` is either `0` or `>= MIN_INITIAL_DEPOSIT` |
| #1444 | 6. if `effective_pos_q(i) != 0`, require post-withdraw initial-margin health on the hypothetical post-withdraw state where: |
| #1445 | - `C_i' = C_i - amount` |
| #1446 | - `V' = V - amount` |
| #1447 | - exact `Eq_init_raw_i` is recomputed from that hypothetical state and compared against `IM_req_i` in the widened signed domain of §3.4 |
| #1448 | - all other touched-state quantities are unchanged |
| #1449 | - equivalently, because both `V` and `C_tot` decrease by the same `amount`, `Residual` and `h` are unchanged by the simulation |
| #1450 | 7. apply: |
| #1451 | - `set_capital(i, C_i - amount)` |
| #1452 | - `V = V - amount` |
| #1453 | 8. `schedule_end_of_instruction_resets(ctx)` |
| #1454 | 9. `finalize_end_of_instruction_resets(ctx)` |
| #1455 | 10. recompute `r_last` exactly once from the final post-reset state |
| #1456 | |
| #1457 | ### 10.4.1 `convert_released_pnl(i, x_req, oracle_price, now_slot)` |
| #1458 | |
| #1459 | Explicit voluntary conversion of matured released positive PnL for an account that still has an open position. |
| #1460 | |
| #1461 | This instruction exists because ordinary `touch_account_full` auto-conversion is intentionally flat-only. It allows a user with an open position to realize matured profit into protected principal on current state, accept the resulting maintenance-equity change on their own terms, and immediately sweep any outstanding fee debt from the new capital. |
| #1462 | |
| #1463 | Procedure: |
| #1464 | |
| #1465 | 1. require account `i` is materialized |
| #1466 | 2. initialize fresh instruction context `ctx` |
| #1467 | 3. `touch_account_full(i, oracle_price, now_slot)` |
| #1468 | 4. if `basis_pos_q_i == 0`: |
| #1469 | - the ordinary touch flow has already auto-converted any released profit eligible on the now-flat state |
| #1470 | - `schedule_end_of_instruction_resets(ctx)` |
| #1471 | - `finalize_end_of_instruction_resets(ctx)` |
| #1472 | - recompute `r_last` exactly once from the final post-reset state |
| #1473 | - return |
| #1474 | 5. require `0 < x_req <= ReleasedPos_i` |
| #1475 | 6. compute `y` using the same pre-conversion haircut rule as §7.4: |
| #1476 | - because `x_req > 0` implies `PNL_matured_pos_tot > 0`, define `y = mul_div_floor_u128(x_req, h_num, h_den)` |
| #1477 | 7. `consume_released_pnl(i, x_req)` |
| #1478 | 8. `set_capital(i, checked_add_u128(C_i, y))` |
| #1479 | 9. sweep fee debt per §7.5 |
| #1480 | 10. require the current post-step-9 state is maintenance healthy if `effective_pos_q(i) != 0` |
| #1481 | 11. `schedule_end_of_instruction_resets(ctx)` |
| #1482 | 12. `finalize_end_of_instruction_resets(ctx)` |
| #1483 | 13. recompute `r_last` exactly once from the final post-reset state |
| #1484 | |
| #1485 | A failed post-conversion maintenance check MUST revert atomically. This instruction MUST NOT materialize a missing account. |
| #1486 | |
| #1487 | ### 10.5 `execute_trade(a, b, oracle_price, now_slot, size_q, exec_price)` |
| #1488 | |
| #1489 | `size_q > 0` means account `a` buys base from account `b`. |
| #1490 | |
| #1491 | Procedure: |
| #1492 | |
| #1493 | 1. require both accounts are materialized |
| #1494 | 2. require `a != b` |
| #1495 | 3. require trusted `now_slot >= current_slot` |
| #1496 | 4. require trusted `now_slot >= slot_last` |
| #1497 | 5. require validated `0 < oracle_price <= MAX_ORACLE_PRICE` |
| #1498 | 6. require validated `0 < exec_price <= MAX_ORACLE_PRICE` |
| #1499 | 7. require `0 < size_q <= MAX_TRADE_SIZE_Q` |
| #1500 | 8. compute `trade_notional = mul_div_floor_u128(size_q, exec_price, POS_SCALE)` |
| #1501 | 9. require `trade_notional <= MAX_ACCOUNT_NOTIONAL` |
| #1502 | 10. initialize fresh instruction context `ctx` |
| #1503 | 11. `touch_account_full(a, oracle_price, now_slot)` |
| #1504 | 12. `touch_account_full(b, oracle_price, now_slot)` |
| #1505 | 13. let `old_eff_pos_q_a = effective_pos_q(a)` and `old_eff_pos_q_b = effective_pos_q(b)` |
| #1506 | 14. let `MM_req_pre_a`, `MM_req_pre_b` be maintenance requirement on the post-touch pre-trade state |
| #1507 | 15. let `Eq_maint_raw_pre_a = Eq_maint_raw_a` and `Eq_maint_raw_pre_b = Eq_maint_raw_b` in the exact widened signed domain of §3.4 |
| #1508 | 16. let `margin_buffer_pre_a = Eq_maint_raw_pre_a - (MM_req_pre_a as wide_signed)` and `margin_buffer_pre_b = Eq_maint_raw_pre_b - (MM_req_pre_b as wide_signed)` in the exact widened signed domain of §3.4 |
| #1509 | 17. invoke `maybe_finalize_ready_reset_sides_before_oi_increase()` |
| #1510 | 18. define: |
| #1511 | - `new_eff_pos_q_a = checked_add_i128(old_eff_pos_q_a, size_q as i128)` |
| #1512 | - `new_eff_pos_q_b = checked_sub_i128(old_eff_pos_q_b, size_q as i128)` |
| #1513 | 19. require `abs(new_eff_pos_q_a) <= MAX_POSITION_ABS_Q` and `abs(new_eff_pos_q_b) <= MAX_POSITION_ABS_Q` |
| #1514 | 20. compute `OI_long_after_trade` and `OI_short_after_trade` exactly via §5.2.2 using `old_eff_pos_q_a`, `old_eff_pos_q_b`, `new_eff_pos_q_a`, and `new_eff_pos_q_b`; require `OI_long_after_trade <= MAX_OI_SIDE_Q` and `OI_short_after_trade <= MAX_OI_SIDE_Q`; reject if `mode_long ∈ {DrainOnly, ResetPending}` and `OI_long_after_trade > OI_eff_long`; reject if `mode_short ∈ {DrainOnly, ResetPending}` and `OI_short_after_trade > OI_eff_short` |
| #1515 | 21. apply immediate execution-slippage alignment PnL before fees: |
| #1516 | - `trade_pnl_num = checked_mul_i128(size_q as i128, (oracle_price as i128) - (exec_price as i128))` |
| #1517 | - `trade_pnl_a = floor_div_signed_conservative(trade_pnl_num, POS_SCALE)` |
| #1518 | - `trade_pnl_b = -trade_pnl_a` |
| #1519 | - record `old_R_a = R_a` and `old_R_b = R_b` |
| #1520 | - `set_pnl(a, checked_add_i128(PNL_a, trade_pnl_a))` |
| #1521 | - `set_pnl(b, checked_add_i128(PNL_b, trade_pnl_b))` |
| #1522 | - if `R_a > old_R_a`, invoke `restart_warmup_after_reserve_increase(a)` |
| #1523 | - if `R_b > old_R_b`, invoke `restart_warmup_after_reserve_increase(b)` |
| #1524 | 22. apply the resulting effective positions using `attach_effective_position(a, new_eff_pos_q_a)` and `attach_effective_position(b, new_eff_pos_q_b)` |
| #1525 | 23. update side OI atomically by writing the exact candidate after-values from step 20: |
| #1526 | - set `OI_eff_long = OI_long_after_trade` |
| #1527 | - set `OI_eff_short = OI_short_after_trade` |
| #1528 | 24. settle post-trade losses from principal for both accounts via §7.1 |
| #1529 | 25. if `new_eff_pos_q_a == 0`, require `PNL_a >= 0` after step 24 |
| #1530 | 26. if `new_eff_pos_q_b == 0`, require `PNL_b >= 0` after step 24 |
| #1531 | 27. compute `fee = mul_div_ceil_u128(trade_notional, trading_fee_bps, 10_000)` |
| #1532 | 28. charge explicit trading fees using `charge_fee_to_insurance(a, fee)` and `charge_fee_to_insurance(b, fee)` |
| #1533 | 29. enforce post-trade margin for each account using the current post-step-28 state: |
| #1534 | - if the resulting effective position is zero: |
| #1535 | - the flat-account guard from steps 25–26 still applies, and |
| #1536 | - require exact `Eq_maint_raw_i >= 0` in the widened signed domain of §3.4 on the current post-step-28 state |
| #1537 | - else if the trade is risk-increasing for that account, require exact raw initial-margin healthy using `Eq_init_raw_i` and `IM_req_i` as defined in §9.1 |
| #1538 | - else if the account is maintenance healthy using `Eq_net_i`, allow |
| #1539 | - else if the trade is strictly risk-reducing for that account, allow only if **both** of the following hold in the exact widened signed domain of §3.4: |
| #1540 | - the post-trade **fee-neutral** raw maintenance buffer `((Eq_maint_raw_i + (fee as wide_signed)) - (MM_req_i as wide_signed))` is strictly greater than the corresponding exact widened pre-trade raw maintenance buffer recorded in steps 15–16, and |
| #1541 | - the post-trade **fee-neutral** raw maintenance-equity shortfall below zero does not worsen, equivalently `min(Eq_maint_raw_i + (fee as wide_signed), 0) >= min(Eq_maint_raw_pre_i, 0)` |
| #1542 | - else reject |
| #1543 | |
| #1544 | A bilateral trade is valid only if **both** participating accounts independently satisfy one of the permitted post-trade conditions above. If either account fails, the entire instruction MUST revert atomically; one counterparty's strict risk-reducing exemption never rescues the other. |
| #1545 | |
| #1546 | This strict risk-reducing comparison is evaluated on the actual post-step-28 state but holds only the explicit fee of the candidate trade constant for the before/after comparison. Equivalently, it compares pre-trade raw maintenance buffer against post-trade raw maintenance buffer plus that same trade fee, so pure fee friction alone cannot make a genuinely de-risking trade fail the exemption. In addition, the fee-neutral raw maintenance-equity shortfall below zero must not worsen, so a large maintenance-requirement drop from a partial close cannot be used to mask newly created bad debt from execution slippage. All execution-slippage PnL, all position / notional changes, and all other current-state liabilities still remain in the comparison. Likewise, a voluntary organic flat close whose actual post-fee state would have negative exact `Eq_maint_raw_i` MUST still be rejected rather than exiting with unpaid fee debt that could later be forgiven by reclamation. |
| #1547 | 30. `schedule_end_of_instruction_resets(ctx)` |
| #1548 | 31. `finalize_end_of_instruction_resets(ctx)` |
| #1549 | 32. recompute `r_last` exactly once from the final post-reset state |
| #1550 | 33. assert `OI_eff_long == OI_eff_short` |
| #1551 | |
| #1552 | ### 10.6 `liquidate(i, oracle_price, now_slot, policy)` |
| #1553 | |
| #1554 | `policy` MUST be one of: |
| #1555 | |
| #1556 | - `FullClose` |
| #1557 | - `ExactPartial(q_close_q)` where `0 < q_close_q < abs(old_eff_pos_q_i)` on the already-touched current state |
| #1558 | |
| #1559 | No other liquidation-policy encoding is compliant in this revision. |
| #1560 | |
| #1561 | Procedure: |
| #1562 | |
| #1563 | 1. require account `i` is materialized |
| #1564 | 2. initialize fresh instruction context `ctx` |
| #1565 | 3. `touch_account_full(i, oracle_price, now_slot)` |
| #1566 | 4. require liquidation eligibility from §9.3 |
| #1567 | 5. if `policy == ExactPartial(q_close_q)`, attempt that exact partial-liquidation subroutine on the already-touched current state per §9.4, passing `ctx` through any `enqueue_adl` call; if any current-state validity check for that exact partial fails, reject |
| #1568 | 6. else (`policy == FullClose`), execute the full-close liquidation subroutine on the already-touched current state per §9.5, passing `ctx` through any `enqueue_adl` call |
| #1569 | 7. if any remaining nonzero position exists after liquidation, it MUST already have been reattached via `attach_effective_position` |
| #1570 | 8. `schedule_end_of_instruction_resets(ctx)` |
| #1571 | 9. `finalize_end_of_instruction_resets(ctx)` |
| #1572 | 10. recompute `r_last` exactly once from the final post-reset state |
| #1573 | 11. assert `OI_eff_long == OI_eff_short` |
| #1574 | |
| #1575 | ### 10.7 `reclaim_empty_account(i)` |
| #1576 | |
| #1577 | Permissionless empty- or flat-dust-account recycling wrapper. |
| #1578 | |
| #1579 | Procedure: |
| #1580 | |
| #1581 | 1. require account `i` is materialized |
| #1582 | 2. require all preconditions of §2.6 hold on the current state |
| #1583 | 3. execute the reclamation effects of §2.6 |
| #1584 | |
| #1585 | `reclaim_empty_account` MUST NOT call `accrue_market_to`, MUST NOT mutate side state, and MUST NOT materialize any account. |
| #1586 | |
| #1587 | ### 10.8 `keeper_crank(now_slot, oracle_price, ordered_candidates[], max_revalidations)` |
| #1588 | |
| #1589 | `keeper_crank` is the minimal on-chain permissionless shortlist processor. Candidate discovery, ranking, deduplication, and sequential simulation MAY be performed entirely off chain. `ordered_candidates[]` is an untrusted keeper-supplied ordered list of existing account identifiers and MAY include optional liquidation-policy hints in the same `FullClose` / `ExactPartial(q_close_q)` format used by §10.6. The on-chain program MUST treat every candidate and order choice as advisory only. A liquidation-policy hint is advisory in the sense that it is untrusted and MUST be ignored unless it is current-state-valid under this section. |
| #1590 | |
| #1591 | Procedure: |
| #1592 | |
| #1593 | 1. initialize fresh instruction context `ctx` |
| #1594 | 2. require trusted `now_slot >= current_slot` |
| #1595 | 3. require trusted `now_slot >= slot_last` |
| #1596 | 4. require validated `0 < oracle_price <= MAX_ORACLE_PRICE` |
| #1597 | 5. call `accrue_market_to(now_slot, oracle_price)` exactly once at the start |
| #1598 | 6. set `current_slot = now_slot` |
| #1599 | 7. let `attempts = 0` |
| #1600 | 8. for each candidate in keeper-supplied order: |
| #1601 | - if `attempts == max_revalidations`, break |
| #1602 | - if `ctx.pending_reset_long` or `ctx.pending_reset_short`, break |
| #1603 | - if candidate account is missing, continue |
| #1604 | - increment `attempts` by exactly `1` |
| #1605 | - perform one exact current-state revalidation attempt on that account by executing the same local state transition as `touch_account_full` on the already-accrued instruction state, namely the logic of §10.1 steps 7–13 in the same order; this local keeper helper MUST NOT call `accrue_market_to` again |
| #1606 | - if the account is liquidatable after that exact current-state touch and a current-state-valid liquidation-policy hint is present, the keeper MUST execute liquidation on the already-touched state using the same already-touched local liquidation execution as §§9.4–9.5 and §10.6 steps 4–7; the valid hint's exact policy is applied as-is, while an invalid or stale hint MUST be ignored; the keeper path MUST reuse `ctx`, MUST NOT repeat the touch, MUST NOT invoke end-of-instruction reset handling inside the loop, and MUST NOT nest a separate top-level instruction |
| #1607 | - if liquidation or the exact touch schedules a pending reset, break |
| #1608 | 9. `schedule_end_of_instruction_resets(ctx)` |
| #1609 | 10. `finalize_end_of_instruction_resets(ctx)` |
| #1610 | 11. recompute `r_last` exactly once from the final post-reset state |
| #1611 | 12. assert `OI_eff_long == OI_eff_short` |
| #1612 | |
| #1613 | Rules: |
| #1614 | |
| #1615 | - missing accounts MUST NOT be materialized |
| #1616 | - `max_revalidations` measures normal exact current-state revalidation attempts on materialized accounts; missing-account skips do not count |
| #1617 | - the engine MUST process candidates in keeper-supplied order except for the mandatory stop-on-pending-reset rule |
| #1618 | - the engine MUST NOT impose any on-chain liquidation-first ordering across keeper-supplied candidates |
| #1619 | - a candidate that proves safe or needs only cleanup after exact current-state touch still counts against `max_revalidations` |
| #1620 | - a fatal conservative failure or invariant violation encountered during exact touch or liquidation remains a top-level instruction failure and MUST revert atomically; `max_revalidations` is not a sandbox against corruption |
| #1621 | |
| #1622 | --- |
| #1623 | |
| #1624 | ## 11. Permissionless off-chain shortlist keeper mode |
| #1625 | |
| #1626 | This section is the sole normative specification for the optimized keeper path. Candidate discovery, ranking, deduplication, and sequential simulation MAY be performed entirely off chain. The protocol's on-chain safety derives only from exact current-state revalidation immediately before any liquidation write. |
| #1627 | |
| #1628 | ### 11.1 Core rules |
| #1629 | |
| #1630 | 1. The engine does **not** require any on-chain phase-1 search, barrier classifier, or no-false-negative scan proof. |
| #1631 | 2. `ordered_candidates[]` in §10.8 is keeper-supplied and untrusted. It MAY be stale, incomplete, duplicated, adversarially ordered, or produced by approximate heuristics. |
| #1632 | 3. Optional liquidation-policy hints are untrusted. They MUST be ignored unless they encode one of the §10.6 policies and pass the same exact current-state validity checks as the normal `liquidate` entrypoint. A current-state-valid hint is then applied exactly; otherwise that keeper attempt performs no liquidation action for that candidate. |
| #1633 | 4. The protocol MUST NOT require that a keeper discover *all* currently liquidatable accounts before it may process a useful subset. |
| #1634 | 5. Because `settle_account`, `liquidate`, `reclaim_empty_account`, and `keeper_crank` are permissionless, reset progress and dead-account recycling MUST remain possible without any mandatory on-chain scan order. |
| #1635 | |
| #1636 | ### 11.2 Exact current-state revalidation attempts |
| #1637 | |
| #1638 | Let `max_revalidations` be the keeper's per-instruction budget measured in **exact current-state revalidation attempts**. |
| #1639 | |
| #1640 | An exact current-state revalidation attempt begins when `keeper_crank` invokes the local exact-touch path on one materialized account after the single instruction-level `accrue_market_to(now_slot, oracle_price)` and `current_slot = now_slot` anchor. |
| #1641 | |
| #1642 | It counts against `max_revalidations` once that materialized-account revalidation reaches a normal per-candidate outcome, including when the account: |
| #1643 | |
| #1644 | - is liquidatable and is liquidated |
| #1645 | - is touched and only cleanup happens |
| #1646 | - is touched and proves safe |
| #1647 | - is touched, remains liquidatable, but no valid current-state liquidation action is applied for that attempt |
| #1648 | |
| #1649 | A pure missing-account skip does **not** count. |
| #1650 | |
| #1651 | Inside `keeper_crank`, the per-candidate local exact-touch helper MUST be economically equivalent to `touch_account_full(i, oracle_price, now_slot)` on a state that has already been globally accrued once to `(now_slot, oracle_price)` at the start of the instruction. Concretely, for each materialized candidate it MUST execute the same local logic and in the same order as §10.1 steps 7–13, and it MUST NOT call `accrue_market_to` again for that account. |
| #1652 | |
| #1653 | If the account is liquidatable after this local exact-touch path and a current-state-valid liquidation-policy hint is present, the keeper MUST invoke liquidation on the already-touched state using the same already-touched local liquidation execution as §§9.4–9.5 and §10.6 steps 4–7 and must apply that hint's exact policy. If no current-state-valid hint is present, that candidate receives no liquidation action in that attempt. The keeper path MUST NOT duplicate the touch, invoke end-of-instruction reset handling mid-loop, or nest a second top-level instruction. |
| #1654 | |
| #1655 | A fatal conservative failure or invariant violation encountered after an exact-touch attempt begins is **not** a counted skip. It is a top-level instruction failure and reverts atomically under §0. |
| #1656 | |
| #1657 | ### 11.3 On-chain ordering constraints |
| #1658 | |
| #1659 | The protocol MUST NOT impose a mandatory on-chain liquidation-first, cleanup-first, or priority-queue ordering across keeper-supplied candidates. |
| #1660 | |
| #1661 | Inside `keeper_crank`, the only mandatory on-chain ordering constraints are: |
| #1662 | |
| #1663 | 1. the single initial `accrue_market_to(now_slot, oracle_price)` and trusted `current_slot = now_slot` anchor happen before per-candidate exact revalidation |
| #1664 | 2. materialized candidates are processed in keeper-supplied order |
| #1665 | 3. once either pending-reset flag becomes true, the instruction stops further candidate processing and proceeds directly to end-of-instruction reset handling |
| #1666 | |
| #1667 | A stale or adversarial shortlist MAY waste that instruction's own `max_revalidations` budget or the submitting keeper's own call opportunity, but it MUST NOT permit an incorrect liquidation. |
| #1668 | |
| #1669 | ### 11.4 Honest-keeper guidance (non-normative) |
| #1670 | |
| #1671 | An honest keeper SHOULD, when compute permits, simulate the same single `accrue_market_to(now_slot, oracle_price)` step off chain, then sequentially simulate the shortlisted touches and liquidations on the evolving simulated state before submission. This is recommended because liquidation ordering is path-dependent through `A_side`, `K_side`, `OI_eff_*`, side modes, and end-of-instruction reset stop conditions. |
| #1672 | |
| #1673 | For off-chain ordering, an honest keeper SHOULD usually prioritize: |
| #1674 | |
| #1675 | - reset-progress or dust-progress candidates that can unblock finalization on already-constrained sides |
| #1676 | - opposite-side bankruptcy candidates **before** a touch that is expected to zero the last stored position on side `S` while phantom OI would remain on `S`, because once `stored_pos_count_S == 0` while phantom OI remains, further `D_rem` can no longer be written into `K_S` and is routed through uninsured protocol loss after insurance |
| #1677 | - otherwise, higher expected uncovered deficit after insurance, larger maintenance shortfall, larger notional, and `DrainOnly`-side candidates ahead of otherwise similar `Normal`-side candidates |
| #1678 | |
| #1679 | These `SHOULD` recommendations are operational guidance only, not consensus rules. |
| #1680 | |
| #1681 | ## 12. Required test properties (minimum) |
| #1682 | |
| #1683 | An implementation MUST include tests that cover at least: |
| #1684 | |
| #1685 | 1. **Conservation:** `V >= C_tot + I` always, and `Σ PNL_eff_matured_i <= Residual`. |
| #1686 | 2. **Fresh-profit reservation:** a positive `set_pnl` increase raises `R_i` by the same positive delta and does not immediately increase `PNL_matured_pos_tot`. |
| #1687 | 3. **Oracle-manipulation haircut safety:** fresh, unwarmed manipulated PnL cannot dilute `h`, cannot satisfy initial-margin or withdrawal checks, and cannot reduce another account's equity before warmup release; it MAY only support the generating account's own maintenance equity. |
| #1688 | 4. **Warmup anti-retroactivity:** newly generated profit cannot inherit old dormant maturity headroom. |
| #1689 | 5. **Pure release slope preservation:** repeated touches do not create exponential-decay maturity. |
| #1690 | 6. **Same-epoch local settlement:** settlement of one account does not depend on any canonical-order prefix. |
| #1691 | 7. **Non-compounding quantity basis:** repeated same-epoch touches without explicit position mutation do not compound quantity-flooring loss. |
| #1692 | 8. **Dynamic dust bound:** after same-epoch zeroing events, basis replacements, and ADL multiplier truncations before a reset, authoritative OI on a side with no stored positions is bounded by that side's cumulative phantom-dust bound. |
| #1693 | 9. **Dust-clear scheduling:** dust clearance and reset initiation happen only at end of top-level instructions, never mid-instruction. |
| #1694 | 10. **Epoch-safe reset:** accounts cannot be attached to a new epoch before `begin_full_drain_reset` runs. |
| #1695 | 11. **Precision-exhaustion terminal drain:** if `A_candidate == 0` with `OI_post > 0`, the engine force-drains both sides instead of reverting. |
| #1696 | 12. **ADL representability fallback:** if `delta_K_abs` is non-representable or `K_opp + delta_K_exact` overflows, quantity socialization still proceeds and the remainder routes through `record_uninsured_protocol_loss`. |
| #1697 | 13. **Insurance-first deficit coverage:** `enqueue_adl` spends `I` down to `I_floor` before any remaining bankruptcy loss is socialized or left as junior undercollateralization. |
| #1698 | 14. **Unit consistency:** margin, notional, and fees use quote-token atomic units consistently. |
| #1699 | 15. **`set_pnl` aggregate safety:** positive-PnL updates do not overflow `PNL_pos_tot` or `PNL_matured_pos_tot`. |
| #1700 | 16. **`PNL_i == i128::MIN` forbidden:** every negation path is safe. |
| #1701 | 17. **Trading and liquidation fee shortfalls:** unpaid explicit fees become negative `fee_credits_i`, not `PNL_i` and not `D`. |
| #1702 | 18. **Zero-rate recomputation ordering:** every standard-lifecycle endpoint recomputes `r_last` exactly once and only from final post-reset state, and every compliant recomputation stores exactly `0`. |
| #1703 | 19. **Zero-rate funding surface determinism:** no compliant instruction in this revision applies a funding transfer, and `fund_px_last` always equals the oracle price from the most recent successful `accrue_market_to`. |
| #1704 | 20. **Flat-account negative remainder:** a flat account with negative `PNL_i` after principal exhaustion resolves through `absorb_protocol_loss` only in the allowed already-authoritative flat-account paths. |
| #1705 | 21. **Reset finalization:** after reconciling stale accounts, the side can leave `ResetPending` and accept fresh OI again. |
| #1706 | 22. **Deposit loss seniority:** in `deposit`, realized losses are settled from newly deposited principal before any outstanding fee debt is swept. |
| #1707 | 23. **Deposit materialization threshold:** a missing account cannot be materialized by a deposit smaller than `MIN_INITIAL_DEPOSIT`, while an existing materialized account may still receive smaller top-ups. |
| #1708 | 24. **Dust liquidation minimum fee:** if `q_close_q > 0` but `closed_notional` floors to zero, `liq_fee` still honors `min_liquidation_abs`. |
| #1709 | 25. **Risk-reducing trade exemption:** a strict non-flipping position reduction that improves the exact widened **fee-neutral** raw maintenance buffer is allowed even if the account remains below maintenance after the trade, but only if the same trade does not worsen the exact widened **fee-neutral** raw maintenance-equity shortfall below zero. A reduction whose fee-neutral raw maintenance buffer worsens, or whose fee-neutral negative raw maintenance equity becomes more negative, is rejected. |
| #1710 | 26. **Positive local PnL supports maintenance but not initial margin / withdrawal at face value:** on a touched generating account, maintenance uses full local `PNL_i`, so a freshly profitable account is not liquidated solely because profit is still warming up and pure warmup release on unchanged `PNL_i` does not reduce `Eq_maint_raw_i`; the same junior profit still cannot satisfy a risk-increasing initial-margin or withdrawal check except through the matured-haircutted component of exact `Eq_init_raw_i`. |
| #1711 | 27. **Reserve-loss ordering:** when positive `PNL_i` shrinks for true market-loss reasons, losses consume `R_i` before matured released positive PnL, so neutral price chop does not ratchet previously matured margin into reserve. |
| #1712 | 28. **Organic close bankruptcy guard:** a flat trade cannot bypass ADL by leaving negative `PNL_i` behind. |
| #1713 | 29. **Full-close liquidation requirement:** full-close liquidation always closes the full remaining effective position. |
| #1714 | 30. **Dead-account reclamation:** a flat account with `0 <= C_i < MIN_INITIAL_DEPOSIT`, zero `PNL_i`, zero `R_i`, zero basis, and nonpositive `fee_credits_i` can be reclaimed safely; any remaining dust capital is swept into `I` and the slot is reused. |
| #1715 | 31. **Missing-account safety:** `settle_account`, `withdraw`, `execute_trade`, `liquidate`, and `keeper_crank` do not materialize missing accounts. |
| #1716 | 32. **Standalone settle lifecycle:** `settle_account` can reconcile the last stale or dusty account and still trigger required reset scheduling/finalization and final-state funding recomputation. |
| #1717 | 33. **Off-chain shortlist stale/adversarial safety:** replaying or adversarially ordering an old shortlist cannot cause an incorrect liquidation, because `keeper_crank` revalidates each processed candidate on current state before any liquidation write. |
| #1718 | 34. **Keeper single global accrual:** `keeper_crank` calls `accrue_market_to(now_slot, oracle_price)` exactly once per instruction and per-candidate exact revalidation does not reaccrue the market. |
| #1719 | 35. **Keeper local-touch equivalence:** the per-candidate exact local touch used inside `keeper_crank` is economically equivalent to `touch_account_full` on the same already-accrued state. |
| #1720 | 36. **Keeper revalidation budget accounting:** `max_revalidations` bounds the number of normal exact current-state revalidation attempts on materialized accounts, including safe false positives and cleanup-only touches; missing-account skips do not count. Fatal conservative failures are instruction failures, not counted skips. |
| #1721 | 37. **No duplicate keeper touch before liquidation:** when `keeper_crank` liquidates a candidate, it does so from the already-touched current state and does not perform a second full touch of that same candidate inside the same attempt. |
| #1722 | 38. **Keeper local liquidation is not a nested top-level finalize:** the per-candidate keeper liquidation path executes only the already-touched local liquidation subroutine and does not call `schedule_end_of_instruction_resets`, `finalize_end_of_instruction_resets`, or `recompute_r_last_from_final_state()` mid-loop. |
| #1723 | 39. **Keeper candidate-order freedom:** the engine imposes no on-chain liquidation-first ordering across keeper-supplied candidates; a cleanup-first shortlist is processed in the keeper-supplied order unless a pending reset is scheduled. |
| #1724 | 40. **Keeper stop on pending reset:** once a candidate touch or liquidation schedules a pending reset, `keeper_crank` performs no further candidate processing before end-of-instruction reset handling. |
| #1725 | 41. **Permissionless reset or dust progress without on-chain scan:** targeted `settle_account` calls or targeted `keeper_crank` shortlists can reconcile stale accounts on a `ResetPending` side and can also clear targeted pre-reset dust-progress accounts on a side already within its phantom-dust-clear bound, without any on-chain phase-1 search. |
| #1726 | 42. **Post-reset funding recomputation in keeper:** `keeper_crank` recomputes `r_last` exactly once after final reset handling, and the stored value is exactly `0` under the zero-rate core profile. |
| #1727 | 43. **K-pair chronology correctness:** same-epoch and epoch-mismatch settlement call `wide_signed_mul_div_floor_from_k_pair(abs_basis, k_then, k_now, den)` in chronological order; a true loss cannot be settled as a gain due to swapped arguments. |
| #1728 | 44. **Deposit true-flat guard and latent-loss seniority:** a `deposit` into an account with `basis_pos_q_i != 0` neither routes unresolved negative PnL through §7.3 nor sweeps fee debt before a later full current-state touch. |
| #1729 | 45. **No duplicate full-close touch:** both the top-level `liquidate` path and the `keeper_crank` local liquidation path execute the already-touched full-close / bankruptcy liquidation subroutine without a second full touch or second deterministic `last_fee_slot_i` stamp. |
| #1730 | 46. **Zero-rate funding recomputation:** `recompute_r_last_from_final_state()` always stores `0` and never reverts. |
| #1731 | 47. **Keeper atomicity alignment:** a normal safe / cleanup / liquidated candidate counts against `max_revalidations`, but a fatal conservative failure during exact touch or liquidation reverts the whole instruction atomically rather than being treated as a counted skip. |
| #1732 | 48. **Exact raw maintenance-buffer comparison:** strict risk-reducing trade permission uses the exact widened signed pre/post raw maintenance buffers and cannot be satisfied solely because both sides of the comparison were clamped at the negative representation floor. |
| #1733 | 49. **Profit-conversion reserve preservation:** converting `ReleasedPos_i = x` leaves `R_i` unchanged and reduces both `PNL_pos_tot` and `PNL_matured_pos_tot` by exactly `x`; repeated settles cannot drain reserve faster than `advance_profit_warmup`. |
| #1734 | 50. **Flat-only automatic conversion:** an open-position `touch_account_full` does not automatically convert matured released profit into capital, while a truly flat touched state may convert it via §7.4. |
| #1735 | 51. **Universal withdrawal dust guard:** any withdrawal must leave either `0` capital or at least `MIN_INITIAL_DEPOSIT`; a materialize-open-dust-withdraw-close loop cannot end at a flat unreclaimable `C_i = 1` account. |
| #1736 | 52. **Explicit open-position profit conversion:** `convert_released_pnl` consumes only `ReleasedPos_i`, leaves `R_i` unchanged, sweeps fee debt from the new capital, and rejects atomically if the post-conversion open-position state is not maintenance healthy. |
| #1737 | 53. **Phantom-dust ADL ordering awareness:** if a keeper simulation zeroes the last stored position on a side while phantom OI remains, opposite-side bankruptcies processed after that point lose current-instruction K-socialization capacity; processing them before that zeroing touch preserves it. |
| #1738 | 54. **Exact-drain reset scheduling under OI symmetry:** whenever `enqueue_adl` reaches an opposing-zero branch (`OI == 0` after step 1, or `OI_post == 0`), the maintained `OI_eff_long == OI_eff_short` invariant implies the liquidated side is also authoritatively zero at that point, the required pending resets are scheduled, and subsequent close / liquidation attempts do not underflow against zero authoritative OI. |
| #1739 | 55. **Organic flat-close fee-debt guard:** if a trade would leave an account with resulting effective position `0` but exact post-fee `Eq_maint_raw_i < 0`, the instruction rejects atomically; a user cannot wash-trade away assets, exit flat with unpaid fee debt, and then reclaim the slot to forgive it. A profitable fast winner with positive reserved `R_i` and nonnegative exact post-fee `Eq_maint_raw_i` may still close risk to zero even though `Eq_init_raw_i` excludes that reserved profit. |
| #1740 | 56. **Exact raw initial-margin approval:** a risk-increasing trade or open-position withdrawal with exact `Eq_init_raw_i < IM_req_i` is rejected even if `Eq_init_net_i` would floor to `0` and the proportional notional term would otherwise floor low. |
| #1741 | 57. **Absolute nonzero-position margin floors:** any nonzero position faces at least `MIN_NONZERO_MM_REQ` and `MIN_NONZERO_IM_REQ`; a microscopic nonzero position cannot remain healthy or be newly opened solely because proportional notional floors to zero. |
| #1742 | 58. **Flat dust-capital reclamation:** a trade- or conversion-created flat account with `0 < C_i < MIN_INITIAL_DEPOSIT` cannot pin capacity permanently, because `reclaim_empty_account` may sweep that dust capital into `I` and recycle the slot. |
| #1743 | 59. **Epoch-gap invariant preservation:** every materialized nonzero-basis account is either attached to the current side epoch or lags by exactly one epoch while that side is `ResetPending`; a gap larger than one is rejected as corruption. |
| #1744 | 60. **Direct fee-credit repayment cap:** `deposit_fee_credits` applies only `min(amount, FeeDebt_i)`, never makes `fee_credits_i` positive, increases `V` and `I` by exactly the applied amount, and does not mutate `C_i` or side state. |
| #1745 | 61. **Insurance top-up bounded arithmetic:** `top_up_insurance_fund` uses checked addition, enforces `MAX_VAULT_TVL`, increases `V` and `I` by the same exact amount, and does not mutate any other state. |
| #1746 | 62. **Pure deposit no-insurance-draw:** `deposit` never calls `absorb_protocol_loss`, never decrements `I`, and leaves any surviving flat negative `PNL_i` in place for a later accrued touch. |
| #1747 | 63. **Bilateral trade approval atomicity:** if one trade counterparty qualifies under step 29 but the other fails every permitted branch, the entire trade reverts atomically. |
| #1748 | 64. **Exact trade OI decomposition and constrained-side gating:** §10.5 uses the exact bilateral candidate after-values of §5.2.2 both for constrained-side gating and for final OI writeback; sign flips are therefore handled as a same-side close plus opposite-side open without ambiguity. |
| #1749 | 65. **Liquidation policy determinism:** direct `liquidate` accepts only `FullClose` or `ExactPartial(q_close_q)`; keeper hints use the same format, valid keeper hints are applied exactly, and absent or invalid keeper hints cause no liquidation action for that candidate in that attempt. |
| #1750 | 66. **Flat authoritative deposit sweep:** on a flat authoritative state (`basis_pos_q_i == 0`) with `PNL_i >= 0`, `deposit` sweeps fee debt immediately after principal-loss settlement even when `PNL_i > 0` because of remaining warmup reserve or other positive flat PnL; only a surviving negative `PNL_i` blocks the sweep. |
| #1751 | 67. **Configuration immutability:** no runtime instruction in this revision can change `T`, fee parameters, margin parameters, liquidation parameters, `I_floor`, or the live-balance floors after initialization. |
| #1752 | 68. **Partial liquidation remainder nonzero:** any compliant partial liquidation satisfies `0 < q_close_q < abs(old_eff_pos_q_i)` and therefore produces strictly nonzero `new_eff_pos_q_i`; there is no zero-result partial-liquidation branch. |
| #1753 | 69. **Positive conversion denominator:** whenever flat auto-conversion or `convert_released_pnl` consumes `x > 0` released profit, `PNL_matured_pos_tot > 0` on that state and the haircut denominator is strictly positive. |
| #1754 | 70. **Partial-liquidation local health check survives reset scheduling:** if a partial liquidation reattaches a nonzero remainder and `enqueue_adl` schedules a pending reset in the same instruction, the instruction still evaluates the post-step local maintenance-health requirement of §9.4 on that remaining state before final reset handling; only further live-OI-dependent work is skipped. |
| #1755 | |
| #1756 | ## 13. Compatibility and upgrade notes |
| #1757 | |
| #1758 | 1. LP accounts and user accounts may share the same protected-principal and junior-profit mechanics. |
| #1759 | 2. The mandatory `O(1)` global aggregates for solvency are `C_tot`, `PNL_pos_tot`, and `PNL_matured_pos_tot`; the A/K side indices add `O(1)` state for lazy settlement. |
| #1760 | 3. This spec deliberately rejects hidden residual matching. Bankruptcy socialization occurs only through explicit Insurance Fund usage, explicit A/K state, or junior undercollateralization. |
| #1761 | 4. Any upgrade path from a version that did not maintain `R_i`, `PNL_matured_pos_tot`, `basis_pos_q_i`, `a_basis_i`, `stored_pos_count_*`, `stale_account_count_*`, or `phantom_dust_bound_*_q` consistently MUST complete migration before OI-increasing operations are re-enabled. |
| #1762 | 5. Any upgrade from an earlier integrated barrier-preview or addendum-based keeper design MAY drop the on-chain preview helper and barrier-scan logic once the exact current-state `keeper_crank` path and the shortlist-oriented tests from §12 are implemented. |
| #1763 | 6. Any future revision that wishes to re-enable nonzero funding or recurring maintenance fees MUST replace the zero-rate and fee-disabled rules of §§4.12 and 8.2 with a fully explicit normative formula, funding-transfer arithmetic, and test suite; implementation-defined formulas are non-compliant. |
| #1764 | 7. Any future revision that wishes to allow runtime parameter mutation MUST define an explicit safe update procedure that preserves warmup, margin, liquidation, and dust-floor invariants across the transition. |
| #1765 | |
| #1766 |