Skip to content

fix(kani): Fix kani.rs compilation — 3 bitrotted references (PERC-238) - #5

Merged
dcccrypto merged 2 commits into
masterfrom
fix/PERC-238-kani-compilation
Feb 27, 2026
Merged

fix(kani): Fix kani.rs compilation — 3 bitrotted references (PERC-238)#5
dcccrypto merged 2 commits into
masterfrom
fix/PERC-238-kani-compilation

Conversation

@dcccrypto

@dcccrypto dcccrypto commented Feb 27, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes 3 compilation errors that made all 152 kani harnesses non-functional:

Error 1: No field funding_rate_bps_per_slot on RiskEngine

  • Line 7661: engine.funding_rate_bps_per_slot = funding_rate;
  • Fix: Renamed to funding_rate_bps_per_slot_last to match current struct

Error 2: Method takes 5 args but 3 supplied

  • Line 7666: engine.keeper_crank(now_slot, 1_000_000, false)
  • Fix: Added missing caller_idx (user) and funding_rate params

Error 3: No method set_mode_resolved

  • Line 7698: engine.set_mode_resolved(1_000_000)
  • Fix: Replaced with proof_stale_sweep_blocks_risk_increasing_trade. The 'Resolved mode' was replaced by force-realize mode + sweep staleness. New proof tests the actual mechanism.

Impact

All 152 kani harnesses should now compile and be runnable.

Task: PERC-238

Summary by CodeRabbit

Release Notes

  • Breaking Changes

    • keeper_crank function now requires an additional funding_rate parameter when called.
  • Bug Fixes

    • Updated funding rate tracking behavior in risk calculations to accurately reflect last observed funding rates.

- Fix 1: funding_rate_bps_per_slot → funding_rate_bps_per_slot_last (field renamed)
- Fix 2: keeper_crank(3 args) → keeper_crank(5 args) — add caller_idx + funding_rate
- Fix 3: Replace set_mode_resolved() (removed method) with stale sweep proof
  The 'Resolved mode' concept was replaced by force-realize mode + sweep staleness.
  New proof_stale_sweep_blocks_risk_increasing_trade verifies the actual mechanism.
@coderabbitai

coderabbitai Bot commented Feb 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A test file is updated with a field rename from funding_rate_bps_per_slot to funding_rate_bps_per_slot_last, the keeper_crank function signature is extended to accept a funding_rate parameter, and a test is renamed to reflect new stale sweep validation logic with corresponding test setup adjustments.

Changes

Cohort / File(s) Summary
Test Suite Updates
tests/kani.rs
Field renamed to funding_rate_bps_per_slot_last for tracking last observed funding rate. keeper_crank function signature updated to accept new funding_rate: i64 parameter positioned between amount and caller_settle_ok. Test proof_resolved_mode_blocks_trading renamed to proof_stale_sweep_blocks_risk_increasing_trade with updated setup to initialize last_full_sweep_start_slot and max_crank_staleness_slots for stale sweep scenario validation. Call sites updated to pass new parameter.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A funding rate hops into view,
Last observed, tracked fresh and true,
The crank swings wide with rates in hand,
Tests now sweep through stale-ish land,
Logic flows where staleness grows! 📊

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing compilation errors in kani.rs by addressing three bitrotted references, with a task reference for context.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/PERC-238-kani-compilation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/kani.rs (1)

7666-7666: Avoid a vacuous proof by checking crank success.

Line 7666 drops the Result; if crank returns Err, the proof can pass without actually exercising the intended funding path.

Suggested diff
-    let _ = engine.keeper_crank(user, now_slot, 1_000_000, funding_rate, false);
+    let crank_res = engine.keeper_crank(user, now_slot, 1_000_000, funding_rate, false);
+    assert_ok!(crank_res, "non-vacuity: keeper_crank must succeed");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/kani.rs` at line 7666, The call to engine.keeper_crank(user, now_slot,
1_000_000, funding_rate, false) currently drops the Result which can let a
failure silently pass; change this to explicitly check the Result (e.g.,
unwrap(), expect(...), or assert!(res.is_ok())) so the test fails if
keeper_crank returns Err and the funding path is actually exercised—locate the
invocation of engine.keeper_crank in tests/kani.rs and replace the discarded-let
with a checked result handling using the same parameters (user, now_slot,
1_000_000, funding_rate, false).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/kani.rs`:
- Line 7666: The call to engine.keeper_crank(user, now_slot, 1_000_000,
funding_rate, false) currently drops the Result which can let a failure silently
pass; change this to explicitly check the Result (e.g., unwrap(), expect(...),
or assert!(res.is_ok())) so the test fails if keeper_crank returns Err and the
funding path is actually exercised—locate the invocation of engine.keeper_crank
in tests/kani.rs and replace the discarded-let with a checked result handling
using the same parameters (user, now_slot, 1_000_000, funding_rate, false).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65f1aac and f33d273.

📒 Files selected for processing (1)
  • tests/kani.rs

@dcccrypto
dcccrypto merged commit 50cf8bd into master Feb 27, 2026
4 checks passed
@dcccrypto
dcccrypto deleted the fix/PERC-238-kani-compilation branch March 2, 2026 06:50
dcccrypto added a commit that referenced this pull request Mar 22, 2026
feat: Market Lifecycle Manager — Quick Launch mode
dcccrypto pushed a commit that referenced this pull request Mar 22, 2026
Verified remaining bug reports:

Bug #4 (Token names): ALREADY FIXED
- Markets page properly fetches and displays token metadata
- Uses useMultiTokenMeta hook + Supabase data
- Shows symbol, name, and mint address

Bug #5 (Width alignment): FIXED BY PR aeyakovenko#133
- Bloomberg UI overhaul resolved layout issues
- Proper grid constraints (340px right column)
- overflow-x-hidden on main container

Bug #6 (Devnet faucet): EXTERNAL - NOT OUR BUG
- faucet.solana.com, not Percolator's faucet
- Cannot fix (external dependency)

Bug #7 (Bitget warning): EXTERNAL - WALLET-SPECIFIC
- Requires investigation of Bitget's verification process
- May need direct communication with Bitget
- Low priority (wallet-specific issue)

Result: ALL ACTIONABLE BUGS FIXED (7 total bugs resolved today)
dcccrypto added a commit that referenced this pull request Mar 22, 2026
Backports oracle fix from percolator-keeper PRs #5/#7 to the monorepo
packages/keeper, which is where Railway keeper1 currently builds from.

Changes:
- liquidation.ts: Detect oracle mode (Pyth-pinned / Hyperp / Admin) and
  use appropriate price source (lastEffectivePriceE6 vs authorityPriceE6)
  instead of always using authorityPriceE6 with a blanket 60s staleness
  check. This fixes false-negative liquidation skips on Hyperp markets
  where authorityTimestamp stores funding rate, not a real timestamp.
- liquidation.ts: Fall back to lastEffectivePriceE6 when authority price
  is stale, mirroring on-chain read_price_with_authority behavior.
- liquidation.ts: Re-verification in liquidate() uses same oracle mode
  logic for price source consistency.
- index.ts: Add 5-minute startup grace period to health endpoint so
  Railway doesn't restart the container before it has time to crank.
- tests: Update liquidation test mocks with oracle mode fields
  (oracleAuthority, indexFeedId, lastEffectivePriceE6).
dcccrypto pushed a commit that referenced this pull request Mar 22, 2026
- Create tests/setup.ts with loadTestKeypair() utility
- Validates test keypair files have 600 permissions (development only)
- Auto-fixes loose permissions or warns if skipAutoFix enabled
- Update devnet-e2e.ts, t4-liquidation.ts, t9-pricing-engine.ts to use setup helper
- Improves test security infrastructure by preventing keypair exposure

Fixes: PERCOLATOR_SECURITY_AUDIT_2026-03-16 finding #5
dcccrypto pushed a commit that referenced this pull request Apr 6, 2026
Fix 2 broken proofs (None→Some(FullClose) after §11.2 change):
- proof_adl_pipeline_trade_liquidate_reopen
- t11_53_keeper_crank_quiesces_after_pending_reset

Add 9 gap-filling proofs from comprehensive audit analysis:
- Gap #3: bounded_trade_conservation_with_fees (nonzero trading fees)
- Gap #4: proof_validate_hint_preflight_conservative (ExactPartial pre-flight)
- Gap #5: proof_partial_liquidation_can_succeed (80% partial close)
- Gap #6: proof_sign_flip_trade_conserves (long→short flip)
- Gap #7: proof_convert_released_pnl_conservation (symbolic conversion)
- Gap #8: proof_close_account_fee_forgiveness_bounded (fee debt forgiveness)
- Weakness #9: proof_symbolic_margin_enforcement_on_reduce (symbolic PnL)
- Weakness #11: bounded_trade_conservation_symbolic_size (symbolic size)
- Weakness #12: proof_convert_released_pnl_exercises_conversion (non-early-return)

All proofs verified with Kani (cadical solver).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dcccrypto pushed a commit that referenced this pull request Apr 6, 2026
1. enforce_post_trade_margin now receives per-side actual collected
   fees (fee_collected_a, fee_collected_b) instead of the shared
   nominal fee. The fee-neutral comparison in the strict risk-reducing
   exemption now correctly adds back only what each side actually paid,
   preventing overstated buffers when charge_fee_to_insurance caps at
   collectible headroom.

2. validate_params comment updated: "0 <= maintenance_bps <= initial_bps"
   (was incorrectly "0 < ... <").

