percolator-engine 0.4.0

Formally verified risk engine for perpetual futures — fair exits (H) and O(1) overhang clearing (A/K)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
# Risk Engine Spec (Source of Truth) — v12.1.0

**Combined Single-Document Native 128-bit Revision  
(Off-Chain Shortlist Keeper / Flat-Only Auto-Conversion / Full-Local-PnL Maintenance / Live Premium-Based Funding / Live Recurring Maintenance-Fee Accrual / Immutable Configuration / Unencumbered-Flat Deposit Sweep / Mandatory Post-Partial Local Health Check / Reclaim-Time Fee Realization Edition)**

**Design:** Protected Principal + Junior Profit Claims + Lazy A/K Side Indices (Native 128-bit Base-10 Scaling)  
**Status:** implementation source-of-truth (normative language: MUST / MUST NOT / SHOULD / MAY)  
**Scope:** perpetual DEX risk engine for a single quote-token vault.  
**Goal:** preserve conservation, bounded insolvency handling, oracle-manipulation resistance, deterministic recurring-fee realization, 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.

This is a single combined spec. It supersedes prior delta-style revisions by restating the full current design in one document. It replaces the earlier funding-disabled maintenance-fee-disabled profile with a live premium-based funding model and a live recurring account-local maintenance-fee model, both wired into the same exact current-state touch discipline.

## Change summary from v12.0.2

This revision preserves v12.0.2's live premium-based funding design and fixes the maintenance-fee-disabled profile by enabling recurring account-local maintenance fees without introducing non-minor inconsistencies.

1. **Recurring account-local maintenance fees are now enabled.** A new immutable configuration parameter `maintenance_fee_per_slot` defines a lazy per-materialized-account recurring fee realized from `last_fee_slot_i`.
2. **Maintenance-fee realization ordering is now explicit.** Full current-state touch realizes recurring maintenance fees only after trading-loss settlement and any allowed flat-loss absorption, and before profit conversion and fee-debt sweep.
3. **Pure capital-only instructions remain pure.** `deposit`, `deposit_fee_credits`, and `top_up_insurance_fund` do not realize recurring maintenance fees or current-state market effects; `reclaim_empty_account(i, now_slot)` is the only no-oracle path that may realize recurring maintenance fees on an already-flat state.
4. **Protocol-fee representability is now explicit.** `MAX_PROTOCOL_FEE_ABS` is increased to cover cumulative recurring-fee realization, and `charge_fee_to_insurance` now caps charging at the account's collectible capital-plus-fee-debt headroom so `fee_credits_i` never underflows its representable range.
5. **Tests and compatibility notes are updated.** The minimum test matrix now covers recurring maintenance-fee realization, pure-capital exclusion, reclaim-time realization, and deterministic fee-headroom saturation.

## 0. Security goals (normative)

The engine MUST provide the following properties.

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.

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.

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.

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.

5. **Conservation:** The engine MUST NOT create withdrawable claims exceeding vault tokens, except for explicitly bounded rounding slack.

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, liquidate, repay fee debt, or reclaim.

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.

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.

9. **No hidden protocol MM:** The protocol MUST NOT secretly internalize user flow against an undisclosed residual inventory.

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.

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.

12. **Protocol-fee neutrality:** Explicit protocol fees MUST either be collected into `I` immediately or tracked as account-local fee debt up to the account's collectible capital-plus-fee-debt limit. Any explicit fee amount beyond that collectible limit MUST be dropped rather than socialized through `h` or inflated into bankruptcy deficit `D`. Unpaid explicit fees within the collectible range MUST NOT inflate `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.

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.

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.

15. **Instruction-final funding anti-retroactivity:** The engine MUST expose instruction-final ordering such that a deployment wrapper can inject the next-interval `r_last` only after final post-reset state is known. For compliant deployments, if an instruction mutates any funding-rate input or wrapper state used to compute funding, the wrapper-supplied stored `r_last` MUST correspond to that instruction's final post-reset state, not any intermediate state.

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.

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.

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.

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, recurring maintenance-fee realization where explicitly allowed, 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.

20. **Configuration immutability within a market instance:** The warmup, recurring-fee, trading-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.

21. **Lazy recurring maintenance-fee realization:** Recurring maintenance fees MUST accrue deterministically from `last_fee_slot_i`. When realized, they MUST affect only `C_i`, `fee_credits_i`, `I`, `C_tot`, and `last_fee_slot_i`; they MUST NOT mutate `PNL_i`, `R_i`, any `K_side`, any `A_side`, any `OI_eff_*`, or bankruptcy deficit `D`.

**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.

---

## 1. Types, units, scaling, and arithmetic requirements

### 1.1 Amounts

- `u128` unsigned amounts are denominated in quote-token atomic units, positive-PnL aggregates, OI, fixed-point position magnitudes, and bounded fee amounts.
- `i128` signed amounts represent realized PnL, K-space liabilities, and fee-credit balances.
- `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.
- 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.

### 1.2 Prices and internal positions

- `POS_SCALE = 1_000_000` (6 decimal places of position precision).
- `price: u64` is quote-token atomic units per `1` base. There is no separate `PRICE_SCALE`.
- All external price inputs, including `oracle_price`, `exec_price`, and any stored funding-price sample, MUST satisfy `0 < price <= MAX_ORACLE_PRICE`.
- Internally the engine stores position bases as signed fixed-point base quantities:
  - `basis_pos_q_i: i128`, with units `(base * POS_SCALE)`.
- Effective notional at oracle is:
  - `Notional_i = mul_div_floor_u128(abs(effective_pos_q(i)), oracle_price, POS_SCALE)`.
- Trade fees MUST use executed trade size, not account notional:
  - `trade_notional = mul_div_floor_u128(size_q, exec_price, POS_SCALE)`.

### 1.3 A/K scale

- `ADL_ONE = 1_000_000` (6 decimal places of fractional decay accuracy).
- `A_side` is dimensionless and scaled by `ADL_ONE`.
- `K_side` has units `(ADL scale) * (quote atomic units per 1 base)`.

### 1.4 Concrete normative bounds

The following bounds are normative and MUST be enforced.

- `MAX_VAULT_TVL = 10_000_000_000_000_000`
- `MAX_ORACLE_PRICE = 1_000_000_000_000`
- `MAX_POSITION_ABS_Q = 100_000_000_000_000`
- `MAX_TRADE_SIZE_Q = MAX_POSITION_ABS_Q`
- `MAX_OI_SIDE_Q = 100_000_000_000_000`
- `MAX_ACCOUNT_NOTIONAL = 100_000_000_000_000_000_000`
- `MAX_PROTOCOL_FEE_ABS = 1_000_000_000_000_000_000_000_000_000_000_000_000`
- `MAX_MAINTENANCE_FEE_PER_SLOT = 10_000_000_000_000_000`
- configured `MIN_INITIAL_DEPOSIT` MUST satisfy `0 < MIN_INITIAL_DEPOSIT <= MAX_VAULT_TVL`
- configured `MIN_NONZERO_MM_REQ` and `MIN_NONZERO_IM_REQ` MUST satisfy `0 < MIN_NONZERO_MM_REQ < MIN_NONZERO_IM_REQ <= MIN_INITIAL_DEPOSIT`
- 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
- `MAX_ABS_FUNDING_BPS_PER_SLOT = 10_000`
- `|r_last| <= MAX_ABS_FUNDING_BPS_PER_SLOT`
- `MAX_TRADING_FEE_BPS = 10_000`
- `MAX_INITIAL_BPS = 10_000`
- `MAX_MAINTENANCE_BPS = 10_000`
- `MAX_LIQUIDATION_FEE_BPS = 10_000`
- configured margin parameters MUST satisfy `0 <= maintenance_bps <= initial_bps <= MAX_INITIAL_BPS`
- configured recurring-fee parameter MUST satisfy `0 <= maintenance_fee_per_slot <= MAX_MAINTENANCE_FEE_PER_SLOT`
- `MAX_FUNDING_DT = 65_535`
- `MAX_MATERIALIZED_ACCOUNTS = 1_000_000`
- `MAX_ACTIVE_POSITIONS_PER_SIDE` MUST be finite and MUST NOT exceed `MAX_MATERIALIZED_ACCOUNTS`
- `MAX_ACCOUNT_POSITIVE_PNL = 100_000_000_000_000_000_000_000_000_000_000`
- `MAX_PNL_POS_TOT = MAX_MATERIALIZED_ACCOUNTS * MAX_ACCOUNT_POSITIVE_PNL = 100_000_000_000_000_000_000_000_000_000_000_000_000`
- `MIN_A_SIDE = 1_000`
- `0 <= I_floor <= MAX_VAULT_TVL`
- `0 <= min_liquidation_abs <= liquidation_fee_cap <= MAX_PROTOCOL_FEE_ABS`
- `A_side > 0` whenever `OI_eff_side > 0` and the side is still representable.

The following interpretation is normative for dust accounting:

- `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.

### 1.5 Trusted time / oracle requirements

- `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.
- `oracle_price` MUST come from a validated configured oracle feed. Stale, invalid, or out-of-range oracle reads MUST fail conservatively before state mutation.
- Any helper or instruction that accepts `now_slot` MUST require `now_slot >= current_slot`.
- Any call to `accrue_market_to(now_slot, oracle_price)` MUST require `now_slot >= slot_last`.
- `current_slot` and `slot_last` MUST be monotonically nondecreasing.

### 1.6 Arithmetic requirements

The engine MUST satisfy all of the following.

1. All products involving `A_side`, `K_side`, `k_snap_i`, `basis_pos_q_i`, `effective_pos_q(i)`, `price`, the raw funding numerator `fund_px_0 * r_last * dt_sub`, recurring-fee due `maintenance_fee_per_slot * (current_slot - last_fee_slot_i)`, funding deltas, or ADL deltas MUST use checked arithmetic.
2. When `r_last != 0` and the accrual interval `dt > 0`, `accrue_market_to` MUST split `dt` into consecutive sub-steps each of length `dt_sub <= MAX_FUNDING_DT`, with any shorter remainder last. Mark-to-market MUST be applied once before the funding sub-step loop, not inside it. Each funding sub-step MUST use the same start-of-call funding-price snapshot `fund_px_0 = fund_px_last`, with any current-oracle update written only after the loop.
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.
4. Signed division with positive denominator MUST use the exact helper in §4.8.
5. Positive ceiling division MUST use the exact helper in §4.8.
6. Warmup-cap computation `w_slope_i * elapsed` MUST use `saturating_mul_u128_u64` or a formally equivalent min-preserving construction.
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.
8. Every increment of `stored_pos_count_*`, `phantom_dust_bound_*_q`, `C_tot`, `PNL_pos_tot`, `PNL_matured_pos_tot`, `V`, or `I` MUST use checked addition and MUST enforce the relevant configured bound.
9. Funding sub-steps MUST use the same `fund_term` value for both the long-side and short-side `K` deltas, and `fund_term` itself MUST be computed with `floor_div_signed_conservative`. Positive non-integral funding quotients therefore round down toward zero, while negative non-integral funding quotients round down away from zero toward negative infinity. Because individual account settlement also uses `wide_signed_mul_div_floor_from_k_pair` (mathematical floor), payer-side claims are realized weakly more negative than theoretical and receiver-side claims weakly less positive than theoretical, so aggregate claims cannot be minted by rounding in either sign.
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.
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.
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.
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.
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.
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.
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.
17. Global A-truncation dust added in `enqueue_adl` MUST be accounted using checked arithmetic and the exact conservative bound from §5.6.
18. `trade_notional <= MAX_ACCOUNT_NOTIONAL` MUST be enforced before charging trade fees.
19. Any out-of-bound external price input, any invalid oracle read, or any non-monotonic slot input MUST fail conservatively before state mutation.
20. `charge_fee_to_insurance` MUST cap its applied fee at the account's exact collectible capital-plus-fee-debt headroom. It MUST never set `fee_credits_i < -(i128::MAX)`.
21. 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`.
22. 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`.
23. Any realized recurring maintenance-fee amount MUST satisfy `fee_due <= MAX_PROTOCOL_FEE_ABS` before it is passed to `charge_fee_to_insurance`.

### 1.7 Reference 128-bit boundary proof

By clamping constants to base-10 metrics, on-chain persistent state fits natively in 128-bit registers without truncation.

Under live funding and live recurring maintenance fees, the following bounds are active and exercised during normal execution.

- Effective-position numerator: `MAX_POSITION_ABS_Q * ADL_ONE = 10^14 * 10^6 = 10^20`
- Notional / trade-notional numerator: `MAX_POSITION_ABS_Q * MAX_ORACLE_PRICE = 10^14 * 10^12 = 10^26`
- Trade slippage numerator: `MAX_TRADE_SIZE_Q * MAX_ORACLE_PRICE = 10^26`, which fits inside signed 128-bit
- Mark term max step: `ADL_ONE * MAX_ORACLE_PRICE = 10^18`
- Raw funding numerator max: `MAX_ORACLE_PRICE * MAX_ABS_FUNDING_BPS_PER_SLOT * MAX_FUNDING_DT ≈ 6.55 × 10^20`
- `fund_term` max magnitude: `MAX_ORACLE_PRICE * MAX_ABS_FUNDING_BPS_PER_SLOT * MAX_FUNDING_DT / 10_000 ≈ 6.55 × 10^16`
- Funding payer max step: `ADL_ONE * (MAX_ORACLE_PRICE * MAX_ABS_FUNDING_BPS_PER_SLOT * MAX_FUNDING_DT / 10_000) ≈ 6.55 × 10^22`
- Funding receiver numerator: `6.55 × 10^22 * ADL_ONE ≈ 6.55 × 10^28`
- `A_old * OI_post`: `10^6 * 10^14 = 10^20`
- `PNL_pos_tot` hard cap: `10^38 < u128::MAX ≈ 3.4 × 10^38`
- 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`
- Recurring maintenance-fee realization max: `MAX_MAINTENANCE_FEE_PER_SLOT * (2^64 - 1) ≈ 1.84 × 10^35 < MAX_PROTOCOL_FEE_ABS = 10^36 < i128::MAX`
- `K_side` overflow under max-step accumulation requires on the order of `10^12` years
- The always-wide paths remain:
  1. exact `pnl_delta`
  2. exact haircut multiply-divides
  3. exact ADL `delta_K_abs`

