Skip to content

Commit 6f85a89

Browse files
aeyakovenkoclaude
andcommitted
fix: LP fee tracking + remove dead liquidation_buffer_bps
1. fees_earned_total now tracks only capital actually paid to insurance (realized revenue), not including collectible fee debt that may later be forgiven on close/reclaim. charge_fee_to_insurance returns (cash_paid, total_equity_impact) tuple. LP tracking uses cash_paid; margin enforcement uses total_equity_impact. 2. Removed liquidation_buffer_bps from RiskParams — dead parameter never read by the engine. Not in the spec. Not changed (with rationale): - #1-4 (non-atomic mutations): Solana SVM atomicity. The engine uses validate-then-mutate in critical paths (accrue_market_to, settle_side_effects, force_close_resolved) but the full-instruction atomicity relies on runtime rollback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f12a651 commit 6f85a89

5 files changed

Lines changed: 22 additions & 20 deletions

File tree

src/percolator.rs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,6 @@ pub struct RiskParams {
255255
pub max_crank_staleness_slots: u64,
256256
pub liquidation_fee_bps: u64,
257257
pub liquidation_fee_cap: U128,
258-
pub liquidation_buffer_bps: u64,
259258
pub min_liquidation_abs: U128,
260259
pub min_initial_deposit: U128,
261260
/// Absolute nonzero-position margin floors (spec §9.1)
@@ -2709,37 +2708,45 @@ impl RiskEngine {
27092708
};
27102709

27112710
// Charge fee from both accounts (spec §10.5 step 28)
2712-
let mut fee_collected_a = 0u128;
2713-
let mut fee_collected_b = 0u128;
2711+
// (cash_to_insurance, total_equity_impact) for each side
2712+
let mut fee_cash_a = 0u128;
2713+
let mut fee_cash_b = 0u128;
2714+
let mut fee_impact_a = 0u128;
2715+
let mut fee_impact_b = 0u128;
27142716
if fee > 0 {
27152717
if fee > MAX_PROTOCOL_FEE_ABS {
27162718
return Err(RiskError::Overflow);
27172719
}
2718-
fee_collected_a = self.charge_fee_to_insurance(a as usize, fee)?;
2719-
fee_collected_b = self.charge_fee_to_insurance(b as usize, fee)?;
2720+
let (cash_a, impact_a) = self.charge_fee_to_insurance(a as usize, fee)?;
2721+
let (cash_b, impact_b) = self.charge_fee_to_insurance(b as usize, fee)?;
2722+
fee_cash_a = cash_a;
2723+
fee_cash_b = cash_b;
2724+
fee_impact_a = impact_a;
2725+
fee_impact_b = impact_b;
27202726
}
27212727

2722-
// Track LP fees: use actual collected amount, not nominal fee.
2723-
// LP a earns from counterparty b's fee payment, and vice versa.
2728+
// Track LP fees: use capital actually paid to insurance (realized revenue),
2729+
// not including collectible debt that may later be forgiven.
27242730
if self.accounts[a as usize].is_lp() {
27252731
self.accounts[a as usize].fees_earned_total = U128::new(
2726-
add_u128(self.accounts[a as usize].fees_earned_total.get(), fee_collected_b)
2732+
add_u128(self.accounts[a as usize].fees_earned_total.get(), fee_cash_b)
27272733
);
27282734
}
27292735
if self.accounts[b as usize].is_lp() {
27302736
self.accounts[b as usize].fees_earned_total = U128::new(
2731-
add_u128(self.accounts[b as usize].fees_earned_total.get(), fee_collected_a)
2737+
add_u128(self.accounts[b as usize].fees_earned_total.get(), fee_cash_a)
27322738
);
27332739
}
27342740

27352741
// Step 29: post-trade margin enforcement (spec §10.5)
27362742
// Use actual collected fee per side for fee-neutral comparison,
27372743
// not the nominal fee (which may exceed what was actually applied
27382744
// when charge_fee_to_insurance caps at collectible headroom).
2745+
// Use total equity impact for fee-neutral margin comparison
27392746
self.enforce_post_trade_margin(
27402747
a as usize, b as usize, oracle_price,
27412748
&old_eff_a, &new_eff_a, &old_eff_b, &new_eff_b,
2742-
buffer_pre_a, buffer_pre_b, fee_collected_a, fee_collected_b,
2749+
buffer_pre_a, buffer_pre_b, fee_impact_a, fee_impact_b,
27432750
)?;
27442751

27452752
// Steps 16-17: end-of-instruction resets
@@ -2756,9 +2763,10 @@ impl RiskEngine {
27562763
}
27572764

27582765
/// Charge fee per spec §8.1 — route shortfall through fee_credits instead of PNL.
2759-
/// Returns the amount actually applied (capital paid + collectible debt recorded).
2766+
/// Returns (capital_paid_to_insurance, total_equity_impact).
2767+
/// capital_paid is realized revenue; total includes collectible debt.
27602768
/// Any excess beyond collectible headroom is silently dropped.
2761-
fn charge_fee_to_insurance(&mut self, idx: usize, fee: u128) -> Result<u128> {
2769+
fn charge_fee_to_insurance(&mut self, idx: usize, fee: u128) -> Result<(u128, u128)> {
27622770
if fee > MAX_PROTOCOL_FEE_ABS {
27632771
return Err(RiskError::Overflow);
27642772
}
@@ -2787,9 +2795,9 @@ impl RiskEngine {
27872795
self.accounts[idx].fee_credits = I128::new(new_fc);
27882796
}
27892797
// Any excess beyond collectible headroom is silently dropped
2790-
Ok(fee_paid + collectible)
2798+
Ok((fee_paid, fee_paid + collectible))
27912799
} else {
2792-
Ok(fee_paid)
2800+
Ok((fee_paid, fee_paid))
27932801
}
27942802
}
27952803

tests/amm_tests.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ fn default_params() -> RiskParams {
1919
max_crank_staleness_slots: u64::MAX,
2020
liquidation_fee_bps: 50,
2121
liquidation_fee_cap: U128::new(100_000),
22-
liquidation_buffer_bps: 100,
2322
min_liquidation_abs: U128::new(0),
2423
min_initial_deposit: U128::new(2),
2524
min_nonzero_mm_req: 1,

tests/common/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ pub fn zero_fee_params() -> RiskParams {
114114
max_crank_staleness_slots: u64::MAX,
115115
liquidation_fee_bps: 0,
116116
liquidation_fee_cap: U128::ZERO,
117-
liquidation_buffer_bps: 50,
118117
min_liquidation_abs: U128::ZERO,
119118
min_initial_deposit: U128::new(2),
120119
min_nonzero_mm_req: 1,
@@ -135,7 +134,6 @@ pub fn default_params() -> RiskParams {
135134
max_crank_staleness_slots: 1000,
136135
liquidation_fee_bps: 100,
137136
liquidation_fee_cap: U128::new(1_000_000),
138-
liquidation_buffer_bps: 50,
139137
min_liquidation_abs: U128::new(0),
140138
min_initial_deposit: U128::new(1000),
141139
min_nonzero_mm_req: 1,

tests/fuzzing.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ fn params_regime_a() -> RiskParams {
158158
max_crank_staleness_slots: u64::MAX,
159159
liquidation_fee_bps: 50,
160160
liquidation_fee_cap: U128::new(100_000),
161-
liquidation_buffer_bps: 100,
162161
min_liquidation_abs: U128::new(100_000),
163162
min_initial_deposit: U128::new(2),
164163
min_nonzero_mm_req: 1,
@@ -180,7 +179,6 @@ fn params_regime_b() -> RiskParams {
180179
max_crank_staleness_slots: u64::MAX,
181180
liquidation_fee_bps: 50,
182181
liquidation_fee_cap: U128::new(100_000),
183-
liquidation_buffer_bps: 100,
184182
min_liquidation_abs: U128::new(100_000),
185183
min_initial_deposit: U128::new(1000),
186184
min_nonzero_mm_req: 1,

tests/unit_tests.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ fn default_params() -> RiskParams {
1919
max_crank_staleness_slots: 1000,
2020
liquidation_fee_bps: 100,
2121
liquidation_fee_cap: U128::new(1_000_000),
22-
liquidation_buffer_bps: 50,
2322
min_liquidation_abs: U128::new(0),
2423
min_initial_deposit: U128::new(1000),
2524
min_nonzero_mm_req: 1,

0 commit comments

Comments
 (0)