Not changed:
- #1 (resolved-market haircut order): inherent to the haircut model —
  convert_released_pnl and do_profit_conversion use the same
  release-then-haircut pattern. Not force_close-specific.
- #3 (invalid hints → None): spec §12 property 68 explicitly says
  "invalid keeper hints cause no liquidation action."
- #5 (non-atomic mutations): Solana SVM atomicity guarantee.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dcccrypto pushed a commit that referenced this pull request Apr 6, 2026
1. force_close_resolved now decrements oi_eff_long_q / oi_eff_short_q
   by the account's effective position before zeroing. Without this,
   force-closing all accounts left stored_pos_count == 0 but OI > 0,
   which could trigger CorruptState in subsequent lifecycle operations.

2. force_close_resolved now rejects a_basis == 0 as CorruptState
   instead of silently treating pnl_delta as 0. A nonzero position
   with a_basis == 0 is always corrupt ADL state.

3. New unit tests:
   - test_force_close_decrements_oi: verifies OI goes to 0 after
     force-closing both sides of a bilateral trade
   - test_force_close_rejects_corrupt_a_basis: verifies CorruptState
     error on a_basis == 0

4. Kani proof updated: proof_force_close_resolved_position_conservation
   now asserts OI decreases after force_close.

Not changed:
- #1 (haircut order): inherent to sequential haircut model
- #4 (non-atomic mutations): Solana SVM atomicity
- #5 (recompute_aggregates incomplete): test-only helper

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dcccrypto pushed a commit that referenced this pull request Apr 6, 2026
Proof fixes:
- bounded_margin_withdrawal: added dust-guard constraint (post-withdrawal
  capital must be 0 or >= MIN_INITIAL_DEPOSIT)
- t10_38_accrue_funding_payer_driven: fixed expected K-delta to use
  floor_div_signed_conservative_i128 (was using mul_div_ceil_u128)
- proof_audit4_init_in_place_canonical: updated assertions for
  init_oracle_price=DEFAULT_ORACLE (was asserting 0 from pre-§2.7 era)

Not changed from reviewer issues:
- #1 (public fields): acknowledged as a structural weakness but changing
  field visibility requires wrapper-side refactor
- #2 (stored funding rate validation): addressed by validate_funding_rate
  at instruction entry; stored rate only changes via recompute_r_last
- #5 (saturating counters): acknowledged; these are non-critical paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dcccrypto pushed a commit that referenced this pull request Apr 6, 2026
1. force_close_resolved: adds same-epoch phantom dust accounting
   before zeroing position (same logic as attach_effective_position
   detach path, spec §4.5/§4.6). Prevents understating
   phantom_dust_bound when resolved-closing accounts with fractional
   effective-position remainders.

2. Removed duplicate insurance_floor field from RiskEngine. Now reads
   exclusively from self.params.insurance_floor.get(). Eliminates
   split-brain risk between params and top-level field.

Not changed (with rationale):
- #1 (MAX_PNL_POS_TOT): reviewer said 1e41 but actual value is 1e38,
  which fits in u128 (max 3.4e38). Compiles correctly.
- #3/#4 (non-atomic mutations): Solana SVM atomicity guarantee.
- #5 (assert!/panic in internal helpers): these guard invariants
  proven unreachable by upstream callers. On Solana, both panic
  and Err abort atomically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dcccrypto added a commit that referenced this pull request Apr 6, 2026
* kani: make settle_warmup full model branch-accurate

* fix: liquidation path must reset warmup slope per spec §5.4

touch_account_for_liquidation was missing the warmup slope update after
mark-to-market settlement. When mark settlement increases AvailGross,
spec §5.4 requires w_start_i = current_slot (warmup restart). Without
this, stale cap = slope * elapsed allowed premature PnL-to-capital
conversion during liquidation.

Added Kani proof #158 (proof_liquidation_must_reset_warmup_on_mark_increase)
as regression test. Updated audit results and README: 158 proofs
(11 inductive, 145 strong, 2 unit test).

Closes #22

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(kani): strengthen i5 warmup assertion, clarify i8 equity comments

- i5_warmup_bounded_by_pnl: change `||` to `&&` in warmup_cap assertion
  so the bound is actually verified (was vacuously true due to earlier
  available-PnL assertion)
- i8 equity proofs: clarify comments that account_equity is the
  realized-only reporting helper, not the margin-check equity per
  spec §3.3 (margin checks use account_equity_mtm_at_oracle)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add fees_earned_total to Account for LP reward tracking

Adds a new field `fees_earned_total: U128` to the Account struct that
tracks cumulative fees generated by LP positions. Updated in
execute_trade when fees are charged. This field is read by the rewards
program via QueryLpFees to compute LP COIN rewards.

Account size grows from 240 to 256 bytes. All tests updated.

Also adds .cargo/config.toml with RUST_MIN_STACK=8MB to prevent stack
overflow in tests (RiskEngine accounts array is now >1MB).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(kani): close MM<=equity<IM gap in withdrawal margin proof

The proof asserted withdrawal success when equity >= IM and failure when
equity < MM, but left the MM <= equity < IM range unchecked. Since
withdrawal is risk-increasing, the implementation correctly rejects at
IM. Simplified to: equity >= IM → success, equity < IM → failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* enforce maintenance_margin_bps < initial_margin_bps in constructor

Assert MM < IM in both RiskEngine::new and init_in_place to prevent
degenerate margin logic. Updated unit tests that previously used
MM == IM or MM == IM == 0 to satisfy the invariant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(kani): explicit three-region withdrawal margin assertions

Replace collapsed if/else with explicit checks for all three equity
regions: equity >= IM (success), MM <= equity < IM (fail), and
equity < MM (fail). Makes the proof's coverage of the IM gap visible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: implement v9.4 spec — lazy A/K ADL + wide arithmetic + deferred resets

Complete rewrite of the risk engine to implement the v9.4 specification:

- Lazy A/K side indices for ADL (auto-deleveraging) without global scans
- i256/u256 wide arithmetic with U512 intermediates for exact mul_div
- Fixed-point positions (basis_pos_q: I256, POS_SCALE = 2^64)
- Epoch-based lazy settlement with SideMode (Normal/DrainOnly/ResetPending)
- Non-compounding quantity basis (same-epoch touches update k_snap only)
- Deferred reset finalization via InstructionContext
- Precision-exhaustion terminal drain when A_candidate rounds to 0
- absorb_protocol_loss with insurance floor
- Warmup restart-on-new-profit with old_warmable capture

New files:
- src/wide_math.rs: U256, I256, U512, all spec §4.6 helpers (49 tests)

Rewritten:
- src/percolator.rs: Full v9.4 engine implementation
- spec.md: v9.4 specification (source of truth)
- tests/unit_tests.rs: 64 unit tests for new API
- tests/kani.rs: 41 Kani proofs (6 inductive + 7 bounded + 28 property)
- tests/amm_tests.rs: 2 E2E integration tests

All 115 tests pass. All 41 Kani proofs verify successfully.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(kani): add 37 A/K mechanism proof harnesses in tests/ak.rs

Layered Kani proof suite verifying the lazy A/K mark-to-market,
funding, and ADL mechanisms using small-model algebraic proofs
(u16/i32 domain with S_POS_SCALE=4, S_ADL_ONE=256) to stay within
CBMC solver tractability limits.

Tiers:
- T0 (4 proofs): Primitive helper correctness (floor_div, mul_div, set_pnl, fee_debt)
- T1 (6 proofs): Single-event lazy==eager for mark, funding, ADL quantity/deficit
- T2 (3 proofs): Multi-event composition and fold induction
- T3 (3 proofs): Epoch mismatch, settle monotonicity, reset counter invariant
- T4 (4 proofs): ADL OI balance, A>0 guarantee, drain/bankruptcy routing
- T5 (3 proofs): Dust/rounding bounds (quantity error <=1, phantom dust)
- T6 (3 proofs): Worked example regressions against production code

All 79 harnesses (37 new + 42 existing) pass verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(kani): strengthen 11 weak/unit-test ak.rs proofs per audit

Audit findings and fixes:
- T0.3-sat: now calls set_pnl and verifies pnl_pos_tot (was: no function call)
- T0.4 sat_mul: exercises U256::MAX saturation path (was: u8 only, never saturated)
- T0.4 conservation: asserts deposit preserves vault >= c_tot + insurance (was: empty)
- T2.12: add floor-shift lemma proof; widen step case k_prefix to full i8
  range justified by the lemma (was: bounded to [-15,15])
- T3.14: make position size (u8) and K value (i8) symbolic (was: all concrete)
- T3.16: make K value (i8) symbolic (was: all concrete)
- T4.17: replace tautology with 2-account OI balance proof via A-shrink
  and lazy_eff_q (was: asserting x==x)
- T5.22: prove 2-account floor-rounding dust sum < 2 units with symbolic
  positions and A values (was: assert restated assume)
- T5.23: prove worst-case dust N*(POS_SCALE-1)/POS_SCALE < N (was: circular)
- T6.25: add per-account lazy PnL verification (was: duplicate of T4.19)
- T6.26: make K value (i8) and position size (u8) symbolic (was: all concrete)

All 80 harnesses (38 ak.rs + 42 kani.rs) pass verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update spec to v9.5