---

## 2. State model

### 2.1 Account state

For each materialized account `i`, the engine stores at least:

- `C_i: u128` — protected principal.
- `PNL_i: i128` — realized PnL claim.
- `R_i: u128` — reserved positive PnL that has not yet matured through warmup, with `0 <= R_i <= max(PNL_i, 0)`.
- `basis_pos_q_i: i128` — signed fixed-point base basis at the last explicit position mutation or forced zeroing.
- `a_basis_i: u128` — side multiplier in effect when `basis_pos_q_i` was last explicitly attached.
- `k_snap_i: i128` — last realized `K_side` snapshot.
- `epoch_snap_i: u64` — side epoch in which the basis is defined.
- `fee_credits_i: i128`.
- `last_fee_slot_i: u64` — last slot through which recurring maintenance fees have been realized for this account.
- `w_start_i: u64`.
- `w_slope_i: u128`.

Derived local quantities on a touched state:

- `ReleasedPos_i = max(PNL_i, 0) - R_i`
- `FeeDebt_i = fee_debt_u128_checked(fee_credits_i)`

Fee-credit bounds:

- `fee_credits_i` MUST be initialized to `0`.
- The engine MUST maintain `-(i128::MAX) <= fee_credits_i <= 0` at all times. `fee_credits_i == i128::MIN` is forbidden.

### 2.2 Global engine state

The engine stores at least:

- `V: u128`
- `I: u128`
- `I_floor: u128`
- `current_slot: u64`
- `P_last: u64`
- `slot_last: u64`
- `r_last: i64` — signed funding rate in basis points per slot, stored at the end of each standard-lifecycle instruction for use in the next interval's `accrue_market_to`. Positive means longs pay shorts. Bounded by `|r_last| <= MAX_ABS_FUNDING_BPS_PER_SLOT`.
- `fund_px_last: u64` — funding-price sample stored at the end of the most recent successful `accrue_market_to`. During a later `accrue_market_to(now_slot, oracle_price)`, funding over the elapsed interval intentionally uses the start-of-call snapshot of this field, and only after that elapsed-interval funding is processed does the engine update `fund_px_last = oracle_price` for the next interval.
- `A_long: u128`
- `A_short: u128`
- `K_long: i128`
- `K_short: i128`
- `epoch_long: u64`
- `epoch_short: u64`
- `K_epoch_start_long: i128`
- `K_epoch_start_short: i128`
- `OI_eff_long: u128`
- `OI_eff_short: u128`
- `mode_long ∈ {Normal, DrainOnly, ResetPending}`
- `mode_short ∈ {Normal, DrainOnly, ResetPending}`
- `stored_pos_count_long: u64`
- `stored_pos_count_short: u64`
- `stale_account_count_long: u64`
- `stale_account_count_short: u64`
- `phantom_dust_bound_long_q: u128`
- `phantom_dust_bound_short_q: u128`
- `C_tot: u128 = Σ C_i`
- `PNL_pos_tot: u128 = Σ max(PNL_i, 0)`
- `PNL_matured_pos_tot: u128 = Σ(max(PNL_i, 0) - R_i)`

The engine MUST also store, or deterministically derive from immutable configuration, at least:

- `T = warmup_period_slots`
- `maintenance_fee_per_slot`
- `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`

This revision has **no separate `fee_revenue` state** and **no global recurring maintenance-fee accumulator**. Explicit fee proceeds, realized recurring maintenance fees, and direct fee-credit repayments accrue into `I`. Recurring maintenance fees remain account-local until realized from `last_fee_slot_i`. The funding rate `r_last` is externally supplied by the deployment wrapper at the end of each standard-lifecycle instruction via the parameterized helper of §4.12.

Global invariants:

- `PNL_matured_pos_tot <= PNL_pos_tot <= MAX_PNL_POS_TOT`
- `C_tot <= V <= MAX_VAULT_TVL`
- `I <= V`
- `|r_last| <= MAX_ABS_FUNDING_BPS_PER_SLOT`

### 2.2.1 Configuration immutability

All configuration values that affect economics or liveness are immutable for the lifetime of a market instance in this revision.

No external instruction in this revision may change `T`, `maintenance_fee_per_slot`, `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.

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.

The funding rate `r_last` is not a configured parameter — it is recomputed by the deployment wrapper at the end of each standard-lifecycle instruction. The `MAX_ABS_FUNDING_BPS_PER_SLOT` bound is an engine constant and is immutable.

### 2.3 Materialized-account capacity

The engine MUST track the number of currently materialized account slots. That count MUST NOT exceed `MAX_MATERIALIZED_ACCOUNTS`.

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`.

Only the following path MAY materialize a missing account in this specification:

- a `deposit(i, amount, now_slot)` with `amount >= MIN_INITIAL_DEPOSIT`

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.

### 2.4 Canonical zero-position defaults

The canonical zero-position account defaults are:

- `basis_pos_q_i = 0`
- `a_basis_i = ADL_ONE`
- `k_snap_i = 0`
- `epoch_snap_i = 0`

These defaults are valid because all helpers that use side-attached snapshots MUST first require `basis_pos_q_i != 0`.

### 2.5 Account materialization

`materialize_account(i, slot_anchor)` MAY succeed only if the account is currently missing and materialized-account capacity remains below `MAX_MATERIALIZED_ACCOUNTS`.

On success, it MUST increment the materialized-account count and set:

- `C_i = 0`
- `PNL_i = 0`
- `R_i = 0`
- canonical zero-position defaults from §2.4
- `fee_credits_i = 0`
- `w_start_i = slot_anchor`
- `w_slope_i = 0`
- `last_fee_slot_i = slot_anchor`

### 2.6 Permissionless empty- or flat-dust-account reclamation

The engine MUST provide a permissionless reclamation path `reclaim_empty_account(i, now_slot)`.

It MAY begin only if all of the following hold on the pre-realization state:

- account `i` is materialized
- trusted `now_slot >= current_slot`
- `PNL_i == 0`
- `R_i == 0`
- `basis_pos_q_i == 0`
- `fee_credits_i <= 0`

The path MUST then:

1. set `current_slot = now_slot`
2. realize recurring maintenance fees per §8.2 on that already-flat state
3. require final reclaim eligibility:
   - `0 <= C_i < MIN_INITIAL_DEPOSIT`
   - `PNL_i == 0`
   - `R_i == 0`
   - `basis_pos_q_i == 0`
   - `fee_credits_i <= 0`

On success, it MUST:

- if `C_i > 0`:
  - let `dust = C_i`
  - `set_capital(i, 0)`
  - `I = checked_add_u128(I, dust)`
- forgive any negative `fee_credits_i` by setting `fee_credits_i = 0`
- reset all local fields to canonical zero / anchored defaults
- mark the slot missing / reusable
- decrement the materialized-account count

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.

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.

### 2.7 Initial market state

Market initialization MUST take, at minimum, `init_slot`, `init_oracle_price`, and configured fee / margin / insurance / materialization parameters.

At market initialization, the engine MUST set:

- `V = 0`
- `I = 0`
- `I_floor = configured I_floor`
- `C_tot = 0`
- `PNL_pos_tot = 0`
- `PNL_matured_pos_tot = 0`
- `current_slot = init_slot`
- `slot_last = init_slot`
- `P_last = init_oracle_price`
- `fund_px_last = init_oracle_price`
- `r_last = 0`
- `A_long = ADL_ONE`, `A_short = ADL_ONE`
- `K_long = 0`, `K_short = 0`
- `epoch_long = 0`, `epoch_short = 0`
- `K_epoch_start_long = 0`, `K_epoch_start_short = 0`
- `OI_eff_long = 0`, `OI_eff_short = 0`
- `mode_long = Normal`, `mode_short = Normal`
- `stored_pos_count_long = 0`, `stored_pos_count_short = 0`
- `stale_account_count_long = 0`, `stale_account_count_short = 0`
- `phantom_dust_bound_long_q = 0`, `phantom_dust_bound_short_q = 0`

### 2.8 Side modes and reset lifecycle

A side may be in one of three modes:

- `Normal`: ordinary operation
- `DrainOnly`: the side is live but has decayed below the safe precision threshold; OI on that side may decrease but MUST NOT increase
- `ResetPending`: the side has been fully drained and its prior epoch is awaiting stale-account reconciliation; no operation may increase OI on that side

`begin_full_drain_reset(side)` MAY succeed only if `OI_eff_side == 0`. It MUST:

1. set `K_epoch_start_side = K_side`
2. increment `epoch_side` by exactly `1`
3. set `A_side = ADL_ONE`
4. set `stale_account_count_side = stored_pos_count_side`
5. set `phantom_dust_bound_side_q = 0`
6. set `mode_side = ResetPending`

`finalize_side_reset(side)` MAY succeed only if all of the following hold:

- `mode_side == ResetPending`
- `OI_eff_side == 0`
- `stale_account_count_side == 0`
- `stored_pos_count_side == 0`

On success, it MUST set `mode_side = Normal`.

