1#![allow(clippy::collapsible_else_if)]
2#![allow(clippy::too_many_arguments)]
3
4#[cfg(feature = "wasm")]
5use fusionamm_macros::wasm_expose;
6#[cfg(feature = "wasm")]
7use serde::Serialize;
8#[cfg(feature = "wasm")]
11use wasm_bindgen::prelude::wasm_bindgen;
12use crate::utils::fees;
16use crate::{calculate_tuna_protocol_fee, HUNDRED_PERCENT, INVALID_ARGUMENTS, TOKEN_A, TOKEN_B};
17use fusionamm_core::{
18 sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_get_max_amount_with_slippage_tolerance,
19 try_get_min_amount_with_slippage_tolerance, try_mul_div, CoreError, FusionPoolFacade, TickArrays, TokenPair,
20};
21use libm::{ceil, round};
22
23pub const DEFAULT_SLIPPAGE_TOLERANCE_BPS: u16 = 100;
24
25#[cfg_attr(feature = "wasm", wasm_expose)]
35pub struct IncreaseSpotPositionQuoteResult {
36 pub collateral: u64,
38 pub borrow: u64,
40 pub estimated_amount: u64,
42 pub swap_input_amount: u64,
44 pub swap_output_amount: u64,
46 pub min_swap_output_amount: u64,
48 pub protocol_fee_a: u64,
50 pub protocol_fee_b: u64,
52 pub price_impact: f64,
54}
55
56#[cfg_attr(feature = "wasm", wasm_expose)]
72pub fn get_increase_spot_position_quote(
73 increase_amount: u64,
74 collateral_token: u8,
75 position_token: u8,
76 leverage: f64,
77 slippage_tolerance_bps: Option<u16>,
78 protocol_fee_rate: u16,
79 protocol_fee_rate_on_collateral: u16,
80 fusion_pool: FusionPoolFacade,
81 tick_arrays: Option<TickArrays>,
82) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
83 if collateral_token > TOKEN_B || position_token > TOKEN_B {
84 return Err(INVALID_ARGUMENTS.into());
85 }
86
87 if leverage < 1.0 {
88 return Err(INVALID_ARGUMENTS.into());
89 }
90
91 let borrow: u64;
92 let mut collateral: u64;
93 let mut estimated_amount: u64 = 0;
94 let mut swap_input_amount: u64;
95 let mut swap_output_amount: u64 = 0;
96 let mut min_swap_output_amount: u64 = 0;
97 let mut price_impact: f64 = 0.0;
98
99 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
100 let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
101
102 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
103 let swap_input_token_is_a = borrowed_token == TOKEN_A;
104
105 if borrowed_token == collateral_token {
106 borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
107 collateral =
108 increase_amount - fees::apply_swap_fee(fees::apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
109 collateral = fees::reverse_apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
110 collateral = fees::reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
111
112 swap_input_amount = collateral + borrow;
113 } else {
114 let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
115 let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
116
117 borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
118
119 let borrow_in_position_token_with_fees_applied = fees::apply_swap_fee(
120 fees::apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?,
121 fusion_pool.fee_rate,
122 false,
123 )?;
124
125 collateral = increase_amount - borrow_in_position_token_with_fees_applied;
126 collateral = fees::reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
127
128 swap_input_amount = borrow;
129 }
130
131 let protocol_fee = calculate_tuna_spot_position_protocol_fee(
132 collateral_token,
133 borrowed_token,
134 collateral,
135 borrow,
136 protocol_fee_rate_on_collateral,
137 protocol_fee_rate,
138 );
139
140 swap_input_amount -= if swap_input_token_is_a { protocol_fee.a } else { protocol_fee.b };
141
142 if position_token == collateral_token {
143 estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee.a } else { protocol_fee.b };
144 }
145
146 if swap_input_amount > 0 {
147 if let Some(tick_arrays) = tick_arrays {
148 let quote = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, fusion_pool, tick_arrays, None, None)?;
149 estimated_amount += quote.token_est_out;
150 swap_output_amount = quote.token_est_out;
151 min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(swap_output_amount, slippage_tolerance_bps)?;
152 let new_price = sqrt_price_to_price(quote.next_sqrt_price.into(), 1, 1);
153 price_impact = (new_price / price - 1.0).abs();
154 }
160 }
161
162 Ok(IncreaseSpotPositionQuoteResult {
163 collateral,
164 borrow,
165 estimated_amount,
166 swap_input_amount,
167 swap_output_amount,
168 min_swap_output_amount,
169 protocol_fee_a: protocol_fee.a,
170 protocol_fee_b: protocol_fee.b,
171 price_impact,
172 })
173}
174
175#[cfg_attr(feature = "wasm", wasm_expose)]
232pub struct DecreaseSpotPositionQuoteResult {
233 pub decrease_percent: u32,
235 pub swap_input_amount: u64,
237 pub swap_output_amount: u64,
239 pub required_swap_amount: u64,
244 pub estimated_amount: u64,
246 pub estimated_payable_debt: u64,
248 pub estimated_collateral_to_be_withdrawn: u64,
250 pub price_impact: f64,
252}
253
254#[cfg_attr(feature = "wasm", wasm_expose)]
270pub fn get_decrease_spot_position_quote(
271 decrease_amount: u64,
272 collateral_token: u8,
273 leverage: f64,
274 slippage_tolerance_bps: Option<u16>,
275 position_token: u8,
276 position_amount: u64,
277 position_debt: u64,
278 fusion_pool: FusionPoolFacade,
279 tick_arrays: Option<TickArrays>,
280) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
281 if collateral_token > TOKEN_B || position_token > TOKEN_B {
282 return Err(INVALID_ARGUMENTS.into());
283 }
284
285 if leverage < 1.0 {
286 return Err(INVALID_ARGUMENTS.into());
287 }
288
289 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
290 let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
291 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
292 let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
293
294 let mut required_swap_amount: u64 = 0;
295
296 let mut decrease_amount_in_position_token = if collateral_token == position_token {
297 decrease_amount
298 } else {
299 round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
300 };
301
302 decrease_amount_in_position_token = position_amount.min(decrease_amount_in_position_token);
303
304 let decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
305
306 let estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
307 let estimated_payable_debt = try_mul_div(position_debt, decrease_percent as u128, HUNDRED_PERCENT as u128, true)?;
308 let mut estimated_collateral_to_be_withdrawn = 0;
309
310 let mut next_sqrt_price = fusion_pool.sqrt_price;
311 let mut swap_input_amount = 0;
312 let mut swap_output_amount = 0;
313
314 if collateral_token == position_token {
315 if position_debt > 0 {
316 swap_output_amount = estimated_payable_debt;
317 if let Some(tick_arrays) = tick_arrays {
318 let swap = swap_quote_by_output_token(swap_output_amount, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
319 swap_input_amount = swap.token_est_in;
320 next_sqrt_price = swap.next_sqrt_price;
321 required_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_tolerance_bps)?;
322 estimated_collateral_to_be_withdrawn = position_amount.saturating_sub(swap.token_est_in).saturating_sub(estimated_amount);
323 }
324 } else {
325 estimated_collateral_to_be_withdrawn = position_amount - estimated_amount;
326 }
327 } else {
328 swap_input_amount = position_amount - estimated_amount;
329 if let Some(tick_arrays) = tick_arrays {
330 let swap = swap_quote_by_input_token(swap_input_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
331 next_sqrt_price = swap.next_sqrt_price;
332 swap_output_amount = swap.token_est_out;
333 required_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
334 estimated_collateral_to_be_withdrawn = swap.token_est_out.saturating_sub(estimated_payable_debt);
335 }
336 }
337
338 let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
339
340 let price_impact = (new_price / price - 1.0).abs();
341 Ok(DecreaseSpotPositionQuoteResult {
350 decrease_percent,
351 estimated_payable_debt,
352 estimated_collateral_to_be_withdrawn,
353 swap_input_amount,
354 swap_output_amount,
355 required_swap_amount,
356 estimated_amount,
357 price_impact,
358 })
359}
360
361#[cfg_attr(feature = "wasm", wasm_expose)]
426pub fn get_spot_position_liquidation_price(position_token: u8, amount: u64, debt: u64, liquidation_threshold: u32) -> Result<f64, CoreError> {
427 if liquidation_threshold >= HUNDRED_PERCENT {
428 return Err(INVALID_ARGUMENTS);
429 }
430
431 if debt == 0 || amount == 0 {
432 return Ok(0.0);
433 }
434
435 let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
436
437 if position_token == TOKEN_A {
438 Ok(debt as f64 / (amount as f64 * liquidation_threshold_f))
439 } else {
440 Ok((amount as f64 * liquidation_threshold_f) / debt as f64)
441 }
442}
443
444#[cfg_attr(feature = "wasm", wasm_expose)]
460pub fn get_tradable_amount(
461 collateral_token: u8,
462 available_balance: u64,
463 leverage: f64,
464 position_token: u8,
465 position_amount: u64,
466 protocol_fee_rate: u16,
467 protocol_fee_rate_on_collateral: u16,
468 fusion_pool: FusionPoolFacade,
469 increase: bool,
470) -> Result<u64, CoreError> {
471 if collateral_token > TOKEN_B || position_token > TOKEN_B {
472 return Err(INVALID_ARGUMENTS.into());
473 }
474
475 if leverage < 1.0 {
476 return Err(INVALID_ARGUMENTS.into());
477 }
478
479 let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
483 let mut collateral = fees::apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
484 if collateral_token != position_token {
485 collateral = fees::apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
486 }
487
488 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);
489 let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
490 Ok(total)
491 };
492
493 let available_to_trade = if increase {
494 add_leverage(available_balance)?
495 } else {
496 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
497 let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
498
499 if collateral_token == position_token {
500 position_amount
501 } else {
502 round(position_amount as f64 * position_to_opposite_token_price) as u64
503 }
504 };
505
506 Ok(available_to_trade)
507}
508
509#[cfg_attr(feature = "wasm", wasm_expose)]
571pub fn calculate_tuna_spot_position_protocol_fee(
572 collateral_token: u8,
573 borrowed_token: u8,
574 collateral: u64,
575 borrow: u64,
576 protocol_fee_rate_on_collateral: u16,
577 protocol_fee_rate: u16,
578) -> TokenPair {
579 let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
580 let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
581 let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
582 let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
583
584 let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, protocol_fee_rate_on_collateral, protocol_fee_rate);
585 let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, protocol_fee_rate_on_collateral, protocol_fee_rate);
586
587 TokenPair {
588 a: protocol_fee_a,
589 b: protocol_fee_b,
590 }
591}
592
593#[cfg(all(test, not(feature = "wasm")))]
594mod tests {
595 use super::*;
596 use crate::assert_approx_eq;
597 use fusionamm_core::{
598 get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
599 };
600
601 fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
602 let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
603 FusionPoolFacade {
604 tick_current_index,
605 fee_rate: 3000,
606 liquidity: 10000000000000,
607 sqrt_price,
608 tick_spacing: 2,
609 ..FusionPoolFacade::default()
610 }
611 }
612
613 fn test_tick(liquidity_net: i128) -> TickFacade {
614 TickFacade {
615 initialized: true,
616 liquidity_net,
617 ..TickFacade::default()
618 }
619 }
620
621 fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
622 TickArrayFacade {
623 start_tick_index,
624 ticks: [test_tick(0); TICK_ARRAY_SIZE],
625 }
626 }
627
628 fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
629 let tick_spacing = fusion_pool.tick_spacing;
630 let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
631 let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
632
633 [
634 test_tick_array(tick_array_start_index),
635 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
636 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
637 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
638 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
639 ]
640 .into()
641 }
642
643 #[test]
644 fn test_get_liquidation_price() {
645 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 0, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
646 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 0, 5, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
647 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 800, HUNDRED_PERCENT * 85 / 100), Ok(188.23529411764707));
648 assert_eq!(get_spot_position_liquidation_price(TOKEN_B, 1000, 4, HUNDRED_PERCENT * 85 / 100), Ok(212.5));
649 }
650
651 #[tokio::test]
652 async fn increase_long_position_providing_token_a() {
653 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
654 let fusion_pool = test_fusion_pool(sqrt_price);
655
656 let quote = get_increase_spot_position_quote(
657 5_000_000_000,
658 TOKEN_A,
659 TOKEN_A,
660 5.0,
661 Some(0),
662 (HUNDRED_PERCENT / 100) as u16,
663 (HUNDRED_PERCENT / 200) as u16,
664 fusion_pool,
665 Some(test_tick_arrays(fusion_pool)),
666 )
667 .unwrap();
668
669 assert_eq!(quote.collateral, 1057165829);
670 assert_eq!(quote.borrow, 800000000);
671 assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
672 assert_eq!(quote.estimated_amount, 4_999_303_011);
673 assert_eq!(quote.protocol_fee_a, 5285829);
674 assert_eq!(quote.protocol_fee_b, 8000000);
675 assert_eq!(quote.price_impact, 0.00035316176257027543);
676 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);
677 }
678
679 #[tokio::test]
680 async fn increase_long_position_providing_token_b() {
681 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
682 let fusion_pool = test_fusion_pool(sqrt_price);
683
684 let quote = get_increase_spot_position_quote(
685 5000_000_000,
686 TOKEN_B,
687 TOKEN_A,
688 5.0,
689 Some(0),
690 (HUNDRED_PERCENT / 100) as u16,
691 (HUNDRED_PERCENT / 200) as u16,
692 fusion_pool,
693 Some(test_tick_arrays(fusion_pool)),
694 )
695 .unwrap();
696
697 assert_eq!(quote.collateral, 1060346869);
698 assert_eq!(quote.borrow, 4000000000);
699 assert_eq!(quote.estimated_amount, 24_972_080_293);
700 assert_eq!(quote.protocol_fee_a, 0);
701 assert_eq!(quote.protocol_fee_b, 45301734);
702 assert_eq!(quote.price_impact, 0.0022373179716579372);
703 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);
704 }
705
706 #[tokio::test]
707 async fn increase_short_position_providing_a() {
708 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
709 let fusion_pool = test_fusion_pool(sqrt_price);
710
711 let quote = get_increase_spot_position_quote(
712 5_000_000_000,
713 TOKEN_A,
714 TOKEN_B,
715 5.0,
716 Some(0),
717 (HUNDRED_PERCENT / 100) as u16,
718 (HUNDRED_PERCENT / 200) as u16,
719 fusion_pool,
720 Some(test_tick_arrays(fusion_pool)),
721 )
722 .unwrap();
723
724 assert_eq!(quote.collateral, 1060346869);
725 assert_eq!(quote.borrow, 4000000000);
726 assert_eq!(quote.estimated_amount, 999_776_441);
727 assert_eq!(quote.protocol_fee_a, 45301734);
728 assert_eq!(quote.protocol_fee_b, 0);
729 assert_eq!(quote.price_impact, 0.0004470636400017991);
730 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);
731 }
732
733 #[tokio::test]
734 async fn increase_short_position_providing_b() {
735 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
736 let fusion_pool = test_fusion_pool(sqrt_price);
737
738 let quote = get_increase_spot_position_quote(
739 5000_000_000,
740 TOKEN_B,
741 TOKEN_B,
742 5.0,
743 Some(0),
744 (HUNDRED_PERCENT / 100) as u16,
745 (HUNDRED_PERCENT / 200) as u16,
746 fusion_pool,
747 Some(test_tick_arrays(fusion_pool)),
748 )
749 .unwrap();
750
751 assert_eq!(quote.collateral, 1057165829);
752 assert_eq!(quote.borrow, 20000000000);
753 assert_eq!(quote.estimated_amount, 4996_517_564);
754 assert_eq!(quote.protocol_fee_a, 200000000);
755 assert_eq!(quote.protocol_fee_b, 5285829);
756 assert_eq!(quote.price_impact, 0.0017633175413067637);
757 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);
758 }
759
760 #[tokio::test]
761 async fn increase_quote_with_slippage() {
762 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
763 let fusion_pool = test_fusion_pool(sqrt_price);
764
765 let quote =
767 get_increase_spot_position_quote(200_000, TOKEN_B, TOKEN_A, 5.0, Some(1000), 0, 0, fusion_pool, Some(test_tick_arrays(fusion_pool)))
768 .unwrap();
769 assert_eq!(quote.min_swap_output_amount, 899_994);
770
771 let quote = get_increase_spot_position_quote(200_000, TOKEN_B, TOKEN_A, 5.0, Some(0), 0, 0, fusion_pool, Some(test_tick_arrays(fusion_pool)))
773 .unwrap();
774 assert_eq!(quote.min_swap_output_amount, 999_994);
775 }
776
777 #[tokio::test]
778 async fn decrease_non_leveraged_long_position_providing_a() {
779 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
780 let fusion_pool = test_fusion_pool(sqrt_price);
781
782 let quote = get_decrease_spot_position_quote(
783 1_000_000_000,
784 TOKEN_A,
785 1.0,
786 Some(0),
787 TOKEN_A,
788 5_000_000_000, 0, fusion_pool,
791 Some(test_tick_arrays(fusion_pool)),
792 )
793 .unwrap();
794
795 assert_eq!(quote.decrease_percent, 200000);
796 assert_eq!(quote.estimated_amount, 4_000_000_000);
797 assert_eq!(quote.estimated_payable_debt, 0);
798 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 1_000_000_000);
799 assert_eq!(quote.price_impact, 0.0);
800 }
801
802 #[tokio::test]
803 async fn decrease_non_leveraged_long_position_providing_b() {
804 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
805 let fusion_pool = test_fusion_pool(sqrt_price);
806
807 let quote = get_decrease_spot_position_quote(
808 200_000_000,
809 TOKEN_B,
810 1.0,
811 Some(0),
812 TOKEN_A,
813 5_000_000_000, 0, fusion_pool,
816 Some(test_tick_arrays(fusion_pool)),
817 )
818 .unwrap();
819
820 assert_eq!(quote.decrease_percent, 200000);
821 assert_eq!(quote.estimated_amount, 4_000_000_000);
822 assert_eq!(quote.estimated_payable_debt, 0);
823 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 199_391_108);
824 assert_eq!(quote.price_impact, 0.00008916842709072448);
825 }
826
827 #[tokio::test]
828 async fn decrease_long_position_providing_a() {
829 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
830 let fusion_pool = test_fusion_pool(sqrt_price);
831
832 let quote = get_decrease_spot_position_quote(
833 1_000_000_000,
834 TOKEN_A,
835 5.0,
836 Some(0),
837 TOKEN_A,
838 5_000_000_000, 800_000_000, fusion_pool,
841 Some(test_tick_arrays(fusion_pool)),
842 )
843 .unwrap();
844
845 assert_eq!(quote.decrease_percent, 200000);
846 assert_eq!(quote.estimated_amount, 4_000_000_000);
847 assert_eq!(quote.estimated_payable_debt, 160_000_000);
848 assert_eq!(quote.swap_input_amount, 802_435_931);
849 assert_eq!(quote.swap_output_amount, 160_000_000);
850 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 197_564_069);
851 assert_eq!(quote.price_impact, 0.00007155289528004705);
852
853 let quote = get_decrease_spot_position_quote(
854 6_000_000_000,
855 TOKEN_A,
856 5.0,
857 Some(0),
858 TOKEN_A,
859 5_000_000_000, 800_000_000, fusion_pool,
862 Some(test_tick_arrays(fusion_pool)),
863 )
864 .unwrap();
865
866 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
867 assert_eq!(quote.estimated_amount, 0);
868 }
869
870 #[tokio::test]
871 async fn decrease_long_position_providing_b() {
872 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
873 let fusion_pool = test_fusion_pool(sqrt_price);
874
875 let quote = get_decrease_spot_position_quote(
876 200_000_000,
877 TOKEN_B,
878 5.0,
879 Some(0),
880 TOKEN_A,
881 5_000_000_000, 800_000_000, fusion_pool,
884 Some(test_tick_arrays(fusion_pool)),
885 )
886 .unwrap();
887
888 assert_eq!(quote.estimated_amount, 4000000000);
889 assert_eq!(quote.decrease_percent, 200000);
890 assert_eq!(quote.estimated_payable_debt, 160_000_000);
891 assert_eq!(quote.swap_input_amount, 1_000_000_000);
892 assert_eq!(quote.swap_output_amount, 199_391_108);
893 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 39_391_108);
894 assert_eq!(quote.price_impact, 0.00008916842709072448);
895
896 let quote = get_decrease_spot_position_quote(
897 1200_000_000,
898 TOKEN_B,
899 5.0,
900 Some(0),
901 TOKEN_A,
902 5_000_000_000, 800_000_000, fusion_pool,
905 Some(test_tick_arrays(fusion_pool)),
906 )
907 .unwrap();
908
909 assert_eq!(quote.estimated_amount, 0);
910 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
911 }
912
913 #[tokio::test]
914 async fn tradable_amount_for_1x_long_position_providing_b() {
915 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
916 let fusion_pool = test_fusion_pool(sqrt_price);
917 let tick_arrays = Some(test_tick_arrays(fusion_pool));
918
919 let collateral_token = TOKEN_B;
920 let position_token = TOKEN_A;
921 let leverage = 1.0;
922 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
923 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
924 let available_balance = 200_000_000;
925
926 let tradable_amount = get_tradable_amount(
927 collateral_token,
928 available_balance,
929 leverage,
930 position_token,
931 0,
932 protocol_fee_rate,
933 protocol_fee_rate_on_collateral,
934 fusion_pool,
935 true,
936 )
937 .unwrap();
938 assert_eq!(tradable_amount, 198403000);
939
940 let quote = get_increase_spot_position_quote(
941 tradable_amount,
942 collateral_token,
943 position_token,
944 leverage,
945 Some(0),
946 protocol_fee_rate,
947 protocol_fee_rate_on_collateral,
948 fusion_pool,
949 tick_arrays,
950 )
951 .unwrap();
952 assert_eq!(quote.collateral, available_balance);
953 }
954
955 #[tokio::test]
956 async fn tradable_amount_for_5x_long_position_providing_b() {
957 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
958 let fusion_pool = test_fusion_pool(sqrt_price);
959 let tick_arrays = Some(test_tick_arrays(fusion_pool));
960
961 let collateral_token = TOKEN_B;
962 let position_token = TOKEN_A;
963 let leverage = 5.0;
964 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
965 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
966 let available_balance = 10_000_000;
967
968 let tradable_amount = get_tradable_amount(
969 collateral_token,
970 available_balance,
971 leverage,
972 position_token,
973 0,
974 protocol_fee_rate,
975 protocol_fee_rate_on_collateral,
976 fusion_pool,
977 true,
978 )
979 .unwrap();
980 assert_eq!(tradable_amount, 47154380);
981
982 let quote = get_increase_spot_position_quote(
983 tradable_amount,
984 collateral_token,
985 position_token,
986 leverage,
987 Some(0),
988 protocol_fee_rate,
989 protocol_fee_rate_on_collateral,
990 fusion_pool,
991 tick_arrays,
992 )
993 .unwrap();
994 assert_eq!(quote.collateral, available_balance + 1);
996 }
997
998 #[tokio::test]
999 async fn tradable_amount_for_5x_long_position_providing_a() {
1000 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1001 let fusion_pool = test_fusion_pool(sqrt_price);
1002 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1003
1004 let collateral_token = TOKEN_A;
1005 let position_token = TOKEN_A;
1006 let leverage = 5.0;
1007 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1008 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1009 let available_balance = 1_000_000_000;
1010
1011 let tradable_amount = get_tradable_amount(
1012 collateral_token,
1013 available_balance,
1014 leverage,
1015 position_token,
1016 0,
1017 protocol_fee_rate,
1018 protocol_fee_rate_on_collateral,
1019 fusion_pool,
1020 true,
1021 )
1022 .unwrap();
1023 assert_eq!(tradable_amount, 4729626953);
1024
1025 let quote = get_increase_spot_position_quote(
1026 tradable_amount,
1027 collateral_token,
1028 position_token,
1029 leverage,
1030 Some(0),
1031 protocol_fee_rate,
1032 protocol_fee_rate_on_collateral,
1033 fusion_pool,
1034 tick_arrays,
1035 )
1036 .unwrap();
1037 assert_eq!(quote.collateral, available_balance);
1038 }
1040
1041 #[tokio::test]
1042 async fn tradable_amount_for_5x_short_position_providing_b() {
1043 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1044 let fusion_pool = test_fusion_pool(sqrt_price);
1045 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1046
1047 let collateral_token = TOKEN_B;
1048 let position_token = TOKEN_B;
1049 let leverage = 5.0;
1050 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1051 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1052 let available_balance = 200_000_000;
1053
1054 let tradable_amount = get_tradable_amount(
1055 collateral_token,
1056 available_balance,
1057 leverage,
1058 position_token,
1059 0,
1060 protocol_fee_rate,
1061 protocol_fee_rate_on_collateral,
1062 fusion_pool,
1063 true,
1064 )
1065 .unwrap();
1066 assert_eq!(tradable_amount, 945925390);
1067
1068 let quote = get_increase_spot_position_quote(
1069 tradable_amount,
1070 collateral_token,
1071 position_token,
1072 leverage,
1073 Some(0),
1074 protocol_fee_rate,
1075 protocol_fee_rate_on_collateral,
1076 fusion_pool,
1077 tick_arrays,
1078 )
1079 .unwrap();
1080 assert_eq!(quote.collateral, available_balance + 1);
1082 }
1084
1085 #[tokio::test]
1086 async fn tradable_amount_for_reducing_existing_long_position() {
1087 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1088 let fusion_pool = test_fusion_pool(sqrt_price);
1089 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1090
1091 for i in 0..2 {
1092 let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1093 let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1094 let leverage = 5.0;
1095 let position_amount = 5_000_000_000;
1096 let position_debt = 800_000_000;
1097 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1098 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1099 let available_balance = 50_000_000_000;
1100
1101 let tradable_amount = get_tradable_amount(
1102 collateral_token,
1103 available_balance,
1104 leverage,
1105 position_token,
1106 position_amount,
1107 protocol_fee_rate,
1108 protocol_fee_rate_on_collateral,
1109 fusion_pool,
1110 false,
1111 )
1112 .unwrap();
1113 assert_eq!(tradable_amount, 5_000_000_000);
1114
1115 let quote = get_decrease_spot_position_quote(
1116 tradable_amount,
1117 collateral_token,
1118 5.0,
1119 Some(0),
1120 position_token,
1121 position_amount,
1122 position_debt,
1123 fusion_pool,
1124 tick_arrays.clone(),
1125 )
1126 .unwrap();
1127
1128 assert_eq!(quote.estimated_amount, 0);
1129 }
1130 }
1131}