Key changes:
- Dynamic per-side phantom dust bounds (replaces static MAX_ACCOUNTS cap)
- execute_trade explicit post-trade loss settlement for both accounts
- keeper_crank must run end-of-instruction reset scheduling/finalization
- deposit is pure capital transfer (must not touch positions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: implement spec v9.5 changes

- Dynamic phantom dust bounds (per-side counters replacing static MAX_ACCOUNTS cap)
- Universal post-trade loss settlement in execute_trade step 11
- InstructionContext + schedule/finalize resets in keeper_crank
- Deposit is pure capital transfer (removed touch_account_full)
- Skip trivial dust clearance when no dust and no OI exist

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update spec.md

* fix: spec v9.5 compliance — epoch snaps, OI assertions, version string

- attach_effective_position: set epoch_snap and k_snap to current
  side values when zeroing (spec §4.5), matching settle_side_effects
- execute_trade: add OI_eff_long == OI_eff_short assertion (step 18)
- liquidate_at_oracle: add OI_eff_long == OI_eff_short assertion (step 8)
- execute_trade: document step 13 as no-op (no funding-rate input change)
- Update module doc version string from v9.4 to v9.5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 7 engine safety fixes + 26 new Kani proofs (65 total)

Engine fixes (src/percolator.rs):
1. Organic-close bankruptcy bypass — reject undercollateralized flat
   accounts instead of absorbing losses via resolve_flat_negative
2. Missing reset preflight — add maybe_finalize_ready_reset_sides()
   auto-finalizing completed resets before side-mode checks
3. Mid-instruction reset in keeper_crank — split liquidate_at_oracle
   into internal variant, share InstructionContext across crank loop
4. Fee-debt seniority — sweep fee debt immediately after restart
   conversion yields new capital
5. Warmup restart — capture old_avail before trade, only restart
   when post-trade availability actually exceeds pre-trade
6. Dust accounting in attach_effective_position — increment phantom
   dust bound when replacing a basis with nonzero remainder
7. Checked arithmetic — replace saturating/clamping ops with checked
   variants that panic on corruption in add_u128, sub_u128, mul_u128,
   update_single_oi, charge_fee_safe, do_profit_conversion,
   settle_maintenance_fee_internal, fee_debt_sweep

Proof suite (tests/ak.rs):
- Fix 6 existing broken/weak proofs (T6.24, T4.18, T4.19, T0.4, T0.2,
  T3.14/T3.16)
- Add 26 new proofs across Tiers 7-10: non-compounding basis,
  dynamic dust/reset lifecycle, ADL fallback branches, engine
  integration, fee/warmup, accrue_market_to
- Total proof count: 65 (up from 39)

Test fix (tests/unit_tests.rs):
- test_reset_pending_blocks_new_trades: add stale_account_count_short=1
  so ResetPending side isn't auto-finalized by new preflight logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(kani): convert engine proofs to small-model, fix unwind bounds

- t0_2c/t0_2d: increase unwind from 1 to 18 (U512 division loop)
- t7_27/t7_28: convert from engine to small-model algebraic proofs
  (settle_side_effects calls mul_div_floor_u256 → U512 division needs
  unwind 514+, infeasible for CBMC)
- t4_22: convert to small-model (enqueue_adl uses U256 division)
- t8_30-t8_34: convert to small-model algebraic proofs (execute_trade
  and liquidate_at_oracle use U256 division internally)

All 65 proofs now pass with their declared unwind bounds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(kani): add 16 real-engine integration proofs (Tier 11)

Addresses gaps identified in proof suite audit:

Real-engine proofs with concrete inputs and unwind(70):
- T11.39: same_epoch_settle_idempotent — real settle_side_effects path
- T11.40: non_compounding_quantity_basis — basis/a_basis unchanged across settles
- T11.41: attach_effective_position_remainder — phantom_dust_bound accounting
- T11.42: dynamic_dust_bound_inductive — N zeroings → dust_bound >= N
- T11.43: end_instruction_auto_finalizes — ResetPending → Normal transition
- T11.44: trade_path_reopens_ready_reset — execute_trade auto-finalizes
- T11.45: enqueue_adl_nonrepr_beta — beta overflow → absorb, A still shrinks
- T11.46: enqueue_adl_k_add_overflow — K overflow → absorb, A still shrinks
- T11.47: precision_exhaustion_terminal_drain — A_candidate=0 → both resets
- T11.48: bankruptcy_routes_q_D_zero — D=0 → K unchanged, A shrinks
- T11.49: pure_pnl_bankruptcy — q_close=0, D>0 → K changes, A unchanged
- T11.50: execute_trade_atomic_oi_sign_flip — position flip preserves OI balance
- T11.51: execute_trade_slippage_zero_sum — zero-fee trade preserves vault
- T11.52: touch_account_full_restart_fee_seniority — warmup + fee debt sweep
- T11.53: keeper_crank_quiesces — early break on pending_reset
- T11.54: worked_example_regression — open, ADL, settle with final assertions

Total proof count: 81 (up from 65).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(kani): rewrite stale t5_23 to dynamic dust bound, add t6_26b nonzero k_diff

- t5_23: rewrite from static account-count dust bound to dynamic
  phantom_dust_bound model matching current engine design
- t6_26b: full drain reset regression with nonzero k_diff (the hard
  path) — terminal K_epoch_start used, nonzero pnl_delta realized,
  stale counters decrement, basis zeroes, reset finalizes safely

Total proof count: 82.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: v10.0 — 4 engine correctness fixes + proof suite updates

1. enqueue_adl signed representability: replace broken
   from_raw_u256_pub(x).checked_neg() with try_negate_u256_to_i256()
   that correctly rejects magnitudes > 2^255 (critical: old code could
   turn bankruptcy loss into gain for large D values)

2. Side-mode gating: check_side_mode_for_trade now gates on net side
   OI increase across both trade accounts, not per-account position
   increase (was too restrictive, blocking valid OI-neutral trades)

3. Fee-credit invariants: deposit_fee_credits rejects amount > i128::MAX
   (was silently wrapping via u128 as i128); settle_maintenance_fee and
   fee_debt_sweep use saturating_sub/add instead of unwrap_or silent
   clamps that could forgive debt or lose precision

4. enqueue_adl liq-side OI: saturating_sub → checked_sub returning
   Err(CorruptState) on underflow

Proof suite: t11_45 rewritten as algebraic try_negate_u256 correctness
proof; t11_46/48/49 use POS_SCALE instead of ADL_ONE to keep U512
division shift within unwind(70); kani.rs side-mode proof fixed with
stale_account_count. All 82 proofs + 64 unit tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: enqueue_adl global A truncation dust must feed phantom_dust_bound

The floor in A_candidate = floor(A_old * OI_post / OI) injects up to
ceil(OI / A_old) q-units of phantom OI into the authoritative OI_eff
tracker. When all opposing users close their positions, this untracked
dust remains in OI_eff. schedule_end_of_instruction_resets then fails
with CorruptState (OI_eff > phantom_dust_bound), permanently deadlocking
the market — no user can ever close their final position.

Fix: after computing A_candidate, add ceil(OI / A_old) to
phantom_dust_bound_opp_q. This is economically microscopic (≤ 2^40
internal q-units ≈ 2^{-24} base tokens in the worst case) but
mathematically guarantees that legitimate clean-empty states always
pass the strict clear_bound_q check.

Proof-driven: t12_53 written first, confirmed the deadlock via Kani
(schedule_end_of_instruction_resets returned Err(CorruptState) with
truncation dust ≈ 0.429 * POS_SCALE vs phantom_dust_bound = 1).
After fix, proof passes. All 83 proofs + 64 unit tests verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: spec v10.0 — add global A truncation dust to enqueue_adl §5.6

Updates §5.6 step 8, pseudocode, invariant 5, invariant 28, and key
changes to document that enqueue_adl must add ceil(OI / A_old) to
phantom_dust_bound_opp_q after floor-truncating A_candidate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* spec

* feat: implement spec v10.5 changes — payer-driven funding, fused ADL, fee seniority

- Payer-driven funding rounding (§5.4): ceil for payer, floor for receiver
- Fused delta_K_abs = ceil(D*A*POS_SCALE/OI) in enqueue_adl (§5.6)
- Conditional A-truncation dust only when remainder != 0 (§5.6)
- stored_pos_count_opp == 0 early return with absorb_protocol_loss (§5.6)
- Bilateral/unilateral split in schedule_end_of_instruction_resets (§5.7)
- inc_phantom_dust_bound_by helper (§4.6.1)
- mul_div_floor_u256_with_rem helper for A_candidate remainder
- Immediate fee sweep after restart_on_new_profit in execute_trade (§6.5)
- 7 new kani proofs (T13.54-T13.60) for v10.5 coverage
- 1 new unit test for spec property #23 (fee seniority)
- Updated all affected kani proofs for fused delta_K formula

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: proof suite — false theorem, weak assertions, inductive dust bound

1. Replace t7_28 with two correct theorems:
   - t7_28a: correct floor inequality direction
     (total_two_touch <= pnl_single, not >=)
   - t7_28b: exact additivity for divisible K increments

2. Strengthen t11_52 (fee seniority):
   - Assert on fee_credits, capital, and insurance fund
   - Not just k_snap update

3. Strengthen t11_53 (crank quiescence):
   - Use 3 accounts instead of 2
   - Verify 3rd account's state is completely unchanged
     after pending reset triggers on 2nd

4. Add Tier 14 inductive dust-bound proofs (T14.61-T14.65):
   - T14.61: ADL A-truncation formula sufficient (2 accounts, symbolic)
   - T14.62: Same-epoch position zeroing preservation
   - T14.63: Position reattach remainder preservation
   - T14.64: Full-drain reset trivial preservation
   - T14.65: End-to-end engine clearance (ADL → close → reset)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 5 safety issues — error propagation, overflow guards, clamp-not-panic

1. CRITICAL: keeper_crank now propagates errors from touch_account_full
   and liquidate_at_oracle_internal instead of swallowing them. Prevents
   committing half-mutated state after internal failures.

2. HIGH: enqueue_adl uses checked_mul_div_ceil_u256 for delta_K_abs.
   If quotient overflows U256 (extreme D/OI ratio), routes to
   absorb_protocol_loss instead of panicking (§1.5 Rule 14).

3. HIGH: settle_maintenance_fee_internal clamps fee_credits to
   -(i128::MAX) instead of allowing i128::MIN, which is a dangerous
   sentinel value for downstream negation operations.

4. HIGH: charge_fee_safe clamps PnL on underflow instead of panicking.
   Prevents bricking liquidations for deeply underwater accounts
   (§1.5 Rule 16).

5. MINOR: checked_u256_mul_i256 uses try_negate_u256_to_i256 for the
   negative path, correctly handling the product == 2^255 boundary
   (I256::MIN) instead of returning a false overflow.

Issue #3 from audit (funding ceil vs floor) is NOT a bug — spec §5.4
step 5 explicitly mandates ceil for payer K-space loss.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: proof suite audit — cross-multiplied invariant, dimensional fix, tautology removal

- t13_54: replace false raw-sum no-mint assertion (dk_long + dk_short <= 0)
  with correct cross-multiplied form (dk_long*A_short + dk_short*A_long <= 0)
- t11_53: rewrite crank quiescence test to trigger pending_reset via
  liquidation → enqueue_adl (dust clearance runs post-loop, not mid-loop)
- ADL small models (t1_8, t1_8b, t1_9, t4_19, t6_24, t6_25, t13_59):
  remove extra S_POS_SCALE from delta_k_abs — POS_SCALE cancels with
  OI_eff denominator (OI_eff = OI_base * POS_SCALE)
- t6_24: update hardcoded values (delta_k 256→64, K 2304→2496, PnL 72→78)
- Tier 8 (t8_30-34): delete tautological proofs that proved algebraic
  identities about local variables without exercising engine code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 5 implementation issues — error propagation, self-trade, same-slot mark

1. schedule_end_of_instruction_resets: propagate errors with ? in all
   5 call sites (withdraw, execute_trade, liquidate_at_oracle,
   keeper_crank, close_account) instead of discarding with let _

2. execute_trade: reject a == b self-trades — prevents one account
   from creating matched OI on both sides with only one stored position

3. accrue_market_to: apply mark-only delta_P when dt == 0 but price
   changed — previously silently dropped same-slot price moves

4. add_user/add_lp c_tot: replace saturating_add with checked_add,
   return Err(Overflow) on violation.
   settle_maintenance_fee_internal: return Result<()>, use checked
   arithmetic instead of saturating/clamping

5. charge_fee_safe: return Result<()> with checked_sub instead of
   clamping — propagates error to callers instead of silently absorbing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): strengthen t1_8/t1_9 bounds, t11_52 restart path, t13_54 compile