`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.

### 2.8.1 Epoch-gap invariant

For every materialized account with `basis_pos_q_i != 0` on side `s`, the engine MUST maintain exactly one of the following states:

- **current attachment:** `epoch_snap_i == epoch_s`, or
- **stale one-epoch lag:** `mode_s == ResetPending` and `epoch_snap_i + 1 == epoch_s`.

Epoch gaps larger than `1` are forbidden.

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.

---


## 3. Solvency, matured-profit haircut, and live equity

### 3.1 Residual backing available to matured junior profits

Define:

- `senior_sum = checked_add_u128(C_tot, I)`
- `Residual = max(0, V - senior_sum)`

Invariant: the engine MUST maintain `V >= senior_sum` at all times.

### 3.2 Matured positive-PnL aggregate

Define:

- `ReleasedPos_i = max(PNL_i, 0) - R_i`
- `PNL_matured_pos_tot = Σ ReleasedPos_i`

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`.

### 3.3 Global haircut ratio `h`

Let:

- if `PNL_matured_pos_tot == 0`, define `h = 1`
- else:
  - `h_num = min(Residual, PNL_matured_pos_tot)`
  - `h_den = PNL_matured_pos_tot`

For account `i` on a touched state:

- if `PNL_matured_pos_tot == 0`, `PNL_eff_matured_i = ReleasedPos_i`
- else `PNL_eff_matured_i = mul_div_floor_u128(ReleasedPos_i, h_num, h_den)`

Because each account is floored independently:

- `Σ PNL_eff_matured_i <= h_num <= Residual`

### 3.4 Live equity used by margin and liquidation

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:

- `Eq_init_base_i = (C_i as wide_signed) + min(PNL_i, 0) + (PNL_eff_matured_i as wide_signed)`

Then define:

- `Eq_init_raw_i = Eq_init_base_i - (FeeDebt_i as wide_signed)`
- `Eq_init_net_i = max(0, Eq_init_raw_i)`
- `Eq_maint_raw_i = (C_i as wide_signed) + (PNL_i as wide_signed) - (FeeDebt_i as wide_signed)`
- `Eq_net_i = max(0, Eq_maint_raw_i)`

Interpretation:

- `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.
- `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.
- `Eq_net_i` / `Eq_maint_raw_i` are the quantities used for maintenance-margin and liquidation checks. On a touched generating account, full local `PNL_i` counts here, whether currently released or still reserved.
- `FeeDebt_i` includes unpaid explicit trading, liquidation, and recurring maintenance fees.
- 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`.
- 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.

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.

- Positive overflow of these exact widened intermediates is unreachable under the configured bounds and MUST fail conservatively if encountered.
- 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.
- 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.

### 3.5 Conservatism under pending A/K side effects and warmup

Because live haircut uses only matured positive PnL:

- 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
- 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
- reserved fresh positive PnL MUST NOT enter another account's equity, the global haircut denominator, or any principal-conversion path before warmup release
- pending lazy ADL obligations MUST NOT be counted as backing in `Residual`

---

## 4. Canonical helpers

### 4.1 Checked scalar helpers

`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.

`checked_cast_i128(x)` means an exact cast from a bounded nonnegative integer to `i128`, or conservative failure if the cast would not fit.

### 4.2 `set_capital(i, new_C)`

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`.

### 4.3 `set_reserved_pnl(i, new_R)`

Preconditions:

- `new_R <= max(PNL_i, 0)`

Effects:

1. `old_pos = max(PNL_i, 0) as u128`
2. `old_rel = old_pos - R_i`
3. `new_rel = old_pos - new_R`
4. update `PNL_matured_pos_tot` by the exact delta from `old_rel` to `new_rel` using checked arithmetic
5. require resulting `PNL_matured_pos_tot <= PNL_pos_tot`
6. set `R_i = new_R`

### 4.4 `set_pnl(i, new_PNL)`

When changing `PNL_i` from `old` to `new`, the engine MUST:

1. require `new != i128::MIN`
2. let `old_pos = max(old, 0) as u128`
3. let `old_R = R_i`
4. let `old_rel = old_pos - old_R`
5. let `new_pos = max(new, 0) as u128`
6. require `new_pos <= MAX_ACCOUNT_POSITIVE_PNL`
7. if `new_pos > old_pos`:
   - `reserve_add = new_pos - old_pos`
   - `new_R = checked_add_u128(old_R, reserve_add)`
   - require `new_R <= new_pos`
8. else:
   - `pos_loss = old_pos - new_pos`
   - `new_R = old_R.saturating_sub(pos_loss)`
   - require `new_R <= new_pos`
9. let `new_rel = new_pos - new_R`
10. update `PNL_pos_tot` by the exact delta from `old_pos` to `new_pos` using checked arithmetic
11. require resulting `PNL_pos_tot <= MAX_PNL_POS_TOT`
12. update `PNL_matured_pos_tot` by the exact delta from `old_rel` to `new_rel` using checked arithmetic
13. require resulting `PNL_matured_pos_tot <= PNL_pos_tot`
14. set `PNL_i = new`
15. set `R_i = new_R`

**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.

### 4.4.1 `consume_released_pnl(i, x)`

This helper removes only matured released positive PnL and MUST leave `R_i` unchanged.

Preconditions:

- `x > 0`
- `x <= ReleasedPos_i`

Effects:

1. `old_pos = max(PNL_i, 0) as u128`
2. `old_R = R_i`
3. `old_rel = old_pos - old_R`
4. `new_pos = old_pos - x`
5. `new_rel = old_rel - x`
6. require `new_pos >= old_R`
7. update `PNL_pos_tot` by the exact delta from `old_pos` to `new_pos` using checked arithmetic
8. update `PNL_matured_pos_tot` by the exact delta from `old_rel` to `new_rel` using checked arithmetic
9. `PNL_i = checked_sub_i128(PNL_i, checked_cast_i128(x))`
10. require resulting `PNL_matured_pos_tot <= PNL_pos_tot`
11. leave `R_i` unchanged

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.

### 4.5 `set_position_basis_q(i, new_basis_pos_q)`

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`.

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.

### 4.6 `attach_effective_position(i, new_eff_pos_q)`

This helper MUST convert a current effective quantity into a new position basis at the current side state.

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:

- let `s = side(basis_pos_q_i)`
- if `epoch_snap_i == epoch_s`, compute `rem = (abs(basis_pos_q_i) * A_s) mod a_basis_i` in exact arithmetic
- if `rem != 0`, invoke `inc_phantom_dust_bound(s)`

If `new_eff_pos_q == 0`, it MUST:

- `set_position_basis_q(i, 0)`
- reset snapshots to canonical zero-position defaults

If `new_eff_pos_q != 0`, it MUST:

- require `abs(new_eff_pos_q) <= MAX_POSITION_ABS_Q`
- `set_position_basis_q(i, new_eff_pos_q)`
- `a_basis_i = A_side(new_eff_pos_q)`
- `k_snap_i = K_side(new_eff_pos_q)`
- `epoch_snap_i = epoch_side(new_eff_pos_q)`

### 4.7 Phantom-dust helpers

- `inc_phantom_dust_bound(side)` increments `phantom_dust_bound_side_q` by exactly `1` q-unit using checked addition.
- `inc_phantom_dust_bound_by(side, amount_q)` increments `phantom_dust_bound_side_q` by exactly `amount_q` q-units using checked addition.

### 4.8 Exact math helpers (normative)

The engine MUST use the following exact helpers.

**Signed conservative floor division**

`floor_div_signed_conservative(n, d)`:

- require `d > 0`
- `q = trunc_toward_zero(n / d)`
- `r = n % d`
- if `n < 0` and `r != 0`, return `q - 1`
- else return `q`

**Positive checked ceiling division**

`ceil_div_positive_checked(n, d)`:

- require `d > 0`
- `q = n / d`
- `r = n % d`
- if `r != 0`, return checked(`q + 1`)
- else return `q`

**Exact multiply-divide floor for nonnegative inputs**

`mul_div_floor_u128(a, b, d)`:

- require `d > 0`
- compute the exact quotient `q = floor(a * b / d)`
- this MUST be exact even if the exact product `a * b` exceeds native `u128`
- require `q <= u128::MAX`
- return `q`

**Exact multiply-divide ceil for nonnegative inputs**

`mul_div_ceil_u128(a, b, d)`:

- require `d > 0`
- compute the exact quotient `q = ceil(a * b / d)`
- this MUST be exact even if the exact product `a * b` exceeds native `u128`
- require `q <= u128::MAX`
- return `q`

**Exact wide signed multiply-divide floor from K snapshots**

`wide_signed_mul_div_floor_from_k_pair(abs_basis_u128, k_then_i128, k_now_i128, den_u128)`:

- require `den_u128 > 0`
- compute the exact signed wide difference `k_diff = k_now_i128 - k_then_i128` in a transient wide signed type
- compute the exact wide magnitude `p = abs_basis_u128 * abs(k_diff)`
- let `q = floor(p / den_u128)`
- let `r = p mod den_u128`
- if `k_diff >= 0`, return `q` as positive `i128` (require representable)
- if `k_diff < 0`, return `-q` if `r == 0`, else return `-(q + 1)` to preserve mathematical floor semantics (require representable)

**Checked fee-debt conversion**

`fee_debt_u128_checked(fee_credits)`:

- require `fee_credits != i128::MIN`
- if `fee_credits >= 0`, return `0`
- else return `(-fee_credits) as u128`

**Checked fee-credit headroom**

`fee_credit_headroom_u128_checked(fee_credits)`:

- require `fee_credits != i128::MIN`
- return `(i128::MAX as u128) - fee_debt_u128_checked(fee_credits)`

**Saturating warmup multiply**

`saturating_mul_u128_u64(a, b)`:

- if `a == 0` or `b == 0`, return `0`
- if `a > u128::MAX / (b as u128)`, return `u128::MAX`
- else return `a * (b as u128)`

**Wide ADL quotient helper**

`wide_mul_div_ceil_u128_or_over_i128max(a, b, d)`:

- require `d > 0`
- compute the exact quotient `q = ceil(a * b / d)` in a transient wide type
- if `q > i128::MAX as u128`, return the tagged result `OverI128Magnitude`
- else return `Ok(q as u128)`

### 4.9 Warmup helpers

`restart_warmup_after_reserve_increase(i)` MUST:

1. if `T == 0`:
   - `set_reserved_pnl(i, 0)`
   - `w_slope_i = 0`
   - `w_start_i = current_slot`
   - return
2. if `R_i == 0`:
   - `w_slope_i = 0`
   - `w_start_i = current_slot`
   - return
3. set `w_slope_i = max(1, floor(R_i / T))`
4. set `w_start_i = current_slot`

`advance_profit_warmup(i)` MUST:

1. if `R_i == 0`:
   - `w_slope_i = 0`
   - `w_start_i = current_slot`
   - return
2. if `T == 0`:
   - `set_reserved_pnl(i, 0)`
   - `w_slope_i = 0`
   - `w_start_i = current_slot`
   - return
3. `elapsed = current_slot - w_start_i`
4. `release = min(R_i, saturating_mul_u128_u64(w_slope_i, elapsed))`
5. if `release > 0`, `set_reserved_pnl(i, R_i - release)`
6. if `R_i == 0`, set `w_slope_i = 0`
7. set `w_start_i = current_slot`

### 4.10 `charge_fee_to_insurance(i, fee_abs)`

Preconditions:

- `fee_abs <= MAX_PROTOCOL_FEE_ABS`

Effects:

1. `debt_headroom = fee_credit_headroom_u128_checked(fee_credits_i)`
2. `collectible = checked_add_u128(C_i, debt_headroom)`
3. `fee_applied = min(fee_abs, collectible)`
4. `fee_paid = min(fee_applied, C_i)`
5. if `fee_paid > 0`:
   - `set_capital(i, C_i - fee_paid)`
   - `I = checked_add_u128(I, fee_paid)`
