defituna_core/quote/
tuna_spot_position.rs

1#![allow(clippy::collapsible_else_if)]
2#![allow(clippy::too_many_arguments)]
3
4use crate::{CoreError, HUNDRED_PERCENT, INVALID_ARGUMENTS, TOKEN_A, TOKEN_B};
5use fusionamm_core::{
6    sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_get_max_amount_with_slippage_tolerance,
7    try_get_min_amount_with_slippage_tolerance, try_mul_div, FusionPoolFacade, TickArrays,
8};
9use libm::{ceil, round};
10
11pub const DEFAULT_SLIPPAGE_TOLERANCE_BPS: u16 = 100;
12
13#[cfg(feature = "wasm")]
14use fusionamm_macros::wasm_expose;
15
16#[cfg_attr(feature = "wasm", wasm_expose)]
17pub struct IncreaseSpotPositionQuoteResult {
18    /** Required collateral amount */
19    pub collateral: u64,
20    /** Required amount to borrow */
21    pub borrow: u64,
22    /** Estimated position size in the position token. */
23    pub estimated_amount: u64,
24    /** Swap input amount. */
25    pub swap_input_amount: u64,
26    /** Minimum swap output amount according to the provided slippage. */
27    pub min_swap_output_amount: u64,
28    /** Protocol fee in token A */
29    pub protocol_fee_a: u64,
30    /** Protocol fee in token B */
31    pub protocol_fee_b: u64,
32    /** Price impact in percents */
33    pub price_impact: f64,
34}
35
36/// Spot position increase quote
37///
38/// # Parameters
39/// - `increase_amount`: Position total size in the collateral_token.
40/// - `collateral_token`: Collateral token.
41/// - `position_token`: Token of the position.
42/// - `leverage`: Leverage (1.0 or higher).
43/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
44/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
45/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
46/// - `fusion_pool`: Fusion pool.
47/// - `tick_arrays`: Five tick arrays around the current pool price.
48///
49/// # Returns
50/// - `IncreaseSpotPositionQuoteResult`: quote result
51#[cfg_attr(feature = "wasm", wasm_expose)]
52pub fn get_increase_spot_position_quote(
53    increase_amount: u64,
54    collateral_token: u8,
55    position_token: u8,
56    leverage: f64,
57    slippage_tolerance_bps: Option<u16>,
58    protocol_fee_rate: u32,
59    protocol_fee_rate_on_collateral: u32,
60    fusion_pool: FusionPoolFacade,
61    tick_arrays: TickArrays,
62) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
63    if collateral_token > TOKEN_B || position_token > TOKEN_B {
64        return Err(INVALID_ARGUMENTS.into());
65    }
66
67    if leverage < 1.0 {
68        return Err(INVALID_ARGUMENTS.into());
69    }
70
71    if protocol_fee_rate >= HUNDRED_PERCENT {
72        return Err(INVALID_ARGUMENTS.into());
73    }
74
75    if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
76        return Err(INVALID_ARGUMENTS.into());
77    }
78
79    let borrow: u64;
80    let mut collateral: u64;
81    let mut estimated_amount: u64 = 0;
82    let mut swap_input_amount: u64;
83    let mut next_sqrt_price = fusion_pool.sqrt_price;
84    let mut min_swap_output_amount: u64 = 0;
85
86    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
87    let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
88
89    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
90    let swap_input_token_is_a = borrowed_token == TOKEN_A;
91
92    if borrowed_token == collateral_token {
93        borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
94        collateral = increase_amount - apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
95        collateral = reverse_apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
96        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
97
98        swap_input_amount = collateral + borrow;
99    } else {
100        let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
101        let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
102
103        borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
104
105        let borrow_in_position_token_with_fees_applied =
106            apply_swap_fee(apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
107
108        collateral = increase_amount - borrow_in_position_token_with_fees_applied;
109        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
110
111        swap_input_amount = borrow;
112    }
113
114    let (protocol_fee_a, protocol_fee_b) =
115        compute_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
116
117    swap_input_amount -= if swap_input_token_is_a { protocol_fee_a } else { protocol_fee_b };
118
119    if position_token == collateral_token {
120        estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee_a } else { protocol_fee_b };
121    }
122
123    if swap_input_amount > 0 {
124        let swap = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, fusion_pool, tick_arrays, None, None)?;
125        estimated_amount += swap.token_est_out;
126        next_sqrt_price = swap.next_sqrt_price;
127        min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
128    }
129
130    let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
131    let price_impact = (new_price / price - 1.0).abs() * 100.0;
132
133    Ok(IncreaseSpotPositionQuoteResult {
134        collateral,
135        borrow,
136        estimated_amount,
137        swap_input_amount,
138        min_swap_output_amount,
139        protocol_fee_a,
140        protocol_fee_b,
141        price_impact,
142    })
143}
144
145#[cfg_attr(feature = "wasm", wasm_expose)]
146pub struct DecreaseSpotPositionQuoteResult {
147    /** Position decrease percentage */
148    pub decrease_percent: u32,
149    /** Collateral token of the new position */
150    pub collateral_token: u8,
151    /** Token of the new position */
152    pub position_token: u8,
153    /** Required additional collateral amount */
154    pub collateral: u64,
155    /** Required amount to borrow */
156    pub borrow: u64,
157    /** The maximum acceptable swap input amount for position decrease according to the provided slippage
158     * (if collateral_token == position_token) OR the minimum swap output amount (if collateral_token != position_token).
159     */
160    pub decrease_acceptable_swap_amount: u64,
161    /** The minimum swap output amount for position increase according to the provided slippage. */
162    pub increase_min_swap_output_amount: u64,
163    /** Estimated total amount of the new position */
164    pub estimated_amount: u64,
165    /** Protocol fee in token A */
166    pub protocol_fee_a: u64,
167    /** Protocol fee in token B */
168    pub protocol_fee_b: u64,
169    /** Price impact in percents */
170    pub price_impact: f64,
171}
172
173/// Spot position decrease quote
174///
175/// # Parameters
176/// - `decrease_amount`: Position total decrease size in the collateral_token.
177/// - `collateral_token`: Collateral token.
178/// - `leverage`: Leverage (1.0 or higher).
179/// - `reduce_only`: Only allow reducing the existing position.
180/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
181/// - `position_token`: Token of the existing position.
182/// - `position_amount`: Existing position amount in the position_token.
183/// - `position_debt`: Existing position debt in the token opposite to the position_token.
184/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
185/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
186/// - `fusion_pool`: Fusion pool.
187/// - `tick_arrays`: Five tick arrays around the current pool price.
188///
189/// # Returns
190/// - `DecreaseSpotPositionQuoteResult`: quote result
191#[cfg_attr(feature = "wasm", wasm_expose)]
192pub fn get_decrease_spot_position_quote(
193    decrease_amount: u64,
194    collateral_token: u8,
195    leverage: f64,
196    reduce_only: bool,
197    slippage_tolerance_bps: Option<u16>,
198    position_token: u8,
199    position_amount: u64,
200    position_debt: u64,
201    protocol_fee_rate: u32,
202    protocol_fee_rate_on_collateral: u32,
203    fusion_pool: FusionPoolFacade,
204    tick_arrays: TickArrays,
205) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
206    if collateral_token > TOKEN_B || position_token > TOKEN_B {
207        return Err(INVALID_ARGUMENTS.into());
208    }
209
210    if leverage < 1.0 {
211        return Err(INVALID_ARGUMENTS.into());
212    }
213
214    if protocol_fee_rate >= HUNDRED_PERCENT {
215        return Err(INVALID_ARGUMENTS.into());
216    }
217
218    if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
219        return Err(INVALID_ARGUMENTS.into());
220    }
221
222    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
223    let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
224    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
225    let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
226
227    let mut collateral = 0;
228    let mut borrow = 0;
229    let estimated_amount: u64;
230    let mut next_sqrt_price = fusion_pool.sqrt_price;
231    let mut new_position_token = position_token;
232    let decrease_percent: u32;
233    let mut increase_swap_output_amount: u64 = 0;
234    let mut decrease_acceptable_swap_amount: u64 = 0;
235
236    let mut decrease_amount_in_position_token = if collateral_token == position_token {
237        decrease_amount
238    } else {
239        round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
240    };
241
242    if reduce_only && decrease_amount_in_position_token > position_amount {
243        decrease_amount_in_position_token = position_amount;
244    }
245
246    decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
247
248    if decrease_amount_in_position_token <= position_amount {
249        estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
250
251        if collateral_token == position_token {
252            if position_debt > 0 {
253                let amount_out = position_debt * decrease_percent as u64 / HUNDRED_PERCENT as u64;
254                let swap = swap_quote_by_output_token(amount_out, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
255                next_sqrt_price = swap.next_sqrt_price;
256                decrease_acceptable_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_tolerance_bps)?;
257            }
258        } else {
259            let amount_in = position_amount - estimated_amount;
260            let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
261            next_sqrt_price = swap.next_sqrt_price;
262            decrease_acceptable_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
263        }
264    } else {
265        new_position_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
266        let increase_amount = decrease_amount_in_position_token - position_amount;
267
268        if position_token == collateral_token {
269            // Example:
270            // collateral_token: A
271            // position_token: A
272            // position_debt: B
273            // new_position_token: B
274            // newBorrowedToken: A
275
276            // A
277            borrow = ((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
278            let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
279
280            collateral = increase_amount - borrow_with_fees_applied;
281
282            // Decrease swap: A->B
283            let mut amount_in = 0;
284            if position_debt > 0 {
285                let swap =
286                    swap_quote_by_output_token(position_debt, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
287                amount_in = swap.token_est_in;
288                decrease_acceptable_swap_amount = try_get_max_amount_with_slippage_tolerance(amount_in, slippage_tolerance_bps)?;
289            }
290
291            // Decrease+Increase swap: A->B
292            amount_in += collateral + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
293            if amount_in > 0 {
294                let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
295                next_sqrt_price = swap.next_sqrt_price;
296                increase_swap_output_amount = swap.token_est_out.saturating_sub(position_debt);
297            }
298
299            // B
300            estimated_amount = increase_swap_output_amount;
301        } else {
302            // Example:
303            // collateral_token: B
304            // position_token: A
305            // new_position_token: B
306            // newBorrowedToken: A
307
308            // Decrease swap A->B
309            let swap = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
310            let decrease_amount_out = swap.token_est_out;
311            decrease_acceptable_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
312
313            borrow = ((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
314            let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
315
316            collateral = increase_amount - borrow_with_fees_applied;
317            collateral = ceil(collateral as f64 * position_to_borrowed_token_price) as u64;
318
319            // Decrease+Increase swap A->B
320            let amount_in = position_amount + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
321            if amount_in > 0 {
322                let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
323                next_sqrt_price = swap.next_sqrt_price;
324                increase_swap_output_amount = swap.token_est_out.saturating_sub(decrease_amount_out);
325            }
326
327            // B
328            estimated_amount = increase_swap_output_amount + collateral;
329        }
330
331        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
332    }
333
334    let (protocol_fee_a, protocol_fee_b) =
335        compute_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
336
337    let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
338    let price_impact = (new_price / price - 1.0).abs() * 100.0;
339
340    let increase_min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(increase_swap_output_amount, slippage_tolerance_bps)?;
341
342    Ok(DecreaseSpotPositionQuoteResult {
343        decrease_percent,
344        collateral_token,
345        position_token: new_position_token,
346        collateral,
347        borrow,
348        decrease_acceptable_swap_amount,
349        increase_min_swap_output_amount,
350        estimated_amount,
351        protocol_fee_a,
352        protocol_fee_b,
353        price_impact,
354    })
355}
356
357/// Returns the liquidation price
358///
359/// # Parameters
360/// - `position_token`: Token of the position
361/// - `amount`: Position total size (decimal)
362/// - `debt`: Position total debt (decimal)
363/// - `liquidation_threshold`: Liquidation threshold of the market (decimal)
364///
365/// # Returns
366/// - `f64`: Decimal liquidation price
367#[cfg_attr(feature = "wasm", wasm_expose)]
368pub fn get_liquidation_price(position_token: u8, amount: f64, debt: f64, liquidation_threshold: f64) -> Result<f64, CoreError> {
369    if debt < 0.0 || amount < 0.0 {
370        return Err(INVALID_ARGUMENTS);
371    }
372
373    if liquidation_threshold <= 0.0 || liquidation_threshold >= 1.0 {
374        return Err(INVALID_ARGUMENTS);
375    }
376
377    if debt == 0.0 || amount == 0.0 {
378        return Ok(0.0);
379    }
380
381    if position_token == TOKEN_A {
382        Ok(debt / (amount * liquidation_threshold))
383    } else {
384        Ok((amount * liquidation_threshold) / debt)
385    }
386}
387
388/// Calculates the maximum tradable amount in the collateral token.
389///
390/// # Parameters
391/// - `collateral_token`: Collateral token.
392/// - `available_balance`: Available wallet balance in the collateral_token.
393/// - `leverage`: Leverage (1.0 or higher).
394/// - `new_position_token`: Token of the new position.
395/// - `position_token`: Token of the existing position. Should be set to new_position_token if position_amount is zero.
396/// - `position_amount`: Existing position amount in the position_token.
397/// - `position_debt`: Existing position debt in the token opposite to the position_token.
398/// - `reduce_only`: Only allow reducing the existing position.///
399/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
400/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
401/// - `fusion_pool`: Fusion pool.
402/// - `tick_arrays`: Five tick arrays around the current pool price.
403///
404/// # Returns
405/// - `u64`: the maximum tradable amount
406#[cfg_attr(feature = "wasm", wasm_expose)]
407pub fn get_tradable_amount(
408    collateral_token: u8,
409    available_balance: u64,
410    leverage: f64,
411    new_position_token: u8,
412    reduce_only: bool,
413    position_token: u8,
414    position_amount: u64,
415    position_debt: u64,
416    protocol_fee_rate: u32,
417    protocol_fee_rate_on_collateral: u32,
418    fusion_pool: FusionPoolFacade,
419    tick_arrays: TickArrays,
420) -> Result<u64, CoreError> {
421    if collateral_token > TOKEN_B || position_token > TOKEN_B {
422        return Err(INVALID_ARGUMENTS.into());
423    }
424
425    if leverage < 1.0 {
426        return Err(INVALID_ARGUMENTS.into());
427    }
428
429    if protocol_fee_rate >= HUNDRED_PERCENT {
430        return Err(INVALID_ARGUMENTS.into());
431    }
432
433    if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
434        return Err(INVALID_ARGUMENTS.into());
435    }
436
437    if position_amount == 0 && new_position_token != position_token {
438        return Err(INVALID_ARGUMENTS.into());
439    }
440
441    // T = Câ‹…Fcâ‹…Fs + Bâ‹…Fbâ‹…Fs, where: Fc/Fb/Fs - collateral/borrow/swap fee multiplier
442    // B = Tâ‹…(L - 1) / L
443    // => T = Câ‹…Fcâ‹…Fs / (1 - Fbâ‹…Fsâ‹…(L - 1) / L)
444    let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
445        let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
446        if collateral_token != new_position_token {
447            collateral = apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
448        }
449
450        let fee_multiplier = (1.0 - protocol_fee_rate as f64 / HUNDRED_PERCENT as f64) * (1.0 - fusion_pool.fee_rate as f64 / 1_000_000.0);
451        let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
452        Ok(total)
453    };
454
455    let available_to_trade = if new_position_token == position_token {
456        add_leverage(available_balance)?
457    } else {
458        let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
459        let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
460
461        if reduce_only {
462            if collateral_token == position_token {
463                position_amount
464            } else {
465                round(position_amount as f64 * position_to_opposite_token_price) as u64
466            }
467        } else {
468            let position_amount_in_collateral_token = if collateral_token == position_token {
469                position_amount
470            } else {
471                round(position_amount as f64 * position_to_opposite_token_price) as u64
472            };
473
474            let position_collateral = if collateral_token == position_token {
475                let swap_in = if position_debt > 0 {
476                    swap_quote_by_output_token(position_debt, position_token == TOKEN_B, 0, fusion_pool, tick_arrays, None, None)?.token_est_in
477                } else {
478                    0
479                };
480                position_amount - swap_in
481            } else {
482                if position_amount > 0 {
483                    let swap_quote = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
484                    swap_quote.token_est_out - position_debt
485                } else {
486                    0
487                }
488            };
489
490            // Add the refunded collateral to the available balance
491            position_amount_in_collateral_token + add_leverage(available_balance + position_collateral)?
492        }
493    };
494
495    Ok(available_to_trade)
496}
497
498pub fn apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u32, round_up: bool) -> Result<u64, CoreError> {
499    try_mul_div(amount, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, HUNDRED_PERCENT as u128, round_up)
500}
501
502pub fn reverse_apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u32, round_up: bool) -> Result<u64, CoreError> {
503    try_mul_div(amount, HUNDRED_PERCENT as u128, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, round_up)
504}
505
506pub fn apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
507    try_mul_div(amount, 1_000_000 - fee_rate as u128, 1_000_000, round_up)
508}
509
510pub fn reverse_apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
511    try_mul_div(amount, 1_000_000, 1_000_000 - fee_rate as u128, round_up)
512}
513
514pub fn compute_tuna_protocol_fee(
515    collateral_token: u8,
516    borrowed_token: u8,
517    collateral: u64,
518    borrow: u64,
519    protocol_fee_rate_on_collateral: u32,
520    protocol_fee_rate: u32,
521) -> (u64, u64) {
522    let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
523    let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
524    let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
525    let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
526
527    let protocol_fee_a = ((collateral_a as u128 * protocol_fee_rate_on_collateral as u128 + borrow_a as u128 * protocol_fee_rate as u128)
528        / HUNDRED_PERCENT as u128) as u64;
529    let protocol_fee_b = ((collateral_b as u128 * protocol_fee_rate_on_collateral as u128 + borrow_b as u128 * protocol_fee_rate as u128)
530        / HUNDRED_PERCENT as u128) as u64;
531
532    (protocol_fee_a, protocol_fee_b)
533}
534
535#[cfg(all(test, not(feature = "wasm")))]
536mod tests {
537    use super::*;
538    use crate::assert_approx_eq;
539    use fusionamm_core::{
540        get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
541    };
542
543    fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
544        let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
545        FusionPoolFacade {
546            tick_current_index,
547            fee_rate: 3000,
548            liquidity: 10000000000000,
549            sqrt_price,
550            tick_spacing: 2,
551            ..FusionPoolFacade::default()
552        }
553    }
554
555    fn test_tick(liquidity_net: i128) -> TickFacade {
556        TickFacade {
557            initialized: true,
558            liquidity_net,
559            ..TickFacade::default()
560        }
561    }
562
563    fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
564        TickArrayFacade {
565            start_tick_index,
566            ticks: [test_tick(0); TICK_ARRAY_SIZE],
567        }
568    }
569
570    fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
571        let tick_spacing = fusion_pool.tick_spacing;
572        let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
573        let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
574
575        [
576            test_tick_array(tick_array_start_index),
577            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
578            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
579            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
580            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
581        ]
582        .into()
583    }
584
585    #[test]
586    fn test_get_liquidation_price() {
587        assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 0.0, 0.85), Ok(0.0));
588        assert_eq!(get_liquidation_price(TOKEN_A, 0.0, 5.0, 0.85), Ok(0.0));
589        assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 800.0, 0.85), Ok(188.23529411764707));
590        assert_eq!(get_liquidation_price(TOKEN_B, 1000.0, 4.0, 0.85), Ok(212.5));
591    }
592
593    #[test]
594    fn increase_long_position_providing_token_a() {
595        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
596        let fusion_pool = test_fusion_pool(sqrt_price);
597
598        let quote = get_increase_spot_position_quote(
599            5_000_000_000,
600            TOKEN_A,
601            TOKEN_A,
602            5.0,
603            Some(0),
604            HUNDRED_PERCENT / 100,
605            HUNDRED_PERCENT / 200,
606            fusion_pool,
607            test_tick_arrays(fusion_pool),
608        )
609        .unwrap();
610
611        assert_eq!(quote.collateral, 1057165829);
612        assert_eq!(quote.borrow, 800000000);
613        assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
614        assert_eq!(quote.estimated_amount, 4_999_303_011);
615        assert_eq!(quote.protocol_fee_a, 5285829);
616        assert_eq!(quote.protocol_fee_b, 8000000);
617        assert_eq!(quote.price_impact, 0.03531617625702754);
618        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 * 1000.0) / 200.0), 5.0, 0.1);
619    }
620
621    #[test]
622    fn increase_long_position_providing_token_b() {
623        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
624        let fusion_pool = test_fusion_pool(sqrt_price);
625
626        let quote = get_increase_spot_position_quote(
627            5000_000_000,
628            TOKEN_B,
629            TOKEN_A,
630            5.0,
631            Some(0),
632            HUNDRED_PERCENT / 100,
633            HUNDRED_PERCENT / 200,
634            fusion_pool,
635            test_tick_arrays(fusion_pool),
636        )
637        .unwrap();
638
639        assert_eq!(quote.collateral, 1060346869);
640        assert_eq!(quote.borrow, 4000000000);
641        assert_eq!(quote.estimated_amount, 24_972_080_293);
642        assert_eq!(quote.protocol_fee_a, 0);
643        assert_eq!(quote.protocol_fee_b, 45301734);
644        assert_eq!(quote.price_impact, 0.22373179716579372);
645        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 * 1000.0) / 200.0), 5.0, 0.1);
646    }
647
648    #[test]
649    fn increase_short_position_providing_a() {
650        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
651        let fusion_pool = test_fusion_pool(sqrt_price);
652
653        let quote = get_increase_spot_position_quote(
654            5_000_000_000,
655            TOKEN_A,
656            TOKEN_B,
657            5.0,
658            Some(0),
659            HUNDRED_PERCENT / 100,
660            HUNDRED_PERCENT / 200,
661            fusion_pool,
662            test_tick_arrays(fusion_pool),
663        )
664        .unwrap();
665
666        assert_eq!(quote.collateral, 1060346869);
667        assert_eq!(quote.borrow, 4000000000);
668        assert_eq!(quote.estimated_amount, 999_776_441);
669        assert_eq!(quote.protocol_fee_a, 45301734);
670        assert_eq!(quote.protocol_fee_b, 0);
671        assert_eq!(quote.price_impact, 0.04470636400017991);
672        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 5.0, 0.1);
673    }
674
675    #[test]
676    fn increase_short_position_providing_b() {
677        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
678        let fusion_pool = test_fusion_pool(sqrt_price);
679
680        let quote = get_increase_spot_position_quote(
681            5000_000_000,
682            TOKEN_B,
683            TOKEN_B,
684            5.0,
685            Some(0),
686            HUNDRED_PERCENT / 100,
687            HUNDRED_PERCENT / 200,
688            fusion_pool,
689            test_tick_arrays(fusion_pool),
690        )
691        .unwrap();
692
693        assert_eq!(quote.collateral, 1057165829);
694        assert_eq!(quote.borrow, 20000000000);
695        assert_eq!(quote.estimated_amount, 4996_517_564);
696        assert_eq!(quote.protocol_fee_a, 200000000);
697        assert_eq!(quote.protocol_fee_b, 5285829);
698        assert_eq!(quote.price_impact, 0.17633175413067637);
699        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 5.0, 0.1);
700    }
701
702    #[test]
703    fn increase_quote_with_slippage() {
704        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
705        let fusion_pool = test_fusion_pool(sqrt_price);
706
707        // with slippage 10%
708        let quote =
709            get_increase_spot_position_quote(200_000, TOKEN_B, TOKEN_A, 5.0, Some(1000), 0, 0, fusion_pool, test_tick_arrays(fusion_pool)).unwrap();
710        assert_eq!(quote.min_swap_output_amount, 899_994);
711
712        // without slippage
713        let quote =
714            get_increase_spot_position_quote(200_000, TOKEN_B, TOKEN_A, 5.0, Some(0), 0, 0, fusion_pool, test_tick_arrays(fusion_pool)).unwrap();
715        assert_eq!(quote.min_swap_output_amount, 999_994);
716    }
717
718    #[test]
719    fn decrease_non_leveraged_long_position_providing_a_reduce_only() {
720        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
721        let fusion_pool = test_fusion_pool(sqrt_price);
722
723        let quote = get_decrease_spot_position_quote(
724            1_000_000_000,
725            TOKEN_A,
726            1.0,
727            true,
728            Some(0),
729            TOKEN_A,
730            5_000_000_000, // A
731            0,             // B
732            HUNDRED_PERCENT / 100,
733            HUNDRED_PERCENT / 200,
734            fusion_pool,
735            test_tick_arrays(fusion_pool),
736        )
737        .unwrap();
738
739        assert_eq!(quote.decrease_percent, 200000);
740        assert_eq!(quote.collateral_token, TOKEN_A);
741        assert_eq!(quote.position_token, TOKEN_A);
742        assert_eq!(quote.estimated_amount, 4_000_000_000);
743        assert_eq!(quote.protocol_fee_a, 0);
744        assert_eq!(quote.protocol_fee_b, 0);
745        assert_eq!(quote.price_impact, 0.0);
746        assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
747    }
748
749    #[test]
750    fn decrease_non_leveraged_long_position_providing_b_reduce_only() {
751        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
752        let fusion_pool = test_fusion_pool(sqrt_price);
753
754        let quote = get_decrease_spot_position_quote(
755            200_000_000,
756            TOKEN_B,
757            1.0,
758            true,
759            Some(0),
760            TOKEN_A,
761            5_000_000_000, // A
762            0,             // B
763            HUNDRED_PERCENT / 100,
764            HUNDRED_PERCENT / 200,
765            fusion_pool,
766            test_tick_arrays(fusion_pool),
767        )
768        .unwrap();
769
770        assert_eq!(quote.decrease_percent, 200000);
771        assert_eq!(quote.collateral_token, TOKEN_B);
772        assert_eq!(quote.position_token, TOKEN_A);
773        assert_eq!(quote.estimated_amount, 4_000_000_000);
774        assert_eq!(quote.protocol_fee_a, 0);
775        assert_eq!(quote.protocol_fee_b, 0);
776        assert_eq!(quote.price_impact, 0.008916842709072448);
777        assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
778    }
779
780    #[test]
781    fn decrease_long_position_providing_a_reduce_only() {
782        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
783        let fusion_pool = test_fusion_pool(sqrt_price);
784
785        let quote = get_decrease_spot_position_quote(
786            1_000_000_000,
787            TOKEN_A,
788            5.0,
789            true,
790            Some(0),
791            TOKEN_A,
792            5_000_000_000, // A
793            800_000_000,   // B
794            HUNDRED_PERCENT / 100,
795            HUNDRED_PERCENT / 200,
796            fusion_pool,
797            test_tick_arrays(fusion_pool),
798        )
799        .unwrap();
800
801        assert_eq!(quote.decrease_percent, 200000);
802        assert_eq!(quote.collateral_token, TOKEN_A);
803        assert_eq!(quote.position_token, TOKEN_A);
804        assert_eq!(quote.collateral, 0);
805        assert_eq!(quote.borrow, 0);
806        assert_eq!(quote.estimated_amount, 4_000_000_000);
807        assert_eq!(quote.protocol_fee_a, 0);
808        assert_eq!(quote.protocol_fee_b, 0);
809        assert_eq!(quote.price_impact, 0.007155289528004705);
810
811        let quote = get_decrease_spot_position_quote(
812            6_000_000_000,
813            TOKEN_A,
814            5.0,
815            true,
816            Some(0),
817            TOKEN_A,
818            5_000_000_000, // A
819            800_000_000,   // B
820            HUNDRED_PERCENT / 100,
821            HUNDRED_PERCENT / 200,
822            fusion_pool,
823            test_tick_arrays(fusion_pool),
824        )
825        .unwrap();
826
827        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
828        assert_eq!(quote.collateral, 0);
829        assert_eq!(quote.borrow, 0);
830        assert_eq!(quote.estimated_amount, 0);
831    }
832
833    #[test]
834    fn decrease_long_position_providing_b_reduce_only() {
835        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
836        let fusion_pool = test_fusion_pool(sqrt_price);
837
838        let quote = get_decrease_spot_position_quote(
839            200_000_000,
840            TOKEN_B,
841            5.0,
842            true,
843            Some(0),
844            TOKEN_A,
845            5_000_000_000, // A
846            800_000_000,   // B
847            HUNDRED_PERCENT / 100,
848            HUNDRED_PERCENT / 200,
849            fusion_pool,
850            test_tick_arrays(fusion_pool),
851        )
852        .unwrap();
853
854        assert_eq!(quote.decrease_percent, 200000);
855        assert_eq!(quote.collateral_token, TOKEN_B);
856        assert_eq!(quote.position_token, TOKEN_A);
857        assert_eq!(quote.collateral, 0);
858        assert_eq!(quote.borrow, 0);
859        assert_eq!(quote.estimated_amount, 4000000000);
860        assert_eq!(quote.protocol_fee_a, 0);
861        assert_eq!(quote.protocol_fee_b, 0);
862        assert_eq!(quote.price_impact, 0.008916842709072448);
863
864        let quote = get_decrease_spot_position_quote(
865            1200_000_000,
866            TOKEN_B,
867            5.0,
868            true,
869            Some(0),
870            TOKEN_A,
871            5_000_000_000, // A
872            800_000_000,   // B
873            HUNDRED_PERCENT / 100,
874            HUNDRED_PERCENT / 200,
875            fusion_pool,
876            test_tick_arrays(fusion_pool),
877        )
878        .unwrap();
879
880        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
881        assert_eq!(quote.collateral, 0);
882        assert_eq!(quote.borrow, 0);
883        assert_eq!(quote.estimated_amount, 0);
884    }
885
886    #[test]
887    fn decrease_long_position_providing_a_without_leverage() {
888        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
889        let fusion_pool = test_fusion_pool(sqrt_price);
890
891        let quote = get_decrease_spot_position_quote(
892            6_000_000_000,
893            TOKEN_A,
894            1.0,
895            false,
896            Some(100),
897            TOKEN_A,
898            5_000_000_000, // A
899            800_000_000,   // B
900            HUNDRED_PERCENT / 100,
901            HUNDRED_PERCENT / 200,
902            fusion_pool,
903            test_tick_arrays(fusion_pool),
904        )
905        .unwrap();
906
907        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
908        assert_eq!(quote.collateral_token, TOKEN_A);
909        assert_eq!(quote.position_token, TOKEN_B);
910        assert_eq!(quote.collateral, 1005025125);
911        assert_eq!(quote.borrow, 0);
912        assert_eq!(quote.estimated_amount, 199_319_781);
913        assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
914        assert_eq!(quote.increase_min_swap_output_amount, 197_326_583);
915        assert_eq!(quote.protocol_fee_a, 5025125);
916        assert_eq!(quote.protocol_fee_b, 0);
917        assert_eq!(quote.price_impact, 0.044685946117650754);
918    }
919
920    #[test]
921    fn decrease_long_position_providing_a() {
922        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
923        let fusion_pool = test_fusion_pool(sqrt_price);
924
925        let quote = get_decrease_spot_position_quote(
926            6_000_000_000,
927            TOKEN_A,
928            5.0,
929            false,
930            Some(100),
931            TOKEN_A,
932            5_000_000_000, // A
933            800_000_000,   // B
934            HUNDRED_PERCENT / 100,
935            HUNDRED_PERCENT / 200,
936            fusion_pool,
937            test_tick_arrays(fusion_pool),
938        )
939        .unwrap();
940
941        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
942        assert_eq!(quote.collateral_token, TOKEN_A);
943        assert_eq!(quote.position_token, TOKEN_B);
944        assert_eq!(quote.collateral, 211433165);
945        assert_eq!(quote.borrow, 800000000);
946        assert_eq!(quote.estimated_amount, 199_793_344);
947        assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
948        assert_eq!(quote.increase_min_swap_output_amount, 197_795_410);
949        assert_eq!(quote.protocol_fee_a, 1057165);
950        assert_eq!(quote.protocol_fee_b, 8000000);
951        assert_eq!(quote.price_impact, 0.04470711974918773);
952    }
953
954    #[test]
955    fn decrease_long_position_providing_b() {
956        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
957        let fusion_pool = test_fusion_pool(sqrt_price);
958
959        let quote = get_decrease_spot_position_quote(
960            1200_000_000,
961            TOKEN_B,
962            5.0,
963            false,
964            Some(0),
965            TOKEN_A,
966            5_000_000_000, // A
967            800_000_000,   // B
968            HUNDRED_PERCENT / 100,
969            HUNDRED_PERCENT / 200,
970            fusion_pool,
971            test_tick_arrays(fusion_pool),
972        )
973        .unwrap();
974
975        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
976        assert_eq!(quote.collateral_token, TOKEN_B);
977        assert_eq!(quote.position_token, TOKEN_B);
978        assert_eq!(quote.collateral, 42286633);
979        assert_eq!(quote.borrow, 800_000_000);
980        assert_eq!(quote.estimated_amount, 199_924_036);
981        assert_eq!(quote.decrease_acceptable_swap_amount, 996_777_780);
982        assert_eq!(quote.increase_min_swap_output_amount, 157_848_836);
983        assert_eq!(quote.protocol_fee_a, 0);
984        assert_eq!(quote.protocol_fee_b, 8211433);
985        assert_eq!(quote.price_impact, 0.05162980632488212);
986    }
987
988    #[test]
989    fn decrease_non_leveraged_short_position_providing_a() {
990        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
991        let fusion_pool = test_fusion_pool(sqrt_price);
992
993        let quote = get_decrease_spot_position_quote(
994            6_000_000_000,
995            TOKEN_A,
996            1.0,
997            false,
998            Some(0),
999            TOKEN_B,
1000            1000_000_000, // B
1001            0,            // A
1002            HUNDRED_PERCENT / 100,
1003            HUNDRED_PERCENT / 200,
1004            fusion_pool,
1005            test_tick_arrays(fusion_pool),
1006        )
1007        .unwrap();
1008
1009        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1010        assert_eq!(quote.collateral_token, TOKEN_A);
1011        assert_eq!(quote.position_token, TOKEN_A);
1012        assert_eq!(quote.collateral, 1005025125);
1013        assert_eq!(quote.borrow, 0);
1014        assert_eq!(quote.estimated_amount, 1_000_000_000);
1015        assert_eq!(quote.protocol_fee_a, 5025125);
1016        assert_eq!(quote.protocol_fee_b, 0);
1017        assert_eq!(quote.price_impact, 0.044592165429757635);
1018    }
1019
1020    #[test]
1021    fn decrease_short_position_providing_a() {
1022        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1023        let fusion_pool = test_fusion_pool(sqrt_price);
1024
1025        let quote = get_decrease_spot_position_quote(
1026            6_000_000_000,
1027            TOKEN_A,
1028            5.0,
1029            false,
1030            Some(0),
1031            TOKEN_B,
1032            1000_000_000,  // B
1033            4_000_000_000, // A
1034            HUNDRED_PERCENT / 100,
1035            HUNDRED_PERCENT / 200,
1036            fusion_pool,
1037            test_tick_arrays(fusion_pool),
1038        )
1039        .unwrap();
1040
1041        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1042        assert_eq!(quote.collateral_token, TOKEN_A);
1043        assert_eq!(quote.position_token, TOKEN_A);
1044        assert_eq!(quote.collateral, 211433165);
1045        assert_eq!(quote.borrow, 160000000);
1046        assert_eq!(quote.estimated_amount, 999_620_182);
1047        assert_eq!(quote.decrease_acceptable_swap_amount, 4_983_888_901);
1048        assert_eq!(quote.increase_min_swap_output_amount, 789_244_182);
1049        assert_eq!(quote.protocol_fee_a, 2657165);
1050        assert_eq!(quote.protocol_fee_b, 0);
1051        assert_eq!(quote.price_impact, 0.051656476403882934);
1052    }
1053
1054    #[test]
1055    fn decrease_short_position_providing_b() {
1056        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1057        let fusion_pool = test_fusion_pool(sqrt_price);
1058
1059        let quote = get_decrease_spot_position_quote(
1060            1_200_000_000,
1061            TOKEN_B,
1062            5.0,
1063            false,
1064            Some(0),
1065            TOKEN_B,
1066            1000_000_000,  // B
1067            4_000_000_000, // A
1068            HUNDRED_PERCENT / 100,
1069            HUNDRED_PERCENT / 200,
1070            fusion_pool,
1071            test_tick_arrays(fusion_pool),
1072        )
1073        .unwrap();
1074
1075        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1076        assert_eq!(quote.collateral_token, TOKEN_B);
1077        assert_eq!(quote.position_token, TOKEN_A);
1078        assert_eq!(quote.collateral, 42_286_633);
1079        assert_eq!(quote.borrow, 160000000);
1080        assert_eq!(quote.estimated_amount, 998_966_727);
1081        assert_eq!(quote.protocol_fee_a, 1600000);
1082        assert_eq!(quote.protocol_fee_b, 211433);
1083        assert_eq!(quote.price_impact, 0.04472711596017476);
1084    }
1085
1086    #[test]
1087    fn tradable_amount_for_1x_long_position_providing_b() {
1088        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1089        let fusion_pool = test_fusion_pool(sqrt_price);
1090        let tick_arrays = test_tick_arrays(fusion_pool);
1091
1092        let collateral_token = TOKEN_B;
1093        let position_token = TOKEN_A;
1094        let leverage = 1.0;
1095        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1096        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1097        let available_balance = 200_000_000;
1098
1099        let tradable_amount = get_tradable_amount(
1100            collateral_token,
1101            available_balance,
1102            leverage,
1103            position_token,
1104            false,
1105            position_token,
1106            0,
1107            0,
1108            protocol_fee_rate,
1109            protocol_fee_rate_on_collateral,
1110            fusion_pool,
1111            tick_arrays.clone(),
1112        )
1113        .unwrap();
1114        assert_eq!(tradable_amount, 198403000);
1115
1116        let quote = get_increase_spot_position_quote(
1117            tradable_amount,
1118            collateral_token,
1119            position_token,
1120            leverage,
1121            Some(0),
1122            protocol_fee_rate,
1123            protocol_fee_rate_on_collateral,
1124            fusion_pool,
1125            tick_arrays,
1126        )
1127        .unwrap();
1128        assert_eq!(quote.collateral, available_balance);
1129    }
1130
1131    #[test]
1132    fn tradable_amount_for_5x_long_position_providing_b() {
1133        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1134        let fusion_pool = test_fusion_pool(sqrt_price);
1135        let tick_arrays = test_tick_arrays(fusion_pool);
1136
1137        let collateral_token = TOKEN_B;
1138        let position_token = TOKEN_A;
1139        let leverage = 5.0;
1140        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1141        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1142        let available_balance = 10_000_000;
1143
1144        let tradable_amount = get_tradable_amount(
1145            collateral_token,
1146            available_balance,
1147            leverage,
1148            position_token,
1149            false,
1150            position_token,
1151            0,
1152            0,
1153            protocol_fee_rate,
1154            protocol_fee_rate_on_collateral,
1155            fusion_pool,
1156            tick_arrays.clone(),
1157        )
1158        .unwrap();
1159        assert_eq!(tradable_amount, 47154380);
1160
1161        let quote = get_increase_spot_position_quote(
1162            tradable_amount,
1163            collateral_token,
1164            position_token,
1165            leverage,
1166            Some(0),
1167            protocol_fee_rate,
1168            protocol_fee_rate_on_collateral,
1169            fusion_pool,
1170            tick_arrays,
1171        )
1172        .unwrap();
1173        // TODO: fix precision error
1174        assert_eq!(quote.collateral, available_balance + 1);
1175    }
1176
1177    #[test]
1178    fn tradable_amount_for_5x_long_position_providing_a() {
1179        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1180        let fusion_pool = test_fusion_pool(sqrt_price);
1181        let tick_arrays = test_tick_arrays(fusion_pool);
1182
1183        let collateral_token = TOKEN_A;
1184        let position_token = TOKEN_A;
1185        let leverage = 5.0;
1186        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1187        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1188        let available_balance = 1_000_000_000;
1189
1190        let tradable_amount = get_tradable_amount(
1191            collateral_token,
1192            available_balance,
1193            leverage,
1194            position_token,
1195            false,
1196            position_token,
1197            0,
1198            0,
1199            protocol_fee_rate,
1200            protocol_fee_rate_on_collateral,
1201            fusion_pool,
1202            tick_arrays.clone(),
1203        )
1204        .unwrap();
1205        assert_eq!(tradable_amount, 4729626953);
1206
1207        let quote = get_increase_spot_position_quote(
1208            tradable_amount,
1209            collateral_token,
1210            position_token,
1211            leverage,
1212            Some(0),
1213            protocol_fee_rate,
1214            protocol_fee_rate_on_collateral,
1215            fusion_pool,
1216            tick_arrays,
1217        )
1218        .unwrap();
1219        assert_eq!(quote.collateral, available_balance);
1220        //assert_eq!(quote.estimated_amount, tradable_amount);
1221    }
1222
1223    #[test]
1224    fn tradable_amount_for_5x_short_position_providing_b() {
1225        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1226        let fusion_pool = test_fusion_pool(sqrt_price);
1227        let tick_arrays = test_tick_arrays(fusion_pool);
1228
1229        let collateral_token = TOKEN_B;
1230        let position_token = TOKEN_B;
1231        let leverage = 5.0;
1232        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1233        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1234        let available_balance = 200_000_000;
1235
1236        let tradable_amount = get_tradable_amount(
1237            collateral_token,
1238            available_balance,
1239            leverage,
1240            position_token,
1241            false,
1242            position_token,
1243            0,
1244            0,
1245            protocol_fee_rate,
1246            protocol_fee_rate_on_collateral,
1247            fusion_pool,
1248            tick_arrays.clone(),
1249        )
1250        .unwrap();
1251        assert_eq!(tradable_amount, 945925390);
1252
1253        let quote = get_increase_spot_position_quote(
1254            tradable_amount,
1255            collateral_token,
1256            position_token,
1257            leverage,
1258            Some(0),
1259            protocol_fee_rate,
1260            protocol_fee_rate_on_collateral,
1261            fusion_pool,
1262            tick_arrays,
1263        )
1264        .unwrap();
1265        // TODO: fix precision error
1266        assert_eq!(quote.collateral, available_balance + 1);
1267        //assert_eq!(quote.estimated_amount, tradable_amount);
1268    }
1269
1270    #[test]
1271    fn tradable_amount_for_reducing_existing_long_position() {
1272        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1273        let fusion_pool = test_fusion_pool(sqrt_price);
1274        let tick_arrays = test_tick_arrays(fusion_pool);
1275
1276        for i in 0..2 {
1277            let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1278            let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1279            let leverage = 5.0;
1280            let reduce_only = true;
1281            let position_amount = 5_000_000_000;
1282            let position_debt = 800_000_000;
1283            let protocol_fee_rate = HUNDRED_PERCENT / 100;
1284            let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1285            let available_balance = 50_000_000_000;
1286
1287            let tradable_amount = get_tradable_amount(
1288                collateral_token,
1289                available_balance,
1290                leverage,
1291                if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1292                reduce_only,
1293                position_token,
1294                position_amount,
1295                position_debt,
1296                protocol_fee_rate,
1297                protocol_fee_rate_on_collateral,
1298                fusion_pool,
1299                tick_arrays.clone(),
1300            )
1301            .unwrap();
1302            assert_eq!(tradable_amount, 5_000_000_000);
1303
1304            let quote = get_decrease_spot_position_quote(
1305                tradable_amount,
1306                collateral_token,
1307                5.0,
1308                reduce_only,
1309                Some(0),
1310                position_token,
1311                position_amount,
1312                position_debt,
1313                protocol_fee_rate,
1314                protocol_fee_rate_on_collateral,
1315                fusion_pool,
1316                tick_arrays.clone(),
1317            )
1318            .unwrap();
1319
1320            assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1321        }
1322    }
1323
1324    #[test]
1325    fn tradable_amount_when_inverting_long_position_providing_a() {
1326        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1327        let fusion_pool = test_fusion_pool(sqrt_price);
1328        let tick_arrays = test_tick_arrays(fusion_pool);
1329
1330        let collateral_token = TOKEN_A;
1331        let position_token = TOKEN_A;
1332        let leverage = 5.0;
1333        let reduce_only = false;
1334        let position_amount = 5_000_000_000;
1335        let position_debt = 800_000_000;
1336        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1337        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1338        let available_balance = 2_000_000_000;
1339
1340        let tradable_amount = get_tradable_amount(
1341            collateral_token,
1342            available_balance,
1343            leverage,
1344            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1345            reduce_only,
1346            position_token,
1347            position_amount,
1348            position_debt,
1349            protocol_fee_rate,
1350            protocol_fee_rate_on_collateral,
1351            fusion_pool,
1352            tick_arrays.clone(),
1353        )
1354        .unwrap();
1355        assert_eq!(tradable_amount, 19_086_173_788);
1356
1357        let quote = get_decrease_spot_position_quote(
1358            tradable_amount,
1359            collateral_token,
1360            5.0,
1361            reduce_only,
1362            Some(0),
1363            position_token,
1364            position_amount,
1365            position_debt,
1366            protocol_fee_rate,
1367            protocol_fee_rate_on_collateral,
1368            fusion_pool,
1369            tick_arrays.clone(),
1370        )
1371        .unwrap();
1372
1373        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1374        assert_eq!(quote.collateral, 2_978_284_319);
1375    }
1376
1377    #[test]
1378    fn tradable_amount_when_inverting_long_position_providing_b() {
1379        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1380        let fusion_pool = test_fusion_pool(sqrt_price);
1381        let tick_arrays = test_tick_arrays(fusion_pool);
1382
1383        let collateral_token = TOKEN_B;
1384        let position_token = TOKEN_A;
1385        let leverage = 5.0;
1386        let reduce_only = false;
1387        let position_amount = 5_000_000_000;
1388        let position_debt = 800_000_000;
1389        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1390        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1391        let available_balance = 500_000_000;
1392
1393        let tradable_amount = get_tradable_amount(
1394            collateral_token,
1395            available_balance,
1396            leverage,
1397            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1398            reduce_only,
1399            position_token,
1400            position_amount,
1401            position_debt,
1402            protocol_fee_rate,
1403            protocol_fee_rate_on_collateral,
1404            fusion_pool,
1405            tick_arrays.clone(),
1406        )
1407        .unwrap();
1408        assert_eq!(tradable_amount, 4_295_498_968);
1409
1410        let quote = get_decrease_spot_position_quote(
1411            tradable_amount,
1412            collateral_token,
1413            5.0,
1414            reduce_only,
1415            Some(0),
1416            position_token,
1417            position_amount,
1418            position_debt,
1419            protocol_fee_rate,
1420            protocol_fee_rate_on_collateral,
1421            fusion_pool,
1422            tick_arrays.clone(),
1423        )
1424        .unwrap();
1425
1426        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1427        assert_eq!(quote.collateral, 696_777_779);
1428    }
1429}