- t1_8/t1_9: add upper bound assertion (lazy_loss <= eager_loss + q_base)
- t11_52: set pre-existing positive PnL so restart_on_new_profit converts
  warmable → capital via do_profit_conversion (old_warmable > 0)
- t13_54: replace I256::checked_mul (doesn't exist) with i128 arithmetic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: run end-of-instruction resets unconditionally in liquidate_at_oracle

touch_account_full mutates state (PnL settle, fee sweep, position zeroing)
even when liquidation returns Ok(false). Skipping schedule/finalize resets
on that path could leave a clean-empty market stuck in Normal mode instead
of entering ResetPending for garbage collection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: reorganize proof suite into 6 topic files + add 20 unit tests

Decompose 132 proofs from 2 monolithic files (kani.rs, ak.rs) into
6 topic-based files matching the 7-section proof checklist:
- proofs_arithmetic.rs (16 proofs) — pure math helper correctness
- proofs_invariants.rs (26 proofs) — global inductive invariants
- proofs_lazy_ak.rs (28 proofs) — A/K refinement, events, settlement
- proofs_safety.rs (27 proofs) — economic safety, conservation
- proofs_instructions.rs (32 proofs) — per-instruction correctness
- proofs_liveness.rs (9 proofs) — liveness, progress, no-deadlock

Add tests/common/mod.rs with shared helpers, constants, and small-model
functions. Add 6 new Kani proofs covering gaps in the checklist.

Add 20 new unit tests covering CBMC-impractical gaps: wide arithmetic
(U512 paths), multi-step funding accrual, keeper crank behavior,
liquidation lifecycle, conservation full-lifecycle, oracle boundaries,
maintenance fee overflow, PnL boundary safety, and side-mode gating.

All 138 Kani proofs compile-check. All 91 unit tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 3 critical issues — withdraw haircut inflation, funding rate DoS, GC fee_credits

1. Withdraw margin simulation desync (exploitable margin bypass):
   withdraw() called set_capital() to simulate post-withdrawal state,
   decreasing c_tot but leaving vault unchanged. This inflated
   Residual = Vault - (C_tot + I), boosting the haircut ratio and
   allowing undercollateralized withdrawals. Fix: also temporarily
   adjust vault during the simulation.

2. Unvalidated funding rate (permanent protocol brick):
   keeper_crank() stored the funding rate without bounds checking.
   A rate > MAX_ABS_FUNDING_BPS_PER_SLOT would cause every future
   accrue_market_to() to return Overflow, bricking the protocol.
   Fix: validate the rate before storing it.

3. GC dust deletes prepaid fee_credits (asset destruction):
   garbage_collect_dust() didn't check fee_credits in the dust
   predicate. Accounts with zero capital/position but prepaid
   fee_credits were silently deleted. Fix: add fee_credits != 0
   guard to the dust predicate.

Each fix is accompanied by a failing proof (Kani) and unit test that
demonstrate the bug before the fix, and pass after.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): 5 liveness proof issues — unreachable state, missing assertions, coverage gaps

1. T11.53: Fixed unreachable OI imbalance. Old proof used oi_long=2*PS,
   oi_short=PS — impossible between instructions per spec. Restructured
   with balanced OI (PS each), single long account holding full side OI.
   Liquidation zeros opposing OI → pending_reset → crank quiesces.

2. T11.46: Added K-invariance and insurance-fund assertions. Now verifies
   K_opp is unchanged on K-space overflow AND insurance fund decreases
   (absorb_protocol_loss invoked), per spec §5.6 step 6.

3. proof_drain_only_to_reset_progress: Fixed to test §5.7.D path in
   isolation. Old setup had stored_pos_count_short=0 so §5.7.A fired
   first. Now short side has stored positions, so only §5.7.D fires.
   Also verifies opposite side does NOT get spurious pending_reset.

4. NEW proof_keeper_reset_lifecycle_last_stale_triggers_finalize:
   Spec property #26 — keeper touches last stale account on
   ResetPending side → stale count drops to 0 → finalize transitions
   side from ResetPending → Normal.

5. NEW proof_unilateral_empty_orphan_dust_clearance:
   Spec property #32 — §5.7.B path: long side has no stored positions
   but phantom dust OI. Short side has positions. Schedule resets clears
   dust and resets both sides when OI <= dust bound.

All 11 liveness proofs verified by Kani (including T11.53 at 567s).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): strengthen t1_8/t1_9 bounds, t11_52 restart path, t13_54 compile

- proofs_arithmetic: document t0_4_fee_debt_i128_min as defensive test,
  rewrite proof_notional_scales_with_price to use engine's notional(),
  add proof_wide_signed_mul_div_floor_sign_and_rounding (U512 path),
  add proof_wide_signed_mul_div_floor_zero_inputs, fix operator precedence
- proofs_invariants: fix t0_4_conservation_check_handles_overflow to use
  full u128 inputs, rewrite proof_account_equity_net_nonnegative with
  2-account haircut interaction, rewrite proof_fee_credits_never_i128_min
  to verify checked_sub boundary + fee_debt safety
- proofs_lazy_ak: fix t3_14_epoch_mismatch_forces_terminal_close to use
  independent k_snap and k_epoch_start (non-trivial k_diff)
- proofs_safety: rewrite bounded_equity_nonneg_flat with 2-account
  non-trivial haircut, rewrite proof_protected_principal to use
  touch_account_full (real settlement pipeline)

All modified proofs Kani-verified. 94 unit tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): eliminate vacuous proofs, strengthen assertions, add missing spec properties

proofs_safety.rs:
- t4_18: rewrite to use engine enqueue_adl (was manually assigning+asserting same values)
- t4_22: rewrite to use engine enqueue_adl with K near I256::MIN (was asserting x==x)
- t4_23: rewrite to use engine enqueue_adl with D=0 (was manually assigning k_after=k_before)
- proof_junior_profit_backing: fix assertion to effective_ppt <= residual (was <= residual+ppt)

proofs_instructions.rs:
- t14_62: rewrite to use engine settle_side_effects for same-epoch zeroing (was assert 1>=1)
- t14_63: strengthen with floor division identity and remainder bound (was assert 1>=1)
- NEW proof_fee_shortfall_deducted_from_pnl: spec property #18 — fee shortfall from PnL
- NEW proof_organic_close_bankruptcy_guard: spec property #16 — flat close with neg PnL rejected

All 8 modified/new proofs Kani-verified. 94 unit tests pass. 147 total proofs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 3 safety issues — GC dust predicate, fee seniority, min liquidation fee