6. `fee_shortfall = fee_applied - fee_paid`
7. if `fee_shortfall > 0`:
   - `fee_credits_i = checked_sub_i128(fee_credits_i, fee_shortfall as i128)`
8. any excess `fee_abs - fee_applied` is permanently uncollectible and MUST be dropped; it MUST NOT mutate `PNL_i`, `PNL_pos_tot`, `PNL_matured_pos_tot`, any `K_side`, `D`, or `Residual`

This helper MUST NOT mutate `PNL_i`, `PNL_pos_tot`, `PNL_matured_pos_tot`, or any `K_side`.

### 4.11 Insurance-loss helpers

`use_insurance_buffer(loss_abs)`:

1. precondition: `loss_abs > 0`
2. `available_I = I.saturating_sub(I_floor)`
3. `pay_I = min(loss_abs, available_I)`
4. `I = I - pay_I`
5. return `loss_abs - pay_I`

`record_uninsured_protocol_loss(loss_abs)`:

- precondition: `loss_abs > 0`
- no additional decrement to `V` or `I` occurs
- the uncovered loss remains represented as junior undercollateralization through `Residual` and `h`

`absorb_protocol_loss(loss_abs)`:

1. precondition: `loss_abs > 0`
2. `loss_rem = use_insurance_buffer(loss_abs)`
3. if `loss_rem > 0`, `record_uninsured_protocol_loss(loss_rem)`

### 4.12 Funding-rate injection helper

The engine MUST define:

- `recompute_r_last_from_final_state(externally_computed_rate: i64)`

It MUST:

1. require `|externally_computed_rate| <= MAX_ABS_FUNDING_BPS_PER_SLOT`
2. store `r_last = externally_computed_rate`

The rate is computed by the deployment wrapper, not by the engine. The engine's only obligation is to validate the bound and store the value. The engine cannot verify that the supplied rate was actually derived from final post-reset state; that provenance is a separate deployment-wrapper compliance obligation.

Deployment wrappers that implement premium-based funding SHOULD compute the rate as:

- `clamp(premium_bps * k_bps / (100 * horizon_slots), -max_bps_per_slot, max_bps_per_slot)`

where `premium_bps = (mark_price - index_price) * 10000 / index_price` with validated positive `index_price`, `k_bps` is a multiplier (`100 = 1.00×`), `horizon_slots > 0` converts the premium to a per-slot rate, and `max_bps_per_slot` is the wrapper-side cap with `0 <= max_bps_per_slot <= MAX_ABS_FUNDING_BPS_PER_SLOT`. Positive rate means longs pay shorts. Markets without a mark/index distinction SHOULD pass `0`.

Consequences:

- `|r_last| <= MAX_ABS_FUNDING_BPS_PER_SLOT` holds by construction
- repeated invocations with the same input are idempotent
- for compliant deployments, the anti-retroactivity requirement of §5.5 is preserved: the stored rate reflects the state at the end of the instruction, applied during the next interval
- the engine does not verify rate provenance beyond the bound check; sourcing the input from final post-reset state is a deployment-wrapper obligation

In §10, any reference to `wrapper_computed_rate` is schematic shorthand for this deployment-wrapper output. For compliant deployments it is computed from the instruction's final post-reset state, but the engine core does not derive or verify that provenance internally.

---

## 5. Unified A/K side-index mechanics

### 5.1 Eager-equivalent event law

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:

- `q_q' = α q_q`
- `p' = p + β * q_q / POS_SCALE`

where:

- `α ∈ [0, 1]` is the surviving-position fraction
- `β` is quote PnL per unit pre-event base position

The cumulative side indices compose as:

- `A_new = A_old * α`
- `K_new = K_old + A_old * β`

### 5.2 `effective_pos_q(i)`

For an account `i` with nonzero basis:

- let `s = side(basis_pos_q_i)`
- if `epoch_snap_i != epoch_s`, then `effective_pos_q(i) = 0` for current-market risk purposes until the account is touched and zeroed
- else `effective_abs_pos_q(i) = mul_div_floor_u128(abs(basis_pos_q_i) as u128, A_s, a_basis_i)`
- `effective_pos_q(i) = sign(basis_pos_q_i) * effective_abs_pos_q(i)`

If `basis_pos_q_i == 0`, define `effective_pos_q(i) = 0`.

### 5.2.1 Side-OI components of a signed effective position

For any signed fixed-point position `q` in q-units:

- `OI_long_component(q) = max(q, 0) as u128`
- `OI_short_component(q) = max(-q, 0) as u128`

Because every reachable effective position satisfies `|q| <= MAX_POSITION_ABS_Q < i128::MAX`, both casts are exact.

### 5.2.2 Exact bilateral trade side-OI after-values

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:

- `old_long_a = OI_long_component(old_eff_pos_q_a)`
- `old_short_a = OI_short_component(old_eff_pos_q_a)`
- `old_long_b = OI_long_component(old_eff_pos_q_b)`
- `old_short_b = OI_short_component(old_eff_pos_q_b)`
- `new_long_a = OI_long_component(new_eff_pos_q_a)`
- `new_short_a = OI_short_component(new_eff_pos_q_a)`
- `new_long_b = OI_long_component(new_eff_pos_q_b)`
- `new_short_b = OI_short_component(new_eff_pos_q_b)`

Then the exact candidate side-OI after-values are:

- `OI_long_after_trade = (((OI_eff_long - old_long_a) - old_long_b) + new_long_a) + new_long_b`
- `OI_short_after_trade = (((OI_eff_short - old_short_a) - old_short_b) + new_short_a) + new_short_b`

All arithmetic above MUST use the checked helpers of §4.1.

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.

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.

### 5.3 `settle_side_effects(i)`

When touching account `i`:

1. if `basis_pos_q_i == 0`, return immediately
2. let `s = side(basis_pos_q_i)`
3. let `den = checked_mul_u128(a_basis_i, POS_SCALE)`
4. if `epoch_snap_i == epoch_s` (same epoch):
   - `q_eff_new = mul_div_floor_u128(abs(basis_pos_q_i) as u128, A_s, a_basis_i)`
   - record `old_R = R_i`
   - `pnl_delta = wide_signed_mul_div_floor_from_k_pair(abs(basis_pos_q_i) as u128, k_snap_i, K_s, den)`
   - `set_pnl(i, checked_add_i128(PNL_i, pnl_delta))`
   - if `R_i > old_R`, invoke `restart_warmup_after_reserve_increase(i)`
   - if `q_eff_new == 0`:
     - `inc_phantom_dust_bound(s)`
     - `set_position_basis_q(i, 0)`
     - reset snapshots to canonical zero-position defaults
   - else:
     - leave `basis_pos_q_i` and `a_basis_i` unchanged
     - set `k_snap_i = K_s`
     - set `epoch_snap_i = epoch_s`
5. else (epoch mismatch):
   - require `mode_s == ResetPending`
   - require `epoch_snap_i + 1 == epoch_s`
   - record `old_R = R_i`
   - `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)`
   - `set_pnl(i, checked_add_i128(PNL_i, pnl_delta))`
   - if `R_i > old_R`, invoke `restart_warmup_after_reserve_increase(i)`
   - `set_position_basis_q(i, 0)`
   - decrement `stale_account_count_s` using checked subtraction
   - reset snapshots to canonical zero-position defaults

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.

### 5.4 `accrue_market_to(now_slot, oracle_price)`

Before any operation that depends on current market state, the engine MUST call `accrue_market_to(now_slot, oracle_price)`.

This helper MUST:

1. require trusted `now_slot >= slot_last`
2. require validated `0 < oracle_price <= MAX_ORACLE_PRICE`
3. let `dt = now_slot - slot_last`
4. snapshot `OI_long_0 = OI_eff_long` and `OI_short_0 = OI_eff_short`; let `fund_px_0 = fund_px_last`
5. Mark-to-market (once): compute signed `ΔP = (oracle_price as i128) - (P_last as i128)`:
   - if `OI_long_0 > 0`, `K_long = checked_add_i128(K_long, checked_mul_i128(A_long as i128, ΔP))`
   - if `OI_short_0 > 0`, `K_short = checked_sub_i128(K_short, checked_mul_i128(A_short as i128, ΔP))`
6. Funding transfer (sub-stepped): if `r_last != 0` and `dt > 0` and `OI_long_0 > 0` and `OI_short_0 > 0`:
   - let `remaining = dt`
   - while `remaining > 0`:
     - let `dt_sub = min(remaining, MAX_FUNDING_DT)`
     - `fund_num_1 = checked_mul_i128(fund_px_0 as i128, r_last as i128)`
     - `fund_num = checked_mul_i128(fund_num_1, dt_sub as i128)`
     - `fund_term = floor_div_signed_conservative(fund_num, 10000)`
     - `K_long = checked_sub_i128(K_long, checked_mul_i128(A_long as i128, fund_term))`
     - `K_short = checked_add_i128(K_short, checked_mul_i128(A_short as i128, fund_term))`
     - `remaining = remaining - dt_sub`
7. update `slot_last = now_slot`
8. update `P_last = oracle_price`
9. update `fund_px_last = oracle_price`

When `r_last > 0`, each executed funding sub-step has `fund_term >= 0`, so `K_long` weakly decreases (longs weakly lose) and `K_short` weakly increases (shorts weakly gain); if `fund_term == 0`, that sub-step has no realized funding effect because of integer flooring. When `r_last < 0`, the numerator of `fund_term` is strictly negative, so `floor_div_signed_conservative` yields `fund_term <= -1`; accordingly `K_long` strictly increases (longs gain) and `K_short` strictly decreases (shorts lose). Positive non-integral quotients round down toward zero, while negative non-integral quotients round down away from zero toward negative infinity.

Normative timing note: funding over the elapsed interval intentionally uses `fund_px_0`, the start-of-call snapshot of `fund_px_last`, i.e. the previous interval's closing funding-price sample. This matches `r_last`, which was injected after the prior instruction's final post-reset state. The current `oracle_price` becomes the next interval's funding-price sample only after the current funding loop completes via step 9.

Conservation: given the maintained snapped equality `OI_long_0 == OI_short_0`, using the same `fund_term` for both sides ensures theoretical zero-sum under the A/K settlement law at the side-aggregate quote-PnL level for every funding sub-step and therefore for the full elapsed interval. Per-account settlement via `wide_signed_mul_div_floor_from_k_pair` floors each individual signed claim downward, so in both signs payer-side realized funding is weakly more negative than theoretical and receiver-side realized funding is weakly less positive than theoretical; aggregate realized claims therefore cannot exceed zero in sum.

The mark-to-market step (5) uses `ΔP` directly and does not require sub-stepping because it is a single price-difference event, not a rate-times-time accumulation. Funding step (6) uses sub-stepping because `dt` may exceed `MAX_FUNDING_DT` and the checked product `fund_px_0 * r_last * dt_sub` must remain within `i128` bounds per the analysis of §1.7.

### 5.5 Funding anti-retroactivity

Each standard-lifecycle instruction of §10 MUST invoke `recompute_r_last_from_final_state(rate)` exactly once and only after any end-of-instruction reset handling specified by that instruction.

For compliant deployments, the rate passed to this helper MUST be computed by the deployment wrapper from the instruction's final post-reset state (or from external wrapper state that reflects the post-reset condition). Intermediate pre-reset state MUST NOT influence the supplied stored rate. The engine enforces only the call ordering and bound check; it does not verify the provenance of the supplied rate.

