@@ -6,6 +6,8 @@ use num_traits::One;
66use primitive_types:: U256 ;
77use sp_arithmetic:: Permill ;
88
9+ const PERMILL_SCALE : u128 = 1_000_000 ;
10+
911/// Calculate the slip fee *amount* at full U256 precision, avoiding Permill truncation.
1012///
1113/// Computes: `min(|cumulative| / (Q₀ + cumulative), max_slip_fee) × base_amount`
@@ -38,38 +40,73 @@ pub fn calculate_slip_fee_amount(
3840
3941 let abs_cumulative = cumulative. abs ( ) ;
4042
41- // Check if rate exceeds max_slip_fee: |cum| * 1_000_000 / denom > max_parts?
42- // Safe: u128 * 1_000_000 fits in U256
43- let rate_millionths = U256 :: from ( abs_cumulative)
44- . saturating_mul ( U256 :: from ( 1_000_000u64 ) )
45- . checked_div ( U256 :: from ( denom) ) ?;
46- let max_parts = max_slip_fee. deconstruct ( ) as u64 ;
47-
48- if rate_millionths > U256 :: from ( max_parts) {
49- // Capped: use Permill for the capped amount
43+ if slip_rate_exceeds_cap ( abs_cumulative, denom, max_slip_fee) ? {
5044 Some ( max_slip_fee. mul_floor ( base_amount) )
5145 } else {
52- // Full precision: |cumulative| * base_amount / denom
5346 let amount_hp = U256 :: from ( abs_cumulative)
5447 . checked_mul ( U256 :: from ( base_amount) ) ?
5548 . checked_div ( U256 :: from ( denom) ) ?;
5649 to_balance ! ( amount_hp) . ok ( )
5750 }
5851}
5952
60- /// Invert buy-side slip fee: given D_net (hub asset actually entering buy pool),
61- /// find D_gross (hub asset before buy-side slip deduction).
62- ///
63- /// Forward formula: `D_net = D_gross - |cum| * D_gross / (L + cum)` where `cum = C + D_gross`.
53+ /// Returns `true` iff `|cumulative| * 10^6 > max_parts * denom`,
54+ /// the same threshold `calculate_slip_fee_amount` uses to switch to the capped formula.
55+ fn slip_rate_exceeds_cap ( abs_cumulative : Balance , denom : Balance , max_slip_fee : Permill ) -> Option < bool > {
56+ if denom == 0 || abs_cumulative == 0 {
57+ return Some ( false ) ;
58+ }
59+ let max_parts = max_slip_fee. deconstruct ( ) as u128 ;
60+ let lhs = U256 :: from ( abs_cumulative) . checked_mul ( U256 :: from ( PERMILL_SCALE ) ) ?;
61+ let rhs = U256 :: from ( max_parts) . checked_mul ( U256 :: from ( denom) ) ?;
62+ Some ( lhs > rhs)
63+ }
64+
65+ fn slip_rate_exceeds_cap_from_state (
66+ hub_reserve_at_block_start : Balance ,
67+ prior_delta : SignedBalance ,
68+ delta_q : SignedBalance ,
69+ max_slip_fee : Permill ,
70+ ) -> Option < bool > {
71+ let cumulative = prior_delta. checked_add ( delta_q) ?;
72+ let denom = cumulative. add_to_unsigned ( hub_reserve_at_block_start) ?;
73+ slip_rate_exceeds_cap ( cumulative. abs ( ) , denom, max_slip_fee)
74+ }
75+
76+ /// Invert buy-side slip fee: given `D_net` (hub asset entering buy pool after slip),
77+ /// find `D_gross` (before slip).
6478///
65- /// - `d_net` — hub asset entering the buy pool after slip deduction
66- /// - `l` — hub reserve at block start (Q₀)
67- /// - `c` — cumulative signed hub asset delta before this trade
79+ /// Forward: `D_net = D_gross - slip(D_gross)`, with
80+ /// `slip = min(|cum|/(L+cum), max_slip_fee) * D_gross`, `cum = C + D_gross`.
6881///
69- /// Two cases based on the sign of cumulative after the trade:
70- /// - Case 1 (cum >= 0): `D_gross = D_net * (L+C) / (L - D_net)` (linear)
71- /// - Case 2 (C < 0, D_gross < |C|): quadratic inversion
72- pub ( crate ) fn invert_buy_side_slip ( d_net : Balance , l : Balance , c : SignedBalance ) -> Option < Balance > {
82+ /// Uncapped: linear when `cum >= 0`, quadratic when `C < 0 && D_gross < |C|`.
83+ /// Capped: `D_gross = floor(D_net * 10^6 / (10^6 - max_parts))`.
84+ pub ( crate ) fn invert_buy_side_slip (
85+ d_net : Balance ,
86+ l : Balance ,
87+ c : SignedBalance ,
88+ max_slip_fee : Permill ,
89+ ) -> Option < Balance > {
90+ // Uncapped inverse may have no real root for very large trades — in that case
91+ // the cap is necessarily binding and we fall through to the capped formula.
92+ if let Some ( d_gross_uncapped) = invert_buy_side_slip_uncapped ( d_net, l, c) {
93+ if !slip_rate_exceeds_cap_from_state ( l, c, SignedBalance :: Positive ( d_gross_uncapped) , max_slip_fee) ? {
94+ return Some ( d_gross_uncapped) ;
95+ }
96+ }
97+
98+ let max_parts = max_slip_fee. deconstruct ( ) as u128 ;
99+ let one_minus_max = PERMILL_SCALE . checked_sub ( max_parts) ?;
100+ if one_minus_max == 0 {
101+ return None ;
102+ }
103+ let d_gross_hp = U256 :: from ( d_net)
104+ . checked_mul ( U256 :: from ( PERMILL_SCALE ) ) ?
105+ . checked_div ( U256 :: from ( one_minus_max) ) ?;
106+ to_balance ! ( d_gross_hp) . ok ( )
107+ }
108+
109+ fn invert_buy_side_slip_uncapped ( d_net : Balance , l : Balance , c : SignedBalance ) -> Option < Balance > {
73110 let s_buy = c. add_to_unsigned ( l) ?; // L + C
74111 if s_buy == 0 {
75112 return None ;
@@ -132,25 +169,44 @@ pub(crate) fn invert_buy_side_slip(d_net: Balance, l: Balance, c: SignedBalance)
132169 d_gross. checked_add ( Balance :: one ( ) )
133170}
134171
135- /// Invert sell-side fees (protocol_fee + sell-side slip) to find `delta_hub_reserve_in`
136- /// given D_gross (hub asset after sell-side deductions).
172+ /// Invert sell-side fees (protocol_fee + sell-side slip) to find `u = delta_hub_reserve_in`
173+ /// given ` D_gross` ( after sell-side deductions).
137174///
138- /// Forward formula : `D_gross = u*(1- pf) - slip_fee (u)` where `u = delta_hub_reserve_in`,
139- /// `pf = protocol_fee`, and `slip_fee = |C - u| * u / (L + C - u) `.
175+ /// Forward: `D_gross = u*(1 - pf) - slip (u)`, with
176+ /// `slip(u) = min(|C-u|/(L+C-u), max_slip_fee) * u `.
140177///
141- /// - `d_gross` — hub asset remaining after protocol fee and sell-side slip
142- /// - `protocol_fee` — protocol fee rate
143- /// - `l` — hub reserve at block start (Q₀) for the sell pool
144- /// - `c` — cumulative signed hub asset delta before this trade (for the sell pool)
145- ///
146- /// Two cases based on the sign of cumulative = C - u (hub asset outflow is negative):
147- /// - Case A (u > C, cumulative < 0): `(k+1)*u² - (kS + C + D)*u + DS = 0`
148- /// - Case B (u <= C, C > 0, opposing flow): `pf*u² + (D + kS - C)*u - DS = 0`
178+ /// Uncapped quadratic with two cases by sign of `cum = C - u`.
179+ /// Capped: `u = floor(D_gross * 10^6 / (10^6 - pf_parts - max_parts))`.
149180pub ( crate ) fn invert_sell_side_fees (
150181 d_gross : Balance ,
151182 protocol_fee : Permill ,
152183 l : Balance ,
153184 c : SignedBalance ,
185+ max_slip_fee : Permill ,
186+ ) -> Option < Balance > {
187+ if let Some ( u_uncapped) = invert_sell_side_fees_uncapped ( d_gross, protocol_fee, l, c) {
188+ if !slip_rate_exceeds_cap_from_state ( l, c, SignedBalance :: Negative ( u_uncapped) , max_slip_fee) ? {
189+ return Some ( u_uncapped) ;
190+ }
191+ }
192+
193+ let pf_parts = protocol_fee. deconstruct ( ) as u128 ;
194+ let max_parts = max_slip_fee. deconstruct ( ) as u128 ;
195+ let denom_parts = PERMILL_SCALE . checked_sub ( pf_parts) ?. checked_sub ( max_parts) ?;
196+ if denom_parts == 0 {
197+ return None ;
198+ }
199+ let u_hp = U256 :: from ( d_gross)
200+ . checked_mul ( U256 :: from ( PERMILL_SCALE ) ) ?
201+ . checked_div ( U256 :: from ( denom_parts) ) ?;
202+ to_balance ! ( u_hp) . ok ( )
203+ }
204+
205+ fn invert_sell_side_fees_uncapped (
206+ d_gross : Balance ,
207+ protocol_fee : Permill ,
208+ l : Balance ,
209+ c : SignedBalance ,
154210) -> Option < Balance > {
155211 let abs_c = c. abs ( ) ;
156212 let c_is_positive = c. is_positive ( ) ;
@@ -163,8 +219,8 @@ pub(crate) fn invert_sell_side_fees(
163219 } ;
164220
165221 let pf_parts = protocol_fee. deconstruct ( ) as u128 ;
166- let k_parts = 1_000_000u128 - pf_parts;
167- let scale = U256 :: from ( 1_000_000u64 ) ;
222+ let k_parts = PERMILL_SCALE . checked_sub ( pf_parts) ? ;
223+ let scale = U256 :: from ( PERMILL_SCALE ) ;
168224
169225 // Try Case B first when C > 0 (opposing flow, u <= C)
170226 if c_is_positive {
@@ -237,7 +293,7 @@ pub(crate) fn invert_sell_side_fees(
237293
238294 // Case A: (k+1)u² - (kS + C + D)u + DS = 0
239295 // Applies when C <= 0 (always) or when C > 0 but Case B yielded u > C.
240- let a_u256 = U256 :: from ( k_parts + 1_000_000 ) ;
296+ let a_u256 = U256 :: from ( k_parts. checked_add ( PERMILL_SCALE ) ? ) ;
241297 // Safe: u128 * u128 fits in U256
242298 let ks = U256 :: from ( k_parts) . saturating_mul ( U256 :: from ( s) ) ;
243299 let d_scaled = U256 :: from ( d_gross) . saturating_mul ( scale) ;
0 commit comments