1. GC dust predicate: `!fee_credits.is_zero()` protected dead accounts with
   negative fee_credits (uncollectible debt), causing permanent state bloat.
   Fix: only protect positive (prepaid) credits; write off negative on GC.

2. Eager maintenance fee sweep: settle_maintenance_fee_internal swept capital
   at Step 8, before settle_losses (Step 9) had first claim. This violated
   trading loss seniority and socialized fee debt through ADL.
   Fix: Step 8 only extends fee debt; capital sweep deferred to Step 12.

3. Minimum liquidation fee: min_liquidation_abs field was defined in RiskParams
   but never enforced. Dust positions liquidated with zero penalty.
   Fix: fee = min(max(bps_fee, min_liquidation_abs), cap).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: rewrite README — H keeps exits fair, A/K keeps overhang clearing fair

Restructure around the two core mechanisms and how they compose:
- H (haircut ratio): proportional profit scaling for fair exits
- A/K (lazy side indices): proportional position/deficit scaling for
  fair overhang clearing with deterministic market recovery

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: remove proof stats from README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: simplify README to high-level math only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: alternative to ADL queues, not ADL itself (A/K is an ADL)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): 7 proof issues — forbidden input, weak assertions, missing coverage

1. t0_4_fee_debt_i128_min: test valid inputs symbolically instead of
   validating the spec-forbidden i128::MIN value
2. t13_54_funding_no_mint_asymmetric_a: widen A range from u8 to u16
3. proof_withdraw_simulation_preserves_residual: call engine.withdraw()
   instead of manual simulation
4. proof_gc_dust_preserves_fee_credits: add negative fee_credits case
   (dead account with debt must be collected and debt written off)
5. proof_fee_shortfall_deducted_from_pnl: non-disjunctive assertion —
   with zero capital, PnL must decrease
6. proof_organic_close_bankruptcy_guard: use oracle price crash instead
   of manual set_pnl to reach insolvent state through market mechanics
7. NEW proof_solvent_flat_close_succeeds: spec property #24 — solvent
   trader closing to flat must not be rejected

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add fairness exactness caveat for H vs A/K

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): add min_liquidation_abs safety + trading loss seniority proofs

1. proof_min_liq_abs_does_not_block_liquidation: symbolic min_abs up to
   u16::MAX, verifies liquidation of underwater accounts is never blocked
   by the fee floor and conservation holds afterward.

2. proof_trading_loss_seniority: verifies touch_account_full ordering —
   settle_losses (step 9) gets capital before fee_debt_sweep (step 12),
   ensuring trading losses are covered before protocol fees.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: reject fee_credits == i128::MIN per spec §2.1 + proof

The claimed bug (fee_debt_u128_checked panics on i128::MIN) is NOT real —
unsigned_abs() handles it gracefully and fee_debt_sweep uses saturating_add.
No panic exists anywhere in the pipeline.

However, spec §2.1 says i128::MIN is forbidden, so add the 1-line guard in
settle_maintenance_fee_internal for spec compliance. Also add a Kani proof
(proof_fee_credits_i128_min_boundary_no_panic) that demonstrates the engine
handles i128::MIN fee_credits without panic even without the guard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): replace contradictory i128::MIN robustness test with rejection proof

The old proof tested fee_debt_u128_checked on i128::MIN (a spec-forbidden
input) and injected i128::MIN into engine state, contradicting both the
spec §2.1 prohibition and the engine guard added in the same commit.

Replace with proof_settle_fee_rejects_i128_min: sets fee_credits to
-(i128::MAX) (lowest valid value), advances 1 fee slot, and asserts the
engine returns Err — confirming the §2.1 guard works.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: migrate to native 128-bit architecture per spec v11.5

All persistent state moves from emulated wide types (I256/U256) to native
i128/u128 with base-10 scaling constants. U256/I256 retained only for
transient wide intermediates in arithmetic helpers.

Key changes:
- POS_SCALE=1e6, ADL_ONE=1e6, MIN_A_SIDE=1e3, MAX_ORACLE_PRICE=1e12
- 12 new normative bounds constants (MAX_POSITION_ABS_Q, MAX_TRADE_SIZE_Q, etc.)
- Account/RiskEngine fields: pnl, reserved_pnl, position_basis_q, adl_k_snap,
  adl_coeff_*, oi_eff_*, phantom_dust_bound_*, pnl_pos_tot all native 128-bit
- New §4.8 arithmetic helpers: mul_div_{floor,ceil}_u128, wide_mul_div_floor_u128,
  wide_signed_mul_div_floor_from_k_pair, wide_mul_div_ceil_u128_or_over_i128max,
  saturating_mul_u128_u64
- charge_fee_safe → charge_fee_to_insurance (shortfall → fee_credits, not PNL)
- set_pnl: per-account + aggregate positive-PnL bounds
- accrue_market_to: mark-once + funding-both-sides rule
- execute_trade: MAX_TRADE_SIZE_Q + MAX_ACCOUNT_NOTIONAL input bounds
- enqueue_adl: delta_K representability via wide_mul_div_ceil_u128_or_over_i128max
- All 150 tests pass, all proof files compile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* spec

* fix: spec v11.9 compliance audit — 10 normative fixes

1. materialized_account_count: increment with checked arithmetic
   and MAX_MATERIALIZED_ACCOUNTS bound in add_user/add_lp (§10.0)
2. MAX_VAULT_TVL: enforce on deposit (§10.2 step 5)
3. Loss seniority: settle_losses before charge_fee in execute_trade
   (§10.4 step 18-19, §0 item 14)
4. Time monotonicity: reject now_slot < current_slot/slot_last in
   touch_account_full, accrue_market_to, deposit (§1.4.1)
5. accrue_market_to sets current_slot = now_slot on all paths (§5.4)
6. settle_account: add top-level wrapper per §10.7
7. trade_notional bound: enforce <= MAX_ACCOUNT_NOTIONAL (§10.4 step 6)
8. Trading fee: use mul_div_ceil_u128 per spec §8.1
9. Liquidation fee: use mul_div_ceil_u128 and enforce min floor when
   q_close_q > 0 even if closed_notional is 0 (§8.4)
10. Canonical zero-position: k_snap_i = 0 per §2.1.1 in
    settle_side_effects and attach_effective_position
11. Deposit: settle losses + resolve flat negative per §10.2 steps 8-9

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): 4 proof audit fixes per v11.9 spec review

1. Add K-pair variant proof (proof_k_pair_variant_sign_and_rounding):
   tests wide_signed_mul_div_floor_from_k_pair (spec §4.8), the
   normative helper used in settle_side_effects. Verifies wide
   K-difference subtraction, sign handling, and floor rounding.

2. Rewrite proof_fee_shortfall_routes_to_fee_credits (was
   proof_fee_shortfall_deducted_from_pnl): now correctly verifies
   spec property #17 — fee shortfall decreases fee_credits, not PNL.
   Old proof's PNL assertion was masked by warmup conversion.

3. Rewrite dust clearance proofs (t12_53, t14_65) to use engine's
   actual settle_side_effects + attach_effective_position instead of
   manual OI/dust simulation. Catches discrepancies if the engine's
   actual settle path produces different dust increments.

4. Add t2_14_compose_mark_adl_mark: composition proof across an
   A-changing ADL event (mark → ADL → mark). All prior t2_1x proofs
   operated at constant A = S_ADL_ONE. This exercises the core §5.1
   identity K_new = K_old + A_old * β when A has been modified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: reduce LIQ_BUDGET_PER_CRANK to 64, make free_slot pub

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove redundant touch_account_full from liquidation per spec §9.4

Spec §9.4 precondition: "the enclosing liquidate(...) top-level
instruction has already called touch_account_full(i)" and "no
additional touch_account_full(i, ...) may be performed inside this
local routine."

Moved touch_account_full from liquidate_at_oracle_internal to the
public liquidate_at_oracle wrapper. keeper_crank already touches
before calling the internal routine.

CU benchmark scenario 9: 1,372K → 1,281K (98.0% → 91.5% of 1.4M limit)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* spec

* spec

* fix: v11.12 spec compliance — engine + 3 proof fixes

Engine (src/percolator.rs):
- free_slot: decrement materialized_account_count (§2.1.2)
- accrue_market_to: reorder per §5.4 — OI snapshot before early return,
  current_slot set only at early-return/end (not step 1)
- touch_account_full: add oracle_price validation, swap settle_losses
  before maintenance fees (§10.1 steps 8-9)
- keeper_crank: apply caller fee discount before touch (current-state
  gating §10.6), add OI balance assert at end (step 7)
- Remove redundant current_slot assignments from execute_trade,
  liquidate_at_oracle_internal, withdraw, close_account

Proofs:
- t2_14: fix POS_SCALE unit error — divide q_eff_new by S_POS_SCALE
  before eager mark2 computation
- t12_53: add liquidated short account so ADL short-side OI decrement
  has a matching account removal (prevents OI underflow)
- t14_65: same pattern — add liquidated short with 3×POS_SCALE

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): 9 proof fixes for base-10 scaling + spec compliance

proofs_instructions.rs:
- t11_42, t5_24, t14_62: use basis=1/a_basis=3 so floor(1*1/3)=0
  (old POS_SCALE≠ADL_ONE gave floor(POS_SCALE/ADL_ONE)=0; now equal)
- t12_53, t14_65: redesign OI accounting — remove manual per-account
  OI subtraction that broke the OI balance invariant; instead compute
  dust from actual vs tracked OI and set balanced residual for reset
- t11_41: already committed (a_basis=7/a_side=6 for nonzero remainder)