This ordering ensures that the funding rate applied in the next interval reflects the market's final state, not any transient mid-instruction condition. In particular, if an instruction triggers a side reset that zeros OI, the wrapper-supplied post-reset rate SHOULD reflect the new OI and price state, not the pre-reset conditions.


### 5.6 `enqueue_adl(ctx, liq_side, q_close_q, D)`

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.

Let `opp = opposite(liq_side)`.

This helper MUST perform the following in order:

1. if `q_close_q > 0`, decrement `OI_eff_liq_side` by `q_close_q` using checked subtraction
2. if `D > 0`, set `D_rem = use_insurance_buffer(D)`; else define `D_rem = 0`
3. read `OI = OI_eff_opp`
4. if `OI == 0`:
   - if `D_rem > 0`, `record_uninsured_protocol_loss(D_rem)`
   - if `OI_eff_liq_side == 0`, set both `ctx.pending_reset_liq_side = true` and `ctx.pending_reset_opp = true`
   - return
5. if `OI > 0` and `stored_pos_count_opp == 0`:
   - require `q_close_q <= OI`
   - let `OI_post = OI - q_close_q`
   - if `D_rem > 0`, `record_uninsured_protocol_loss(D_rem)`
   - set `OI_eff_opp = OI_post`
   - if `OI_post == 0`:
     - set `ctx.pending_reset_opp = true`
     - if `OI_eff_liq_side == 0`, set `ctx.pending_reset_liq_side = true`
   - return
6. otherwise (`OI > 0` and `stored_pos_count_opp > 0`):
   - require `q_close_q <= OI`
   - `A_old = A_opp`
   - `OI_post = OI - q_close_q`
7. if `D_rem > 0`:
   - let `adl_scale = checked_mul_u128(A_old, POS_SCALE)`
   - compute `delta_K_abs_result = wide_mul_div_ceil_u128_or_over_i128max(D_rem, adl_scale, OI)`
   - if `delta_K_abs_result == OverI128Magnitude`, `record_uninsured_protocol_loss(D_rem)`
   - else:
     - `delta_K_abs = unwrap(delta_K_abs_result)`
     - `delta_K_exact = -(delta_K_abs as i128)`
     - if `checked_add_i128(K_opp, delta_K_exact)` fails, `record_uninsured_protocol_loss(D_rem)`
     - else `K_opp = K_opp + delta_K_exact`
8. if `OI_post == 0`:
   - set `OI_eff_opp = 0`
   - set `ctx.pending_reset_opp = true`
   - if `OI_eff_liq_side == 0`, set `ctx.pending_reset_liq_side = true`
   - return
9. compute `A_prod_exact = checked_mul_u128(A_old, OI_post)`
10. `A_candidate = floor(A_prod_exact / OI)`
11. `A_trunc_rem = A_prod_exact mod OI`
12. if `A_candidate > 0`:
   - set `A_opp = A_candidate`
   - set `OI_eff_opp = OI_post`
   - if `A_trunc_rem != 0`:
     - `N_opp = stored_pos_count_opp as u128`
     - `global_a_dust_bound = checked_add_u128(N_opp, ceil_div_positive_checked(checked_add_u128(OI, N_opp), A_old))`
     - `inc_phantom_dust_bound_by(opp, global_a_dust_bound)`
   - if `A_opp < MIN_A_SIDE`, set `mode_opp = DrainOnly`
   - return
13. if `A_candidate == 0` while `OI_post > 0`, enter the precision-exhaustion terminal drain:
   - set `OI_eff_opp = 0`
   - set `OI_eff_liq_side = 0`
   - set both pending-reset flags true

Normative intent:

- Real bankruptcy losses MUST first consume the Insurance Fund down to `I_floor`.
- Only the remaining `D_rem` MAY be socialized through `K_opp` or left as junior undercollateralization.
- Quantity socialization MUST never assert-fail due to `A_side` rounding to zero.
- 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.
- 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.
- Real quote deficits MUST NOT be written into `K_opp` when there are no opposing stored positions left to realize that K change.

### 5.7 End-of-instruction reset handling

The engine MUST provide both:

- `schedule_end_of_instruction_resets(ctx)`
- `finalize_end_of_instruction_resets(ctx)`

`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.

It MUST perform the following in order:

1. **Bilateral-empty dust clearance**
   - if `stored_pos_count_long == 0` and `stored_pos_count_short == 0`:
     - `clear_bound_q = checked_add_u128(phantom_dust_bound_long_q, phantom_dust_bound_short_q)`
     - `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)`
     - if `has_residual_clear_work`:
       - require `OI_eff_long == OI_eff_short`
       - if `OI_eff_long <= clear_bound_q` and `OI_eff_short <= clear_bound_q`:
         - set `OI_eff_long = 0`
         - set `OI_eff_short = 0`
         - set both pending-reset flags true
       - else fail conservatively
2. **Unilateral-empty dust clearance (long empty)**
   - else if `stored_pos_count_long == 0` and `stored_pos_count_short > 0`:
     - `has_residual_clear_work = (OI_eff_long > 0) or (OI_eff_short > 0) or (phantom_dust_bound_long_q > 0)`
     - if `has_residual_clear_work`:
       - require `OI_eff_long == OI_eff_short`
       - if `OI_eff_long <= phantom_dust_bound_long_q`:
         - set `OI_eff_long = 0`
         - set `OI_eff_short = 0`
         - set both pending-reset flags true
       - else fail conservatively
3. **Unilateral-empty dust clearance (short empty)**
   - else if `stored_pos_count_short == 0` and `stored_pos_count_long > 0`:
     - `has_residual_clear_work = (OI_eff_long > 0) or (OI_eff_short > 0) or (phantom_dust_bound_short_q > 0)`
     - if `has_residual_clear_work`:
       - require `OI_eff_long == OI_eff_short`
       - if `OI_eff_short <= phantom_dust_bound_short_q`:
         - set `OI_eff_long = 0`
         - set `OI_eff_short = 0`
         - set both pending-reset flags true
       - else fail conservatively
4. **DrainOnly zero-OI reset scheduling**
   - if `mode_long == DrainOnly and OI_eff_long == 0`, set `ctx.pending_reset_long = true`
   - if `mode_short == DrainOnly and OI_eff_short == 0`, set `ctx.pending_reset_short = true`

`finalize_end_of_instruction_resets(ctx)` MUST:

1. if `ctx.pending_reset_long` and `mode_long != ResetPending`, invoke `begin_full_drain_reset(long)`
2. if `ctx.pending_reset_short` and `mode_short != ResetPending`, invoke `begin_full_drain_reset(short)`
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)`
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)`

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.

---

## 6. Warmup and matured-profit release

### 6.1 Parameter

- `T = warmup_period_slots`
- if `T == 0`, warmup is instantaneous

### 6.2 Semantics of `R_i`

`R_i` is the reserved portion of positive `PNL_i` that has not yet matured through warmup.

- `ReleasedPos_i = max(PNL_i, 0) - R_i`
- Only `ReleasedPos_i` contributes to `PNL_matured_pos_tot`, to live haircut, to `Eq_init_net_i`, and to profit conversion
- Reserved fresh positive PnL in `R_i` MAY contribute only to the generating account's maintenance checks
- `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
- Fresh positive PnL MUST enter `R_i` first by the automatic reserve-increase rule in `set_pnl`

### 6.3 Warmup progress

Touched accounts MUST call `advance_profit_warmup(i)` before any logic that depends on current released positive PnL in that touch.

This helper releases previously reserved positive PnL according to the current slope and elapsed slots but never grants newly added reserve any retroactive maturity.

### 6.4 Anti-retroactivity

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.

### 6.5 Release slope preservation

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.

---

## 7. Loss settlement, flat-loss resolution, profit conversion, and fee-debt sweep

### 7.1 `settle_losses_from_principal(i)`

If `PNL_i < 0`, the engine MUST immediately attempt to settle from principal:

1. require `PNL_i != i128::MIN`
2. record `old_R = R_i`
3. `need = (-PNL_i) as u128`
4. `pay = min(need, C_i)`
5. apply:
   - `set_capital(i, C_i - pay)`
   - `set_pnl(i, checked_add_i128(PNL_i, pay as i128))`

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.

### 7.2 Open-position negative remainder

If after §7.1:

- `PNL_i < 0`, and
- `effective_pos_q(i) != 0`

then the account MUST remain liquidatable. It MUST NOT be silently zeroed or routed through flat-account loss absorption.

### 7.3 Flat-account negative remainder

If after §7.1:

- `PNL_i < 0`, and
- `effective_pos_q(i) == 0`

then the engine MUST:

1. call `absorb_protocol_loss((-PNL_i) as u128)`
2. `set_pnl(i, 0)`

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.

### 7.4 Profit conversion

Profit conversion removes matured released profit and converts only its haircutted backed portion into protected principal.

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.

On an eligible touched state, define `x = ReleasedPos_i`. If `x == 0`, do nothing.

Compute `y` using the pre-conversion haircut ratio from §3:

- because `x > 0` implies `PNL_matured_pos_tot > 0`, define `y = mul_div_floor_u128(x, h_num, h_den)`

Apply:

1. `consume_released_pnl(i, x)`
2. `set_capital(i, checked_add_u128(C_i, y))`
3. if `R_i == 0`:
   - `w_slope_i = 0`
   - `w_start_i = current_slot`
4. else leave the existing warmup schedule unchanged

Profit conversion MUST NOT reduce `R_i`. Any still-reserved warmup balance remains reserved and continues to mature only through §6.

### 7.5 Fee-debt sweep

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.

This means:

- 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
- sweep MUST occur in `deposit` only after `settle_losses_from_principal`, and only when `basis_pos_q_i == 0` and `PNL_i >= 0`
- 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
- 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
- sweep MUST NOT be deferred across instructions once capital is both present and no longer senior-encumbered
- 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`

The sweep is:

1. `debt = fee_debt_u128_checked(fee_credits_i)`
2. `pay = min(debt, C_i)`
3. if `pay > 0`:
   - `set_capital(i, C_i - pay)`
   - `fee_credits_i = checked_add_i128(fee_credits_i, pay as i128)`
   - `I = checked_add_u128(I, pay)`

---


## 8. Fees

This revision has no separate `fee_revenue` bucket. All explicit fee collections, realized recurring maintenance fees, and direct fee-credit repayments accrue into `I`.

### 8.1 Trading fees

Trading fees are explicit transfers to insurance and MUST NOT be socialized through `h` or `D`.

Define:

- `fee = mul_div_ceil_u128(trade_notional, trading_fee_bps, 10_000)`

with `0 <= trading_fee_bps <= MAX_TRADING_FEE_BPS`.

Rules:

- if `trading_fee_bps == 0` or `trade_notional == 0`, then `fee = 0`
- if `trading_fee_bps > 0` and `trade_notional > 0`, then `fee >= 1`

The fee MUST be charged using `charge_fee_to_insurance(i, fee)`.

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`.

### 8.2 Account-local recurring maintenance fees

Recurring maintenance fees are enabled in this revision.

The recurring fee is a lazy **per-materialized-account** fee, not a market-wide funding or mark-to-market term. It does not depend on oracle price, side OI, or notional. It accrues only through the elapsed trusted slot interval since the account's `last_fee_slot_i`.

#### 8.2.1 Parameter and due formula

- `maintenance_fee_per_slot` is immutable per market instance and MUST satisfy `0 <= maintenance_fee_per_slot <= MAX_MAINTENANCE_FEE_PER_SLOT`.
- For an account-local realization at `current_slot`, define:
  - `dt_fee = current_slot - last_fee_slot_i`
  - `fee_due = maintenance_fee_per_slot * dt_fee`

`fee_due` MUST be computed with checked arithmetic and MUST satisfy `fee_due <= MAX_PROTOCOL_FEE_ABS`.

#### 8.2.2 Realization helper

The engine MUST define the helper:

- `realize_recurring_maintenance_fee(i)`

It MUST:

1. require `current_slot >= last_fee_slot_i`
2. let `dt_fee = current_slot - last_fee_slot_i`
3. if `maintenance_fee_per_slot == 0` or `dt_fee == 0`:
   - set `last_fee_slot_i = current_slot`
   - return
4. compute `fee_due = checked_mul_u128(maintenance_fee_per_slot, dt_fee as u128)`
5. require `fee_due <= MAX_PROTOCOL_FEE_ABS`
6. charge the fee using `charge_fee_to_insurance(i, fee_due)`
7. set `last_fee_slot_i = current_slot`

Normative consequences:

- recurring maintenance-fee realization MUST NOT mutate `PNL_i`, `R_i`, `PNL_pos_tot`, `PNL_matured_pos_tot`, any `A_side`, any `K_side`, any `OI_eff_*`, or `D`
- if capital is insufficient, the collectible shortfall becomes negative `fee_credits_i` up to representable headroom; any excess beyond collectible headroom is dropped by `charge_fee_to_insurance`
- realizing recurring maintenance fees does not itself change `Residual`, because transfers from `C_i` to `I` leave `C_tot + I` unchanged and pure fee-debt creation does not enter `Residual`

#### 8.2.3 Call sites and exclusions

The following call-site rules are normative:

1. `touch_account_full` MUST call `realize_recurring_maintenance_fee(i)` after:
   - `advance_profit_warmup(i)`
   - `settle_side_effects(i)`
   - `settle_losses_from_principal(i)`
   - any allowed flat-account loss absorption under §7.3  
   and before:
   - flat-only automatic profit conversion under §7.4
   - fee-debt sweep under §7.5

2. The per-candidate local exact-touch helper inside `keeper_crank` MUST inherit the same ordering because it is required to be economically equivalent to `touch_account_full` on the already-accrued state.

3. `reclaim_empty_account(i, now_slot)` MUST realize recurring maintenance fees on the already-flat state after anchoring `current_slot = now_slot` and before the final reclaim-eligibility check and debt forgiveness.

4. `deposit`, `deposit_fee_credits`, and `top_up_insurance_fund` MUST NOT call `realize_recurring_maintenance_fee`. They are pure capital-only instructions in this revision.

Because this model is lazy, wall-clock passage alone does not immediately mutate `I` or `fee_credits_i`; those mutations happen only when one of the explicit realization call sites above executes.

### 8.3 Liquidation fees

The protocol MUST define:

- `liquidation_fee_bps` with `0 <= liquidation_fee_bps <= MAX_LIQUIDATION_FEE_BPS`
- `liquidation_fee_cap` with `0 <= liquidation_fee_cap <= MAX_PROTOCOL_FEE_ABS`
- `min_liquidation_abs` with `0 <= min_liquidation_abs <= liquidation_fee_cap`

For a liquidation that closes `q_close_q` at `oracle_price`, define:

- if `q_close_q == 0`, then `liq_fee = 0`
- else:
  - `closed_notional = mul_div_floor_u128(q_close_q, oracle_price, POS_SCALE)`
  - `liq_fee_raw = mul_div_ceil_u128(closed_notional, liquidation_fee_bps, 10_000)`
  - `liq_fee = min(max(liq_fee_raw, min_liquidation_abs), liquidation_fee_cap)`

The short-circuit is on `q_close_q`, not `closed_notional`. Therefore the minimum fee floor applies even when `closed_notional` floors to zero.

### 8.4 Fee debt as margin liability

`FeeDebt_i = fee_debt_u128_checked(fee_credits_i)`:

- MUST reduce `Eq_maint_raw_i`, `Eq_net_i`, `Eq_init_raw_i`, and therefore also the derived `Eq_init_net_i`
- MUST be swept whenever principal becomes available and is no longer senior-encumbered by already-realized trading losses on the same local state
- MUST NOT directly change `Residual`, `PNL_pos_tot`, or `PNL_matured_pos_tot`
- includes unpaid collectible explicit trading, liquidation, and recurring maintenance fees
- any explicit fee amount beyond collectible capacity is dropped rather than written into `PNL_i` or `D`

---

## 9. Margin checks and liquidation

### 9.1 Margin requirements

After `touch_account_full(i, oracle_price, now_slot)`, define:

- `Notional_i = mul_div_floor_u128(abs(effective_pos_q(i)), oracle_price, POS_SCALE)`
- if `effective_pos_q(i) == 0`:
  - `MM_req_i = 0`
  - `IM_req_i = 0`
- else:
  - `MM_req_i = max(mul_div_floor_u128(Notional_i, maintenance_bps, 10_000), MIN_NONZERO_MM_REQ)`
  - `IM_req_i = max(mul_div_floor_u128(Notional_i, initial_bps, 10_000), MIN_NONZERO_IM_REQ)`

Healthy conditions:

- maintenance healthy if `Eq_net_i > MM_req_i as i128`
- initial-margin healthy if exact `Eq_init_raw_i >= (IM_req_i as wide_signed)` in the widened signed domain of §3.4

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.

### 9.2 Risk-increasing and strict risk-reducing trades

A trade for account `i` is **risk-increasing** when either:

1. `abs(new_eff_pos_q_i) > abs(old_eff_pos_q_i)`, or
2. the position sign flips across zero, or
3. `old_eff_pos_q_i == 0` and `new_eff_pos_q_i != 0`

A trade is **strictly risk-reducing** when:

- `old_eff_pos_q_i != 0`
- `new_eff_pos_q_i != 0`
- `sign(new_eff_pos_q_i) == sign(old_eff_pos_q_i)`
- `abs(new_eff_pos_q_i) < abs(old_eff_pos_q_i)`

### 9.3 Liquidation eligibility

An account is liquidatable when after a full `touch_account_full`:

- `effective_pos_q(i) != 0`, and
- `Eq_net_i <= MM_req_i as i128`

### 9.4 Partial liquidation

A liquidation MAY be partial only if it closes a strictly positive quantity smaller than the full remaining effective position:

- `0 < q_close_q < abs(old_eff_pos_q_i)`

A successful partial liquidation MUST:

1. use the current touched state
2. let `old_eff_pos_q_i = effective_pos_q(i)` and require `old_eff_pos_q_i != 0`
3. determine `liq_side = side(old_eff_pos_q_i)`
4. define `new_eff_abs_q = checked_sub_u128(abs(old_eff_pos_q_i), q_close_q)`
5. require `new_eff_abs_q > 0`
6. define `new_eff_pos_q_i = sign(old_eff_pos_q_i) * (new_eff_abs_q as i128)`
7. close `q_close_q` synthetically at `oracle_price` with zero execution-price slippage
8. apply the resulting position using `attach_effective_position(i, new_eff_pos_q_i)`
9. settle realized losses from principal via §7.1
10. compute `liq_fee` per §8.3 on the quantity actually closed
11. charge that fee using `charge_fee_to_insurance(i, liq_fee)`
12. invoke `enqueue_adl(ctx, liq_side, q_close_q, 0)` to decrease global OI and socialize quantity reduction
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
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

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`.

### 9.5 Full-close / bankruptcy liquidation

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.

Full-close liquidation is a local subroutine on the current touched state. It MUST NOT call `touch_account_full` again.

It MUST:

1. use the current touched state
2. let `old_eff_pos_q_i = effective_pos_q(i)` and require `old_eff_pos_q_i != 0`
3. set `q_close_q = abs(old_eff_pos_q_i)`; full-close liquidation MUST strictly close the full remaining effective position
4. let `liq_side = side(old_eff_pos_q_i)`
5. because the close is synthetic, it MUST execute exactly at `oracle_price` with zero execution-price slippage
6. `attach_effective_position(i, 0)`
7. `OI_eff_liq_side` MUST NOT be decremented anywhere except through `enqueue_adl`
8. `settle_losses_from_principal(i)`
9. compute `liq_fee` per §8.3 and charge it via `charge_fee_to_insurance(i, liq_fee)`
10. determine the uncovered bankruptcy deficit `D`:
    - if `PNL_i < 0`, let `D = (-PNL_i) as u128`
    - else `D = 0`
11. if `q_close_q > 0` or `D > 0`, invoke `enqueue_adl(ctx, liq_side, q_close_q, D)`
12. if `D > 0`, `set_pnl(i, 0)`

### 9.6 Side-mode gating

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()`.

Any operation that would increase net side OI on a side whose mode is `DrainOnly` or `ResetPending` MUST be rejected.

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.

---


## 10. External operations

### 10.0 Standard instruction lifecycle

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:

1. validate trusted monotonic slot inputs and the validated oracle input required by that endpoint
2. initialize a fresh instruction context `ctx`
3. perform the endpoint's exact current-state inner execution
4. call `schedule_end_of_instruction_resets(ctx)` exactly once
5. call `finalize_end_of_instruction_resets(ctx)` exactly once
6. after final reset handling, invoke `recompute_r_last_from_final_state(wrapper_computed_rate)` exactly once
7. if the instruction can mutate live side exposure, assert `OI_eff_long == OI_eff_short` at the end

Here and below, `wrapper_computed_rate` denotes the deployment-wrapper output injected through §4.12's helper. For compliant deployments it is computed from the instruction's final post-reset state, but the core engine does not derive or verify that provenance internally.

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.

### 10.1 `touch_account_full(i, oracle_price, now_slot)`

Canonical settle routine for an existing materialized account. It MUST perform, in order:

1. require account `i` is materialized
2. require trusted `now_slot >= current_slot`
3. require trusted `now_slot >= slot_last`
4. require validated `0 < oracle_price <= MAX_ORACLE_PRICE`
5. set `current_slot = now_slot`
6. call `accrue_market_to(now_slot, oracle_price)`
7. call `advance_profit_warmup(i)`
8. call `settle_side_effects(i)`
9. call `settle_losses_from_principal(i)`
10. if `effective_pos_q(i) == 0` and `PNL_i < 0`, resolve uncovered flat loss via §7.3
11. realize recurring maintenance fees via §8.2
12. if `basis_pos_q_i == 0`, convert matured released profits via §7.4
13. sweep fee debt per §7.5

`touch_account_full` MUST NOT itself begin a side reset.

### 10.2 `settle_account(i, oracle_price, now_slot)`

Standalone settle wrapper for an existing account.

Procedure:

1. initialize fresh instruction context `ctx`
2. `touch_account_full(i, oracle_price, now_slot)`
3. `schedule_end_of_instruction_resets(ctx)`
4. `finalize_end_of_instruction_resets(ctx)`
5. after final reset handling, invoke `recompute_r_last_from_final_state(wrapper_computed_rate)` exactly once

This wrapper MUST NOT materialize a missing account.

### 10.3 `deposit(i, amount, now_slot)`

`deposit` is a pure capital-transfer instruction. It MUST NOT call `accrue_market_to`, MUST NOT mutate side state, MUST NOT auto-touch unrelated accounts, and MUST NOT realize recurring maintenance fees.

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.

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 current-state touch.

Procedure:

1. require trusted `now_slot >= current_slot`
2. if account `i` is missing:
   - require `amount >= MIN_INITIAL_DEPOSIT`
   - `materialize_account(i, now_slot)`
3. set `current_slot = now_slot`
4. require `checked_add_u128(V, amount) <= MAX_VAULT_TVL`
5. set `V = V + amount`
6. `set_capital(i, checked_add_u128(C_i, amount))`
7. `settle_losses_from_principal(i)`
8. MUST NOT invoke §7.3 or otherwise decrement `I`
9. if `basis_pos_q_i == 0` and `PNL_i >= 0`, sweep fee debt via §7.5