proofs_lazy_ak.rs:
- t2_14: remove intermediate position rounding from eager model;
  compute mark2 as floor(q_base*a_new*dp2/a0) directly to match
  K-based lazy model (no floor-then-multiply mismatch)
- t7_28b: add basis%4==0 constraint (positions are POS_SCALE-aligned
  in the real engine; exact additivity requires this)

proofs_safety.rs:
- proof_gc_dust: deposit before setting current_slot=1, use now_slot=1
- proof_settle_fee: increase unwind bound from 1 to 34

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: two-phase keeper barrier wave (spec addendum A2)

Add optional two-phase keeper execution mode for compute-limited
environments. Phase 1 performs a read-only barrier scan to classify
accounts; phase 2 performs bounded exact-state revalidation of
shortlisted accounts.

New types: ReviewClass, BarrierSnapshot with side-accessor methods.
New functions: capture_barrier_snapshot, preview_account_local_fee_debt_ub,
preview_account_at_barrier (core §A2.1+§A3 classifier), keeper_barrier_wave.
CrankOutcome extended with num_phase1_scanned, num_phase2_revalidations.

17 unit tests per spec §A7 (test 16 skipped: no sieve implemented).
7 Kani proofs for barrier scan properties including no-false-negative,
epoch-mismatch safety, fee UB correctness, OI balance, and conservation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: v11.21 spec compliance — off-chain shortlist keeper, split equity, matured warmup

Implements all spec changes from v11.12 to v11.21:

- Replace barrier wave with off-chain shortlist keeper_crank(now_slot, oracle_price, &[u16], max_revalidations)
- Split equity: Eq_maint_raw (full local PnL) vs Eq_init_net (haircutted matured)
- New pnl_matured_pos_tot aggregate tracking with reserve-first semantics in set_pnl
- New warmup model: advance_profit_warmup / restart_warmup_after_reserve_increase
- consume_released_pnl for profit conversion without touching R_i
- convert_released_pnl instruction for open-position matured profit access
- Universal withdrawal dust guard (MIN_INITIAL_DEPOSIT)
- Risk-reducing trade buffer comparison using Eq_maint_raw_i
- Flat-only automatic conversion in touch_account_full
- Remove barrier wave code (ReviewClass, BarrierSnapshot, keeper_barrier_wave)
- Delete proofs_barrier.rs, fix warmup proofs for new model
- Add 6 property tests (49-54) for new spec invariants
- All 106 tests pass, all Kani proofs compile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): update 4 stale proofs for v11.21 compliance

- proof_set_pnl_clamps_reserved_pnl: set up valid state via set_pnl
  instead of directly assigning reserved_pnl (violates invariant)
- proof_haircut_ratio_no_division_by_zero: set pnl_matured_pos_tot
  (v11.21 uses this as denominator, not pnl_pos_tot)
- proof_warmup_bounded_by_available/cap: rewrite as
  proof_warmup_release_bounded_by_reserved/slope for new
  advance_profit_warmup model
- t9_35_warmup_slope_preservation: rewrite as
  t9_35_warmup_release_monotone_in_time
- proofs_liveness keeper_crank calls: update to new signature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 3 spec compliance bugs — insurance-first ADL, liq-side reset, exact equity arithmetic

1. enqueue_adl now calls use_insurance_buffer(D) at step 2 per §5.6,
   socializing only D_rem through K-space instead of the full deficit.
2. enqueue_adl sets pending_reset for liq_side when OI_eff_liq_side==0
   at steps 4, 5, and 8 per §5.6.
3. account_equity_maint_raw/init_raw use exact I256 arithmetic per §3.4
   instead of saturating_add/sub. Risk-reducing buffer comparison in
   execute_trade step 29 now uses exact I256 throughout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): strengthen 8 weak proofs per audit

proofs_safety.rs:
- bounded_haircut_ratio_bounded: set pnl_matured_pos_tot (v11.21 denominator)
  to exercise h < 1 branch. 1/1 cover satisfied, 0.38s.
- bounded_equity_nonneg_flat: set pnl_matured_pos_tot with symbolic matured
  value to exercise non-trivial haircut in equity computation.
- proof_junior_profit_backing: rewrite as pure-math proof with u8 symbolic
  vault/c_tot/ins/matured. Both h < 1 and h = 1 branches covered. 1.4s.
- bounded_withdraw_conservation: make deposit symbolic, add kani::cover!
  to detect vacuity. 1/1 cover satisfied.
- bounded_trade_conservation: add kani::cover! for zero-sum PnL path,
  add pnl_pos_tot bound assertion. 1/1 cover satisfied.
- proof_protected_principal: make deposits symbolic (u32) instead of
  concrete 500_000. 36s.

proofs_invariants.rs:
- t0_4_conservation_check_handles_overflow: add kani::cover! in None
  (overflow) branch.
- proof_account_equity_net_nonnegative: set pnl_matured_pos_tot to
  exercise h < 1 in equity computation.

Key fix: the pnl_matured_pos_tot blind spot. 5 proofs previously had
the haircut always = 1 because pnl_matured_pos_tot was 0. Now all
exercise the h < 1 branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 4 audit findings — bilateral fee, risk-reducing proof, ADL pipeline, r_last

1. Trading fee charged to both a and b (spec §10.5 step 28):
   charge_fee_to_insurance now called for both counterparties.
   Insurance revenue doubled to spec-mandated level. Account b's
   post-trade margin check now uses correct (fee-reduced) equity.

2. Risk-reducing exemption proof (enforce_one_side_margin I256 path):
   New proof_risk_reducing_exemption_path exercises the exact I256
   buffer comparison at lines 2506-2520. Both cover properties
   satisfied: risk-reducing trade accepted, risk-increasing rejected.
   29s verification.

3. Full ADL pipeline integration proof:
   New proof_adl_pipeline_trade_liquidate_reopen exercises:
   execute_trade → liquidate_at_oracle → enqueue_adl → resets →
   subsequent trade. OI_eff_long == OI_eff_short verified after
   every step. Post-ADL trade cover property satisfied. 64s.

4. recompute_r_last_from_final_state added to all instructions:
   execute_trade, withdraw, settle_account, convert_released_pnl,
   close_account, and liquidate_at_oracle now all call
   recompute_r_last after end-of-instruction resets per spec.
   Currently a no-op but required for production funding derivation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(proofs): eliminate vacuous/tautological proofs, strengthen assertions

- Delete 15 tautological lazy A/K proofs where k_init=0 caused algebraic cancellation
- Rewrite 2 vacuous arithmetic proofs to call real mul_div_floor/ceil_u256 instead of native ops
- Rewrite 6 tautological invariant proofs to use real RiskEngine instead of raw arithmetic
- Strengthen 5 safety proofs: use execute_trade/touch_account_full instead of manual simulation
- Strengthen 8 unit tests with exact value assertions and real liquidation scenarios
- Update offsets example with additional field offsets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 3 security audit findings + TDD proofs

1. Buffer masking attack (CRITICAL → FIXED):
   enforce_one_side_margin now requires Eq_maint_raw_post >= Eq_maint_raw_pre
   for risk-reducing trades. Prevents exploitation where a bankrupt account
   closes 99% position with adverse slippage, extracting value via MM_req
   drop while raw equity decreases.
   Proof: proof_buffer_masking_blocked (45s, PASS)

2. Phantom dust liquidation revert (CRITICAL → tested, existing behavior OK):
   When OI is balanced and both sides drain to zero, the existing step 5
   correctly resets. The audit's specific scenario (OI_liq_side > OI_opp)
   requires pre-existing OI imbalance which violates the OI_long == OI_short
   invariant maintained by all instructions.
   Proof: proof_phantom_dust_drain_no_revert (0.4s, PASS)

3. Insurance fund starvation via deferred fee sweep (MAJOR → FIXED):
   fee_debt_sweep now consumes matured released PnL (via consume_released_pnl)
   when capital is insufficient to cover fee debt. Prevents profitable
   open-position accounts from accumulating infinite fee IOUs.
   Proof: proof_fee_debt_sweep_consumes_released_pnl (1.2s, PASS)

4. Trapped winner (Finding 3 → NOT A BUG):
   The flat exit guard at line 2487-2492 checks `pnl < 0`, NOT Eq_init_raw_i.
   A profitable trader closing to flat has pnl >= 0 (trade PnL is zero-sum),
   so the check passes. Trading fees reduce capital, not PnL.

Also: released_pos and fee_debt_sweep made pub for test access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update spec.md

* fix: v11.26 spec compliance — flat-close guard, fee-neutral risk-reducing, constants

v11.26 change #2 — Flat-close guard uses Eq_maint_raw_i >= 0:
  enforce_one_side_margin now checks account_equity_maint_raw_wide >= 0
  for flat exits, not just pnl >= 0. Prevents flat exit with negative
  net wealth from fee debt (C + PNL - FeeDebt < 0).
  TDD: proof_v1126_flat_close_uses_eq_maint_raw (19s, PASS)

v11.26 change #1 — Fee-neutral risk-reducing exemption:
  Buffer comparison now adds fee back: (Eq_maint_raw_post + fee) - MM_req_post.
  Also enforces shortfall guard: min(Eq_maint_raw_post + fee, 0) >= min(pre, 0).
  Pure fee friction no longer blocks genuine de-risking trades.
  TDD: proof_v1126_risk_reducing_fee_neutral (18s, 1/1 cover)