Because `deposit` cannot mutate OI, stored positions, stale-account counts, phantom-dust bounds, side modes, or recurring-fee realization state, it MAY omit §§5.7 end-of-instruction reset handling.

### 10.3.1 `deposit_fee_credits(i, amount, now_slot)`

`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. It MUST NOT realize recurring maintenance fees.

Procedure:

1. require account `i` is materialized
2. require trusted `now_slot >= current_slot`
3. set `current_slot = now_slot`
4. let `debt = fee_debt_u128_checked(fee_credits_i)`
5. let `pay = min(amount, debt)`
6. if `pay == 0`, return
7. require `checked_add_u128(V, pay) <= MAX_VAULT_TVL`
8. set `V = V + pay`
9. set `I = checked_add_u128(I, pay)`
10. set `fee_credits_i = checked_add_i128(fee_credits_i, pay as i128)`
11. require `fee_credits_i <= 0`

Normative consequences:

- the externally accounted repayment amount is exactly `pay`, not the user-specified `amount`
- any over-request above the outstanding debt is silently capped and MUST NOT create positive `fee_credits_i`
- the instruction MUST NOT call `accrue_market_to`
- the instruction MUST NOT mutate side state, `C_i`, `PNL_i`, `R_i`, or any aggregate other than `V` and `I`

### 10.3.2 `top_up_insurance_fund(amount, now_slot)`

`top_up_insurance_fund` is a direct external addition to the Insurance Fund and the vault. It does not credit any account principal and MUST NOT realize recurring maintenance fees.

Procedure:

1. require trusted `now_slot >= current_slot`
2. set `current_slot = now_slot`
3. require `checked_add_u128(V, amount) <= MAX_VAULT_TVL`
4. set `V = V + amount`
5. set `I = checked_add_u128(I, amount)`

This instruction MUST NOT call `accrue_market_to`, MUST NOT mutate any account-local state, and MUST NOT mutate side state.

### 10.4 `withdraw(i, amount, oracle_price, now_slot)`

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.

Procedure:

1. require account `i` is materialized
2. initialize fresh instruction context `ctx`
3. `touch_account_full(i, oracle_price, now_slot)`
4. require `amount <= C_i`
5. require the post-withdraw capital `C_i - amount` is either `0` or `>= MIN_INITIAL_DEPOSIT`
6. if `effective_pos_q(i) != 0`, require post-withdraw initial-margin health on the hypothetical post-withdraw state where:
   - `C_i' = C_i - amount`
   - `V' = V - amount`
   - 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
   - all other touched-state quantities are unchanged
   - equivalently, because both `V` and `C_tot` decrease by the same `amount`, `Residual` and `h` are unchanged by the simulation
7. apply:
   - `set_capital(i, C_i - amount)`
   - `V = V - amount`
8. `schedule_end_of_instruction_resets(ctx)`
9. `finalize_end_of_instruction_resets(ctx)`
10. after final reset handling, invoke `recompute_r_last_from_final_state(wrapper_computed_rate)` exactly once

### 10.4.1 `convert_released_pnl(i, x_req, oracle_price, now_slot)`

Explicit voluntary conversion of matured released positive PnL for an account that still has an open position.

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.

Procedure:

1. require account `i` is materialized
2. initialize fresh instruction context `ctx`
3. `touch_account_full(i, oracle_price, now_slot)`
4. if `basis_pos_q_i == 0`:
   - the ordinary touch flow has already auto-converted any released profit eligible on the now-flat state
   - `schedule_end_of_instruction_resets(ctx)`
   - `finalize_end_of_instruction_resets(ctx)`
   - after final reset handling, invoke `recompute_r_last_from_final_state(wrapper_computed_rate)` exactly once
   - return
5. require `0 < x_req <= ReleasedPos_i`
6. compute `y` using the same pre-conversion haircut rule as §7.4:
   - because `x_req > 0` implies `PNL_matured_pos_tot > 0`, define `y = mul_div_floor_u128(x_req, h_num, h_den)`
7. `consume_released_pnl(i, x_req)`
8. `set_capital(i, checked_add_u128(C_i, y))`
9. sweep fee debt per §7.5
10. require the current post-step-9 state is maintenance healthy if `effective_pos_q(i) != 0`
11. `schedule_end_of_instruction_resets(ctx)`
12. `finalize_end_of_instruction_resets(ctx)`
13. after final reset handling, invoke `recompute_r_last_from_final_state(wrapper_computed_rate)` exactly once

A failed post-conversion maintenance check MUST revert atomically. This instruction MUST NOT materialize a missing account.

### 10.5 `execute_trade(a, b, oracle_price, now_slot, size_q, exec_price)`

`size_q > 0` means account `a` buys base from account `b`.

Procedure:

1. require both accounts are materialized
2. require `a != b`
3. require trusted `now_slot >= current_slot`
4. require trusted `now_slot >= slot_last`
5. require validated `0 < oracle_price <= MAX_ORACLE_PRICE`
6. require validated `0 < exec_price <= MAX_ORACLE_PRICE`
7. require `0 < size_q <= MAX_TRADE_SIZE_Q`
8. compute `trade_notional = mul_div_floor_u128(size_q, exec_price, POS_SCALE)`
9. require `trade_notional <= MAX_ACCOUNT_NOTIONAL`
10. initialize fresh instruction context `ctx`
11. `touch_account_full(a, oracle_price, now_slot)`
12. `touch_account_full(b, oracle_price, now_slot)`
13. let `old_eff_pos_q_a = effective_pos_q(a)` and `old_eff_pos_q_b = effective_pos_q(b)`
14. let `MM_req_pre_a`, `MM_req_pre_b` be maintenance requirement on the post-touch pre-trade state
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
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
17. invoke `maybe_finalize_ready_reset_sides_before_oi_increase()`
18. define:
   - `new_eff_pos_q_a = checked_add_i128(old_eff_pos_q_a, size_q as i128)`
   - `new_eff_pos_q_b = checked_sub_i128(old_eff_pos_q_b, size_q as i128)`
19. require `abs(new_eff_pos_q_a) <= MAX_POSITION_ABS_Q` and `abs(new_eff_pos_q_b) <= MAX_POSITION_ABS_Q`
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`
21. apply immediate execution-slippage alignment PnL before fees:
   - `trade_pnl_num = checked_mul_i128(size_q as i128, (oracle_price as i128) - (exec_price as i128))`
   - `trade_pnl_a = floor_div_signed_conservative(trade_pnl_num, POS_SCALE)`
   - `trade_pnl_b = -trade_pnl_a`
   - record `old_R_a = R_a` and `old_R_b = R_b`
   - `set_pnl(a, checked_add_i128(PNL_a, trade_pnl_a))`
   - `set_pnl(b, checked_add_i128(PNL_b, trade_pnl_b))`
   - if `R_a > old_R_a`, invoke `restart_warmup_after_reserve_increase(a)`
   - if `R_b > old_R_b`, invoke `restart_warmup_after_reserve_increase(b)`
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)`
23. update side OI atomically by writing the exact candidate after-values from step 20:
   - set `OI_eff_long = OI_long_after_trade`
   - set `OI_eff_short = OI_short_after_trade`
24. settle post-trade losses from principal for both accounts via §7.1
25. if `new_eff_pos_q_a == 0`, require `PNL_a >= 0` after step 24
26. if `new_eff_pos_q_b == 0`, require `PNL_b >= 0` after step 24
27. compute `fee = mul_div_ceil_u128(trade_notional, trading_fee_bps, 10_000)`
28. charge explicit trading fees using `charge_fee_to_insurance(a, fee)` and `charge_fee_to_insurance(b, fee)`
29. enforce post-trade margin for each account using the current post-step-28 state:
   - if the resulting effective position is zero:
     - the flat-account guard from steps 25–26 still applies, and
     - require exact `Eq_maint_raw_i >= 0` in the widened signed domain of §3.4 on the current post-step-28 state
   - 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
   - else if the account is maintenance healthy using `Eq_net_i`, allow
   - 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:
     - 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
     - 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)`
   - else reject

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.

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.
30. `schedule_end_of_instruction_resets(ctx)`
31. `finalize_end_of_instruction_resets(ctx)`
32. after final reset handling, invoke `recompute_r_last_from_final_state(wrapper_computed_rate)` exactly once
33. assert `OI_eff_long == OI_eff_short`

### 10.6 `liquidate(i, oracle_price, now_slot, policy)`

`policy` MUST be one of:

- `FullClose`
- `ExactPartial(q_close_q)` where `0 < q_close_q < abs(old_eff_pos_q_i)` on the already-touched current state

No other liquidation-policy encoding is compliant in this revision.

Procedure:

1. require account `i` is materialized
2. initialize fresh instruction context `ctx`
3. `touch_account_full(i, oracle_price, now_slot)`
4. require liquidation eligibility from §9.3
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
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
7. if any remaining nonzero position exists after liquidation, it MUST already have been reattached via `attach_effective_position`
8. `schedule_end_of_instruction_resets(ctx)`
9. `finalize_end_of_instruction_resets(ctx)`
10. after final reset handling, invoke `recompute_r_last_from_final_state(wrapper_computed_rate)` exactly once
11. assert `OI_eff_long == OI_eff_short`

### 10.7 `reclaim_empty_account(i, now_slot)`

Permissionless empty- or flat-dust-account recycling wrapper.

Procedure:

1. require account `i` is materialized
2. require trusted `now_slot >= current_slot`
3. require pre-realization flat-clean preconditions of §2.6:
   - `PNL_i == 0`
   - `R_i == 0`
   - `basis_pos_q_i == 0`
   - `fee_credits_i <= 0`
4. set `current_slot = now_slot`
5. realize recurring maintenance fees via §8.2
6. require the final reclaim-eligibility conditions of §2.6 hold
7. execute the reclamation effects of §2.6

`reclaim_empty_account` MUST NOT call `accrue_market_to`, MUST NOT mutate side state, and MUST NOT materialize any account.

### 10.8 `keeper_crank(now_slot, oracle_price, ordered_candidates[], max_revalidations)`

`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.

Procedure:

1. initialize fresh instruction context `ctx`
2. require trusted `now_slot >= current_slot`
3. require trusted `now_slot >= slot_last`
4. require validated `0 < oracle_price <= MAX_ORACLE_PRICE`
5. call `accrue_market_to(now_slot, oracle_price)` exactly once at the start
6. set `current_slot = now_slot`
7. let `attempts = 0`
8. for each candidate in keeper-supplied order:
   - if `attempts == max_revalidations`, break
   - if `ctx.pending_reset_long` or `ctx.pending_reset_short`, break
   - if candidate account is missing, continue
   - increment `attempts` by exactly `1`
   - 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
   - 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
   - if liquidation or the exact touch schedules a pending reset, break
9. `schedule_end_of_instruction_resets(ctx)`
10. `finalize_end_of_instruction_resets(ctx)`
11. after final reset handling, invoke `recompute_r_last_from_final_state(wrapper_computed_rate)` exactly once
12. assert `OI_eff_long == OI_eff_short`

Rules:

- missing accounts MUST NOT be materialized
- `max_revalidations` measures normal exact current-state revalidation attempts on materialized accounts; missing-account skips do not count
- the engine MUST process candidates in keeper-supplied order except for the mandatory stop-on-pending-reset rule
- the engine MUST NOT impose any on-chain liquidation-first ordering across keeper-supplied candidates
- a candidate that proves safe or needs only cleanup after exact current-state touch still counts against `max_revalidations`
- 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

---


## 11. Permissionless off-chain shortlist keeper mode

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.

### 11.1 Core rules