Other fixes:
  - MAX_TRADE_SIZE_Q: 200T → MAX_POSITION_ABS_Q (100T) per spec §1.4
  - liquidate_at_oracle OI assertion now unconditional per spec §10.6 step 10
  - enforce_one_side_margin takes fee parameter for fee-neutral comparison

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add MIN_NONZERO_MM_REQ / MIN_NONZERO_IM_REQ margin floors (spec §9.1)

New RiskParams fields:
  min_nonzero_mm_req: u128 — absolute floor for maintenance margin requirement
  min_nonzero_im_req: u128 — absolute floor for initial margin requirement

MM_req = max(proportional, min_nonzero_mm_req)
IM_req = max(proportional, min_nonzero_im_req)

Prevents microscopic positions from evading margin enforcement when
proportional notional floors to zero. Also fixes mul_u128 → mul_div_floor_u128
in all margin computations to prevent overflow on large notionals.

Constructor validates: min_nonzero_mm_req < min_nonzero_im_req

TDD: proof_v1126_min_nonzero_margin_floor (4.6s, 1/1 cover)
All existing proofs pass. All test helpers updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: GC reclaims flat-dust accounts below MIN_INITIAL_DEPOSIT (spec §2.6)

garbage_collect_dust now reclaims accounts with 0 < C_i < MIN_INITIAL_DEPOSIT
(not just C_i == 0). Dust capital is swept into the insurance fund before
the slot is freed, maintaining conservation.

Previously, any account with nonzero capital was permanently skipped by GC,
violating spec §2.6 which allows reclaim_empty_account when C_i is below
the live-balance floor. This could permanently exhaust account capacity
(spec security goal 17, test properties 30/58).

TDD: proof_gc_reclaims_flat_dust_capital wrote failing test first (GC
skipped nonzero capital), then fixed impl. 1.7s, PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: fix README haircut formula to use PNL_matured_pos_tot

The haircut ratio denominator is PNL_matured_pos_tot (only warmed/released
positive PnL), not PNL_pos_tot. Per-account effective PnL uses ReleasedPos_i.
Also updates proof count to 146 across 6 files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(readme): correct haircut formula to use matured/released PnL

The haircut denominator is PNL_matured_pos_tot (not PNL_pos_tot) and
per-account effective PnL uses ReleasedPos_i = max(PNL_i, 0) - R_i,
reflecting the warmup reserve that defends against oracle manipulation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update spec version reference from v11.5 to v11.26

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(proofs): add 11 proofs for uncovered spec §12 test properties

Cover 8 previously uncovered and 3 partially covered properties:
- #3: Oracle-manipulation haircut safety (R_i excludes from h and IM)
- #23: Deposit materialization threshold (nonzero MIN_INITIAL_DEPOSIT)
- #26: Maintenance vs IM dual equity (full PnL for MM, haircutted for IM)
- #31: Missing-account safety (all instructions reject unmaterialized)
- #43: K-pair chronology correctness (settle argument ordering)
- #44: Deposit true-flat guard (no resolve_flat_negative when basis!=0)
- #49: Profit-conversion reserve preservation (R_i unchanged)
- #50: Flat-only automatic conversion (open positions skip)
- #51: Universal withdrawal dust guard (nonzero MIN_INITIAL_DEPOSIT)
- #52: Explicit open-position profit conversion (convert_released_pnl)
- #56: Exact raw IM approval (MIN_NONZERO_IM_REQ enforcement)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 3 audit findings — fee sweep PnL breach, LP GC bypass, exact IM

Proof-driven fixes for external audit findings:

1. [CRITICAL] fee_debt_sweep: remove rogue PnL-to-insurance block that
   consumed junior released PnL at face value and credited it 1:1 to
   senior insurance capital. When h < 1, this breaches V >= C_tot + I.
   Spec §7.5 mandates sweep from C_i only.

2. [MAJOR] garbage_collect_dust: remove is_lp() bypass. Empty LP accounts
   with zero capital/PnL/position must be reclaimable per spec §2.6.

3. [MAJOR] is_above_initial_margin: use exact Eq_init_raw instead of
   clamped Eq_init_net per spec §3.4 and §9.1. Negative raw equity
   must be distinguishable from zero for risk-increasing trade approval.

4. [FALSE POSITIVE] K-pair chronology: parameter names differ from spec
   but call sites are correspondingly swapped — math is correct.
   Added proof confirming longs gain when oracle rises.

4 new Kani proofs verify all fixes (162 total proofs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): 3 more findings — funding rate clamp, equity overflow, deposit materialization

- Clamp funding rate in set_funding_rate_for_next_interval per spec §5.5
  to prevent liveness lockup when out-of-range rate blocks accrue_market_to
- Return i128::MAX (not 0) on positive equity overflow in
  account_equity_maint_raw and account_equity_init_raw per spec §3.4
  "MUST fail conservatively" — prevents false liquidation
- Deposit now materializes missing accounts per spec §10.3/§2.3 via new
  materialize_at helper instead of returning AccountNotFound
- 7 new Kani proofs, 1 updated (property 31), all verified

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): prevent i128::MIN negate panic in checked_u128_mul_i128 and compute_trade_pnl

Bound negative result magnitude to i128::MAX (not i128::MAX+1) in both
helpers. The old bound allowed v = 2^127, where `v as i128` wraps to
i128::MIN and `-(i128::MIN)` panics in Rust debug/Solana BPF builds.
i128::MIN is forbidden throughout the engine so rejecting it as Overflow
is correct. Two Kani proofs verify no panic at the boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): 6 structural integrity fixes — atomicity, freelist, TVL bounds, time monotonicity

- init_in_place: fully canonicalize all state fields (safe on non-zeroed memory)
- add_user/add_lp: reorder all fallible checks before state mutations (atomic)
- materialize_at: detect and reject double-materialization (freelist integrity)
- deposit_fee_credits: enforce time monotonicity, checked arithmetic, MAX_VAULT_TVL
- top_up_insurance_fund: replace panicking add_u128 with checked arithmetic + MAX_VAULT_TVL
- 8 new Kani proofs verifying all 6 fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): strengthen 6 weak/vacuous proofs found by self-audit

- compute_trade_pnl: add sign-consistency and zero-input properties
  (old i8 proof couldn't reach 2^127 boundary — now structural)
- init_in_place: exhaustive field checks (30+ fields), full freelist
  walk, used-bitmap verification (was checking ~15 fields)
- materialize_at freelist: test actual freelist removal via deposit on
  interior slot, verify chain structure (old proof skipped materialize_at)
- deposit_rejects_below_min_initial: use min_initial_deposit=1000 (was 0,
  making proof vacuous via early return)
- funding_rate_clamped: verify exact clamped values, not just bounds
- add_user atomic: add MAX_VAULT_TVL failure path test, verify c_tot
- deposit_fee_credits time: verify all state unchanged on rejection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): enforce fee_credits <= 0 invariant + add reclaim_empty_account

- deposit_fee_credits: cap deposits at outstanding debt to enforce
  spec §2.1 invariant (fee_credits_i ∈ [-(i128::MAX), 0])
- reclaim_empty_account: new O(1) permissionless endpoint per spec §10.7,
  sweeps dust capital to insurance, forgives uncollectible fee debt
- Fix unit tests that relied on positive fee_credits (spec violation)
- 6 new Kani proofs: fee_credits cap, zero-debt no-op, reclaim basic,
  reclaim dust sweep, reclaim rejects position, reclaim rejects live capital
- Update 3 existing proofs to work with debt-capped deposit_fee_credits

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* spec

* feat: v11.31 spec compliance — zero-rate funding, bilateral OI, partial liquidation

Implements all v11.31 spec changes with 17 new Kani proofs:

Engine changes:
- Zero-rate funding core profile (§4.12): accrue_market_to no longer
  transfers funding between accounts; recompute_r_last always sets 0
- Configuration immutability (§2.2.1): remove set_funding_rate_for_next_interval
  and set_insurance_floor — parameters are init-time only
- Maintenance fees disabled (§8.2): settle_maintenance_fee_internal stamps
  slot only, no fee accumulation
- Exact bilateral OI decomposition (§5.2.2): replaces per-account delta
  approach with bilateral_oi_after for both gating and writeback
- Partial liquidation (§9.4): LiquidationPolicy enum with FullClose and
  ExactPartial variants; mandatory post-partial health check at step 14
- Unencumbered flat deposit sweep (§7.5): fee debt sweep only when flat
  AND PNL >= 0; deposit no longer calls resolve_flat_negative
- Positive conversion denominator (§7.4): assert h_den > 0 when x > 0
- top_up_insurance_fund now requires now_slot for time monotonicity (§3.1)
- Remove InsuranceFund.fee_revenue field (unused per spec)
- keeper_crank accepts (idx, Option<LiquidationPolicy>) hint tuples

Proof coverage (properties 42, 44, 46, 59, 60-70):
- 17 new Kani proofs in tests/proofs_v1131.rs
- Fix pre-existing proof_fee_debt_sweep_consumes_released_pnl (was
  incorrectly asserting sweep from released PnL; sweep uses capital)
- 27+ existing proofs verified passing with no regressions
- All 103 unit tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: proof strength audit for v11.31 proofs

Audit 17 new proofs from proofs_v1131.rs + 1 fixed proof per
audit-proof-strength.md criteria (6 criteria, 6a-6f sub-criteria).

Results: 7 STRONG, 4 WEAK, 6 UNIT TEST, 0 VACUOUS
- 4 WEAK proofs have concrete-only inputs or vacuity risk
- Priority upgrades listed for all non-STRONG proofs