1. The engine does **not** require any on-chain phase-1 search, barrier classifier, or no-false-negative scan proof.
2. `ordered_candidates[]` in §10.8 is keeper-supplied and untrusted. It MAY be stale, incomplete, duplicated, adversarially ordered, or produced by approximate heuristics.
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.
4. The protocol MUST NOT require that a keeper discover *all* currently liquidatable accounts before it may process a useful subset.
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.

### 11.2 Exact current-state revalidation attempts

Let `max_revalidations` be the keeper's per-instruction budget measured in **exact current-state revalidation attempts**.

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.

It counts against `max_revalidations` once that materialized-account revalidation reaches a normal per-candidate outcome, including when the account:

- is liquidatable and is liquidated
- is touched and only cleanup happens
- is touched and proves safe
- is touched, remains liquidatable, but no valid current-state liquidation action is applied for that attempt

A pure missing-account skip does **not** count.

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, including recurring maintenance-fee realization, and it MUST NOT call `accrue_market_to` again for that account.

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.

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.

### 11.3 On-chain ordering constraints

The protocol MUST NOT impose a mandatory on-chain liquidation-first, cleanup-first, or priority-queue ordering across keeper-supplied candidates.

Inside `keeper_crank`, the only mandatory on-chain ordering constraints are:

1. the single initial `accrue_market_to(now_slot, oracle_price)` and trusted `current_slot = now_slot` anchor happen before per-candidate exact revalidation
2. materialized candidates are processed in keeper-supplied order
3. once either pending-reset flag becomes true, the instruction stops further candidate processing and proceeds directly to end-of-instruction reset handling

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.

### 11.4 Honest-keeper guidance (non-normative)

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, recurring fee realization, and end-of-instruction reset stop conditions.

For off-chain ordering, an honest keeper SHOULD usually prioritize:

- reset-progress or dust-progress candidates that can unblock finalization on already-constrained sides
- 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
- otherwise, higher expected uncovered deficit after insurance, larger maintenance shortfall, larger notional, and `DrainOnly`-side candidates ahead of otherwise similar `Normal`-side candidates

These `SHOULD` recommendations are operational guidance only, not consensus rules.

## 12. Required test properties (minimum)

An implementation MUST include tests that cover at least:

1. **Conservation:** `V >= C_tot + I` always, and `Σ PNL_eff_matured_i <= Residual`.
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`.
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.
4. **Warmup anti-retroactivity:** newly generated profit cannot inherit old dormant maturity headroom.
5. **Pure release slope preservation:** repeated touches do not create exponential-decay maturity.
6. **Same-epoch local settlement:** settlement of one account does not depend on any canonical-order prefix.
7. **Non-compounding quantity basis:** repeated same-epoch touches without explicit position mutation do not compound quantity-flooring loss.
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.
9. **Dust-clear scheduling:** dust clearance and reset initiation happen only at end of top-level instructions, never mid-instruction.
10. **Epoch-safe reset:** accounts cannot be attached to a new epoch before `begin_full_drain_reset` runs.
11. **Precision-exhaustion terminal drain:** if `A_candidate == 0` with `OI_post > 0`, the engine force-drains both sides instead of reverting.
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`.
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.
14. **Unit consistency:** margin, notional, and fees use quote-token atomic units consistently.
15. **`set_pnl` aggregate safety:** positive-PnL updates do not overflow `PNL_pos_tot` or `PNL_matured_pos_tot`.
16. **`PNL_i == i128::MIN` forbidden:** every negation path is safe.
17. **Explicit-fee shortfalls:** unpaid collectible trading, liquidation, and recurring maintenance fees become negative `fee_credits_i`, not `PNL_i` and not `D`; any explicit fee amount beyond collectible headroom is dropped rather than socialized.
18. **Recurring maintenance-fee determinism:** `realize_recurring_maintenance_fee(i)` charges exactly `maintenance_fee_per_slot * (current_slot - last_fee_slot_i)` when both are nonzero, otherwise charges zero, and always ends with `last_fee_slot_i == current_slot`.
19. **Recurring-fee touch ordering:** `touch_account_full` realizes recurring maintenance fees only after `settle_losses_from_principal` and any allowed §7.3 flat-loss absorption, and before flat-only automatic conversion and fee-debt sweep.
20. **Funding rate injection ordering:** every standard-lifecycle endpoint invokes `recompute_r_last_from_final_state` exactly once after final reset handling. For compliant deployments, the supplied rate is sourced from the final post-reset state by the deployment wrapper, and the stored value satisfies `|r_last| <= MAX_ABS_FUNDING_BPS_PER_SLOT`.
21. **Funding transfer conservation under lazy settlement:** when `r_last != 0` and both sides have OI, each funding sub-step in `accrue_market_to` applies the same `fund_term` to both sides' `K` updates, so the side-aggregate funding PnL implied by the A/K law is zero-sum per sub-step and over the full elapsed interval, given the maintained snapped equality `OI_long_0 == OI_short_0`. After any later account settlements for those sub-steps, aggregate realized funding PnL across all accounts is `≤ 0` because payer-side claims are floored downward and receiver-side claims are also floored downward from their own sign.
22. **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.
23. **Reset finalization:** after reconciling stale accounts, the side can leave `ResetPending` and accept fresh OI again.
24. **Deposit loss seniority:** in `deposit`, realized losses are settled from newly deposited principal before any outstanding fee debt is swept.
25. **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.
26. **Dust liquidation minimum fee:** if `q_close_q > 0` but `closed_notional` floors to zero, `liq_fee` still honors `min_liquidation_abs`.
27. **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.
28. **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`.
29. **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.
30. **Organic close bankruptcy guard:** a flat trade cannot bypass ADL by leaving negative `PNL_i` behind.
31. **Full-close liquidation requirement:** full-close liquidation always closes the full remaining effective position.
32. **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.
33. **Missing-account safety:** `settle_account`, `withdraw`, `execute_trade`, `liquidate`, and `keeper_crank` do not materialize missing accounts.
34. **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.
35. **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.
36. **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.
37. **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, including recurring maintenance-fee realization.
38. **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.
39. **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.
40. **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.
41. **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.
42. **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.
43. **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.
44. **Post-reset funding recomputation in keeper:** `keeper_crank` invokes `recompute_r_last_from_final_state` exactly once after final reset handling with the wrapper-supplied rate. For compliant deployments, that supplied rate is sourced from the keeper instruction's final post-reset state, and the stored value satisfies the `MAX_ABS_FUNDING_BPS_PER_SLOT` bound.
45. **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.
46. **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.
47. **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 fee stamp.
48. **Funding rate recomputation determinism and provenance boundary:** `recompute_r_last_from_final_state(rate)` stores exactly `rate` when `|rate| <= MAX_ABS_FUNDING_BPS_PER_SLOT` and rejects otherwise. It does not derive or verify the provenance of `rate`; sourcing that input from final post-reset state is a deployment-wrapper compliance obligation.
49. **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.
50. **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.
51. **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`.
52. **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.
53. **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.
54. **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.
55. **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.
56. **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.
57. **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.
58. **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.
59. **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.
60. **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.
61. **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.
62. **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.
63. **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.
64. **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.
65. **Pure-capital recurring-fee exclusion:** `deposit`, `deposit_fee_credits`, and `top_up_insurance_fund` do not realize recurring maintenance fees and do not mutate `last_fee_slot_i`.
66. **Bilateral trade approval atomicity:** if one trade counterparty qualifies under step 29 but the other fails every permitted branch, the entire trade reverts atomically.
67. **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.
68. **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.
69. **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.
70. **Configuration immutability:** no runtime instruction in this revision can change `T`, `maintenance_fee_per_slot`, fee parameters, margin parameters, liquidation parameters, `I_floor`, or the live-balance floors after initialization.
71. **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.
72. **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.
73. **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.
74. **Funding sub-stepping:** when the accrual interval exceeds `MAX_FUNDING_DT`, `accrue_market_to` splits funding into consecutive sub-steps each `≤ MAX_FUNDING_DT` slots, all using the same start-of-call funding-price sample `fund_px_0 = fund_px_last`, and the total `K` delta equals the sum of sub-step deltas.
75. **Funding sign and floor-direction correctness:** when `r_last > 0`, each executed funding sub-step has `fund_term >= 0`, so long-side `K` weakly decreases under the update `-A_long * fund_term` while short-side `K` weakly increases under the update `+A_short * fund_term`; if `fund_term == 0`, that sub-step transfers nothing. When `r_last < 0`, each executed funding sub-step has `fund_term <= -1`, so long-side `K` strictly increases under `-A_long * fund_term` while short-side `K` strictly decreases under `+A_short * fund_term`. `fund_term` MUST be computed with `floor_div_signed_conservative`, and later account settlement via `wide_signed_mul_div_floor_from_k_pair` MUST also floor signed values; in both signs this keeps payer-side realized funding weakly more negative than theoretical and receiver-side realized funding weakly less positive than theoretical. A positive rate never transfers value from shorts to longs, and a negative rate never transfers value from longs to shorts.
76. **Funding skip on zero OI:** `accrue_market_to` applies no funding `K` delta when either side's snapped OI is zero, even when `r_last != 0`. This prevents writing `K` state into a side that has no stored positions to realize it.
77. **Funding rate bound enforcement:** `recompute_r_last_from_final_state` rejects any input with magnitude exceeding `MAX_ABS_FUNDING_BPS_PER_SLOT`.
78. **Funding price-basis timing:** `accrue_market_to` snapshots `fund_px_0 = fund_px_last` at call start, uses that same `fund_px_0` for every funding sub-step in the elapsed interval, and updates `fund_px_last = oracle_price` only after the funding loop so the current oracle price becomes the next interval's funding-price sample.
79. **Reclaim-time recurring-fee realization:** `reclaim_empty_account(i, now_slot)` anchors `current_slot = now_slot`, realizes recurring maintenance fees on the already-flat state, then checks final reclaim eligibility and only then forgives remaining negative `fee_credits_i`.
80. **Fee-headroom saturation liveness:** if `fee_credits_i` is already near its negative representable limit, `charge_fee_to_insurance` caps the collectible shortfall at remaining headroom and drops any excess explicit fee rather than overflowing or reverting.

## 13. Compatibility and upgrade notes

1. LP accounts and user accounts may share the same protected-principal and junior-profit mechanics.
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.
3. This spec deliberately rejects hidden residual matching. Bankruptcy socialization occurs only through explicit Insurance Fund usage, explicit A/K state, or junior undercollateralization.
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.
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.
6. This revision enables live funding through the A/K mechanism. The v11.31 funding-disabled profile is replaced by a parameterized `recompute_r_last_from_final_state` that accepts an externally computed rate. Deployments upgrading from v11.31 start with `r_last = 0` and begin accruing funding as soon as the wrapper passes a nonzero rate. Markets that should remain unfunded MUST always pass `0`. If a deployment wrapper implements premium-based funding with a wrapper-level parameter such as `funding_k_bps` (equivalently `k_bps` in §4.12's notation), setting that wrapper parameter to `0` is a deployment-level kill switch; equivalently, any wrapper may simply pass `0` directly.
7. This revision also enables recurring account-local maintenance fees. Deployments upgrading from v12.0.2 MUST populate `maintenance_fee_per_slot`, preserve or initialize `last_fee_slot_i` for every materialized account, and adopt the new `reclaim_empty_account(i, now_slot)` signature. A deployment that wants no recurring maintenance fee MAY set `maintenance_fee_per_slot = 0`, but the realization path and its ordering remain part of the normative engine surface.
8. Any future revision that wishes to allow runtime parameter mutation MUST define an explicit safe update procedure that preserves warmup, recurring-fee, margin, liquidation, and dust-floor invariants across the transition.