Updated tally: 175 total proofs (11 INDUCTIVE, 155 STRONG, 4 WEAK, 5 UNIT TEST)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): upgrade 4 WEAK + 5 UNIT TEST proofs to STRONG per audit

All v11.31 proofs now have symbolic inputs exercising key branches:

WEAK → STRONG:
- #168 bilateral OI: symbolic i16 trade size after initial open,
  exercises close/reduce/flip paths (verified in 303s)
- #169 partial liq remainder: near-max leverage with 95%+ close,
  explicit non-vacuity assert on Ok(true) path
- #172 health check: flipped to negative test — symbolic tiny close
  proves health check rejects insufficient partials
- #174 nonflat deposit: symbolic deposit amount + fee debt

UNIT TEST → STRONG:
- #159 accrue: symbolic rate (i64) + slot delta (u16)
- #161 touch: symbolic fee_per_slot (u32) + dt (u16)
- #163 sweep guard: symbolic deposit (u32) + fee debt (u16)
- #164 sweep nonneg: symbolic initial capital + deposit (u32)
- #173 keeper r_last: symbolic rate (full i64 domain)

Also: proof_fee_debt_sweep_consumes_released_pnl (proofs_safety.rs)
upgraded to symbolic capital + debt covering both payment paths.

Final tally: 11 INDUCTIVE, 162 STRONG, 0 WEAK, 3 UNIT TEST

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): 4 external audit fixes + 8 Kani proofs

1. attach_effective_position: epoch_snap set to 0 on zero-out per §2.4
   (was incorrectly anchored to discarded side's epoch)
2. add_user/add_lp: rollback materialized_account_count on alloc_slot failure
3. is_above_maintenance_margin/is_above_initial_margin: MM_req=0 and IM_req=0
   when effective position is zero per §9.1 (was applying min_nonzero floors)
4. fee_debt_sweep: saturating_add → checked_add for defense-in-depth

False positives rejected:
- PnL sign inversion: argument order correct (k_now, k_then) = k_now - k_then
- Keeper panic trap: Solana SVM atomic abort makes Err returns safe
- close_account fee forgiveness: spec guarantee #55 explicitly protects
- set_owner: authority validation is in outer Solana program
- GC cursor / O(N) freelist: bounded design choices, not bugs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): 3 more audit fixes — keeper griefing, missing-account safety, config validation

1. keeper_crank: always use FullClose, ignore untrusted ExactPartial hints.
   A syntactically valid partial that fails post-partial health check (§9.4
   step 14) would return Err after mutating state, reverting the entire crank.
   External callers wanting partial liquidation use liquidate_at_oracle directly.

2. liquidate_at_oracle: add is_used check BEFORE touch_account_full to prevent
   market-state mutation (accrue_market_to, current_slot) on missing accounts.

3. validate_params: enforce max_accounts <= MAX_ACCOUNTS, BPS <= 10_000, and
   all existing assertions. Called from both new() and init_in_place().

False positives rejected:
- K-snapshot chronology: (k_now, k_then) = k_now - k_then, code is correct
- Fresh crank gate: intentional safety gate for margin freshness
- GC vs reclaim semantics: intentional design, GC runs after accrue

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* cleaup

* fix(audit): 4 fixes from third audit round + pre-flight partial hint validation

1. close_account: move PnL checks before fee forgiveness to prevent
   in-memory state mutation on Err path (fee-debt evasion window)

2. settle_side_effects: set epoch_snap = 0 on same-epoch truncation and
   epoch-mismatch zero-out paths per spec §2.4 canonical defaults

3. keeper_crank: restore partial hint support via stateless pre-flight
   check in validate_keeper_hint. Predicts post-partial Eq_maint_raw
   (invariant: settle_losses preserves C+PNL sum) and MM_req. Invalid
   partials fall back to FullClose for liveness. Resolves spec §11.2
   divergence from prior FullClose hardcoding.

4. validate_params: add spec §1.4 cross-parameter constraints:
   - 0 < min_nonzero_mm_req < min_nonzero_im_req <= min_initial_deposit
   - 0 < min_initial_deposit <= MAX_VAULT_TVL

Already-fixed claims rejected:
- is_above_maintenance_margin flat floor: already fixed in 3db8d07
  (lines 1745-1748 have eff==0 early return)

Test param helpers updated to satisfy new validation constraints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): 4 fixes from fourth audit round + 8 Kani proofs

- validate_keeper_hint: None hint → None (no action) per spec §11.2
- garbage_collect_dust: cursor advances by actual scan count, not max_scan
- validate_params: enforce min_liquidation_abs <= liquidation_fee_cap <= MAX_PROTOCOL_FEE_ABS
- touch_account_full: bounds + is_used hardening on public API surface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): remove crank freshness gate from withdraw/trade + GC §2.6 compliance

- Remove require_fresh_crank from withdraw and execute_trade: spec §10.4
  and §10.5 do not gate these on keeper liveness; touch_account_full
  accrues market state directly. Fixes spec §0 goal 6 violation where
  keeper downtime would freeze user funds.
- GC now skips accounts with PNL != 0 instead of absorbing the loss
  inline. Spec §2.6 requires PNL_i == 0 as a reclamation precondition;
  negative PnL must be resolved via touch_account_full → §7.3 first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): reduce internal method visibility + add insurance_floor to RiskParams

Issue 2 (public internals):
- 8 methods made fully private (fn): inc_phantom_dust_bound,
  inc_phantom_dust_bound_by, use_insurance_buffer,
  maybe_finalize_ready_reset_sides, restart_warmup_after_reserve_increase,
  require_fresh_crank, require_recent_full_sweep, add_fee_credits
- 24 methods made test-visible via test_visible! macro: private in
  production builds, pub under feature="test"/kani for proof access.
  Includes free_slot, set_pnl, set_capital, attach_effective_position,
  begin_full_drain_reset, and other internal state mutators.
- Production build exposes only true external API (entrypoints + views)

Issue 3 (insurance_floor):
- Added insurance_floor: U128 field to RiskParams
- validate_params enforces insurance_floor <= MAX_VAULT_TVL
- new() and init_in_place() use params.insurance_floor instead of
  hardcoded zero
- Deployments can now express nonzero insurance floors per spec §1.4

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(proofs): 11 proof fixes — 2 broken + 9 gap-filling Kani proofs

Fix 2 broken proofs (None→Some(FullClose) after §11.2 change):
- proof_adl_pipeline_trade_liquidate_reopen
- t11_53_keeper_crank_quiesces_after_pending_reset

Add 9 gap-filling proofs from comprehensive audit analysis:
- Gap #3: bounded_trade_conservation_with_fees (nonzero trading fees)
- Gap #4: proof_validate_hint_preflight_conservative (ExactPartial pre-flight)
- Gap #5: proof_partial_liquidation_can_succeed (80% partial close)
- Gap #6: proof_sign_flip_trade_conserves (long→short flip)
- Gap #7: proof_convert_released_pnl_conservation (symbolic conversion)
- Gap #8: proof_close_account_fee_forgiveness_bounded (fee debt forgiveness)
- Weakness #9: proof_symbolic_margin_enforcement_on_reduce (symbolic PnL)
- Weakness #11: bounded_trade_conservation_symbolic_size (symbolic size)
- Weakness #12: proof_convert_released_pnl_exercises_conversion (non-early-return)

All proofs verified with Kani (cadical solver).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): set_owner guard + stronger pre-flight proof with oracle shift

1. set_owner rejects if owner already claimed (non-zero pubkey).
   Defense-in-depth — prevents silent overwrite of existing owner.

2. proof_validate_hint_preflight_oracle_shift: stronger variant of
   gap #4 proof using symbolic oracle (900..1100) at crank time,
   exercising settle_side_effects with nonzero pnl_delta.

3. proof_set_owner_rejects_claimed: verifies the new guard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add close_account_resolved for frozen/resolved market path

First-class engine method for closing accounts when touch_account_full
would overflow (ADL state frozen, K at boundary values). Replaces the
pattern of wrapper code manually sequencing set_capital → set_pnl →
set_position_basis_q → free_slot through test_visible! internals.

The method:
- Zeros position via set_position_basis_q (proper stored_pos_count tracking)
- Settles losses from principal via settle_losses
- Absorbs remaining negative PnL via absorb_protocol_loss
- Converts positive PnL to capital with haircut (bypasses warmup)
- Forgives fee debt, returns capital, frees slot

5 unit tests + 3 Kani proofs (symbolic loss, symbolic profit, flat path)
verify conservation and correctness.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: enable maintenance fees (spec §8.2)

Replace the no-op settle_maintenance_fee_internal with actual fee
computation: fee = dt * maintenance_fee_per_slot, clamped to
MAX_PROTOCOL_FEE_ABS on overflow. Charged via charge_fee_to_insurance
(capital-first, shortfall to fee_credits debt).

Implementation:
- settle_maintenance_fee_internal: compute dt, checked_mul with clamp,
  stamp last_fee_slot before charge (prevents re-charge on retry)
- validate_params: maintenance_fee_per_slot <= MAX_PROTOCOL_FEE_ABS

Nothing else changes — the existing fee routing, equity deductions,
sweep pipeline, GC, and conservation invariants already handle fee
debt correctly.

Updated 5 existing tests (disabled→enabled semantics), added 3 new
Kani proofs (conservation with symbolic dt, fee debt accumulation,
validate_params rejection). All 6 fee-related proofs verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: stale comment + close_account_resolved stale count decrement

1. Update keeper_crank per-candidate comment: maintenance fees are now
   live, not disabled.

2. close_account_resolved: decrement stale_account_count when zeroing
   a …
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant