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 price_impact = if swap_input_token_is_a {
155 (swap_input_amount as f64 - swap_output_amount as f64 / price) / swap_input_amount as f64
156 } else {
157 (swap_input_amount as f64 - swap_output_amount as f64 * price) / swap_input_amount as f64
158 }
159 }
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 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 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 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 price_impact = if swap_input_amount > 0 {
342 if position_token == TOKEN_A {
343 (swap_input_amount as f64 - swap_output_amount as f64 / price) / swap_input_amount as f64
344 } else {
345 (swap_input_amount as f64 - swap_output_amount as f64 * price) / swap_input_amount as f64
346 }
347 } else {
348 0.0
349 };
350
351 Ok(DecreaseSpotPositionQuoteResult {
352 decrease_percent,
353 estimated_payable_debt,
354 estimated_collateral_to_be_withdrawn,
355 swap_input_amount,
356 swap_output_amount,
357 required_swap_amount,
358 estimated_amount,
359 price_impact,
360 })
361}
362
363#[cfg_attr(feature = "wasm", wasm_expose)]
428pub fn get_spot_position_liquidation_price(position_token: u8, amount: u64, debt: u64, liquidation_threshold: u32) -> Result<f64, CoreError> {
429 if liquidation_threshold >= HUNDRED_PERCENT {
430 return Err(INVALID_ARGUMENTS);
431 }
432
433 if debt == 0 || amount == 0 {
434 return Ok(0.0);
435 }
436
437 let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
438
439 if position_token == TOKEN_A {
440 Ok(debt as f64 / (amount as f64 * liquidation_threshold_f))
441 } else {
442 Ok((amount as f64 * liquidation_threshold_f) / debt as f64)
443 }
444}
445
446#[cfg_attr(feature = "wasm", wasm_expose)]
462pub fn get_tradable_amount(
463 collateral_token: u8,
464 available_balance: u64,
465 leverage: f64,
466 position_token: u8,
467 position_amount: u64,
468 protocol_fee_rate: u16,
469 protocol_fee_rate_on_collateral: u16,
470 fusion_pool: FusionPoolFacade,
471 increase: bool,
472) -> Result<u64, CoreError> {
473 if collateral_token > TOKEN_B || position_token > TOKEN_B {
474 return Err(INVALID_ARGUMENTS.into());
475 }
476
477 if leverage < 1.0 {
478 return Err(INVALID_ARGUMENTS.into());
479 }
480
481 let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
485 let mut collateral = fees::apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
486 if collateral_token != position_token {
487 collateral = fees::apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
488 }
489
490 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);
491 let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
492 Ok(total)
493 };
494
495 let available_to_trade = if increase {
496 add_leverage(available_balance)?
497 } else {
498 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
499 let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
500
501 if collateral_token == position_token {
502 position_amount
503 } else {
504 round(position_amount as f64 * position_to_opposite_token_price) as u64
505 }
506 };
507
508 Ok(available_to_trade)
509}
510
511#[cfg_attr(feature = "wasm", wasm_expose)]
573pub fn calculate_tuna_spot_position_protocol_fee(
574 collateral_token: u8,
575 borrowed_token: u8,
576 collateral: u64,
577 borrow: u64,
578 protocol_fee_rate_on_collateral: u16,
579 protocol_fee_rate: u16,
580) -> TokenPair {
581 let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
582 let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
583 let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
584 let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
585
586 let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, protocol_fee_rate_on_collateral, protocol_fee_rate);
587 let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, protocol_fee_rate_on_collateral, protocol_fee_rate);
588
589 TokenPair {
590 a: protocol_fee_a,
591 b: protocol_fee_b,
592 }
593}
594
595#[cfg(all(test, not(feature = "wasm")))]
596mod tests {
597 use super::*;
598 use crate::assert_approx_eq;
599 use fusionamm_core::{
600 get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
601 };
602
603 fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
604 let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
605 FusionPoolFacade {
606 tick_current_index,
607 fee_rate: 3000,
608 liquidity: 10000000000000,
609 sqrt_price,
610 tick_spacing: 2,
611 ..FusionPoolFacade::default()
612 }
613 }
614
615 fn test_tick(liquidity_net: i128) -> TickFacade {
616 TickFacade {
617 initialized: true,
618 liquidity_net,
619 ..TickFacade::default()
620 }
621 }
622
623 fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
624 TickArrayFacade {
625 start_tick_index,
626 ticks: [test_tick(0); TICK_ARRAY_SIZE],
627 }
628 }
629
630 fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
631 let tick_spacing = fusion_pool.tick_spacing;
632 let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
633 let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
634
635 [
636 test_tick_array(tick_array_start_index),
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 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
640 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
641 ]
642 .into()
643 }
644
645 #[test]
646 fn test_get_liquidation_price() {
647 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 0, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
648 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 0, 5, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
649 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 800, HUNDRED_PERCENT * 85 / 100), Ok(188.23529411764707));
650 assert_eq!(get_spot_position_liquidation_price(TOKEN_B, 1000, 4, HUNDRED_PERCENT * 85 / 100), Ok(212.5));
651 }
652
653 #[tokio::test]
654 async fn increase_long_position_providing_token_a() {
655 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
656 let fusion_pool = test_fusion_pool(sqrt_price);
657
658 let quote = get_increase_spot_position_quote(
659 5_000_000_000,
660 TOKEN_A,
661 TOKEN_A,
662 5.0,
663 Some(0),
664 (HUNDRED_PERCENT / 100) as u16,
665 (HUNDRED_PERCENT / 200) as u16,
666 fusion_pool,
667 Some(test_tick_arrays(fusion_pool)),
668 )
669 .unwrap();
670
671 assert_eq!(quote.collateral, 1057165829);
672 assert_eq!(quote.borrow, 800000000);
673 assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
674 assert_eq!(quote.estimated_amount, 4_999_303_011);
675 assert_eq!(quote.protocol_fee_a, 5285829);
676 assert_eq!(quote.protocol_fee_b, 8000000);
677 assert_eq!(quote.price_impact, 0.0031760073232324137);
678 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);
679 }
680
681 #[tokio::test]
682 async fn increase_long_position_providing_token_b() {
683 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
684 let fusion_pool = test_fusion_pool(sqrt_price);
685
686 let quote = get_increase_spot_position_quote(
687 5000_000_000,
688 TOKEN_B,
689 TOKEN_A,
690 5.0,
691 Some(0),
692 (HUNDRED_PERCENT / 100) as u16,
693 (HUNDRED_PERCENT / 200) as u16,
694 fusion_pool,
695 Some(test_tick_arrays(fusion_pool)),
696 )
697 .unwrap();
698
699 assert_eq!(quote.collateral, 1060346869);
700 assert_eq!(quote.borrow, 4000000000);
701 assert_eq!(quote.estimated_amount, 24_972_080_293);
702 assert_eq!(quote.protocol_fee_a, 0);
703 assert_eq!(quote.protocol_fee_b, 45301734);
704 assert_eq!(quote.price_impact, 0.004113437834493303);
705 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);
706 }
707
708 #[tokio::test]
709 async fn increase_short_position_providing_a() {
710 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
711 let fusion_pool = test_fusion_pool(sqrt_price);
712
713 let quote = get_increase_spot_position_quote(
714 5_000_000_000,
715 TOKEN_A,
716 TOKEN_B,
717 5.0,
718 Some(0),
719 (HUNDRED_PERCENT / 100) as u16,
720 (HUNDRED_PERCENT / 200) as u16,
721 fusion_pool,
722 Some(test_tick_arrays(fusion_pool)),
723 )
724 .unwrap();
725
726 assert_eq!(quote.collateral, 1060346869);
727 assert_eq!(quote.borrow, 4000000000);
728 assert_eq!(quote.estimated_amount, 999_776_441);
729 assert_eq!(quote.protocol_fee_a, 45301734);
730 assert_eq!(quote.protocol_fee_b, 0);
731 assert_eq!(quote.price_impact, 0.003222888242261054);
732 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);
733 }
734
735 #[tokio::test]
736 async fn increase_short_position_providing_b() {
737 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
738 let fusion_pool = test_fusion_pool(sqrt_price);
739
740 let quote = get_increase_spot_position_quote(
741 5000_000_000,
742 TOKEN_B,
743 TOKEN_B,
744 5.0,
745 Some(0),
746 (HUNDRED_PERCENT / 100) as u16,
747 (HUNDRED_PERCENT / 200) as u16,
748 fusion_pool,
749 Some(test_tick_arrays(fusion_pool)),
750 )
751 .unwrap();
752
753 assert_eq!(quote.collateral, 1057165829);
754 assert_eq!(quote.borrow, 20000000000);
755 assert_eq!(quote.estimated_amount, 4996_517_564);
756 assert_eq!(quote.protocol_fee_a, 200000000);
757 assert_eq!(quote.protocol_fee_b, 5285829);
758 assert_eq!(quote.price_impact, 0.0038794030303030305);
759 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);
760 }
761
762 #[tokio::test]
763 async fn increase_quote_with_slippage() {
764 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
765 let fusion_pool = test_fusion_pool(sqrt_price);
766
767 let quote =
769 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)))
770 .unwrap();
771 assert_eq!(quote.min_swap_output_amount, 899_994);
772
773 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)))
775 .unwrap();
776 assert_eq!(quote.min_swap_output_amount, 999_994);
777 }
778
779 #[tokio::test]
780 async fn decrease_non_leveraged_long_position_providing_a() {
781 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
782 let fusion_pool = test_fusion_pool(sqrt_price);
783
784 let quote = get_decrease_spot_position_quote(
785 1_000_000_000,
786 TOKEN_A,
787 1.0,
788 Some(0),
789 TOKEN_A,
790 5_000_000_000, 0, fusion_pool,
793 Some(test_tick_arrays(fusion_pool)),
794 )
795 .unwrap();
796
797 assert_eq!(quote.decrease_percent, 200000);
798 assert_eq!(quote.estimated_amount, 4_000_000_000);
799 assert_eq!(quote.estimated_payable_debt, 0);
800 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 1_000_000_000);
801 assert_eq!(quote.price_impact, 0.0);
802 }
803
804 #[tokio::test]
805 async fn decrease_non_leveraged_long_position_providing_b() {
806 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
807 let fusion_pool = test_fusion_pool(sqrt_price);
808
809 let quote = get_decrease_spot_position_quote(
810 200_000_000,
811 TOKEN_B,
812 1.0,
813 Some(0),
814 TOKEN_A,
815 5_000_000_000, 0, fusion_pool,
818 Some(test_tick_arrays(fusion_pool)),
819 )
820 .unwrap();
821
822 assert_eq!(quote.decrease_percent, 200000);
823 assert_eq!(quote.estimated_amount, 4_000_000_000);
824 assert_eq!(quote.estimated_payable_debt, 0);
825 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 199_391_108);
826 assert_eq!(quote.price_impact, 0.003044459999999881);
827 }
828
829 #[tokio::test]
830 async fn decrease_long_position_providing_a() {
831 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
832 let fusion_pool = test_fusion_pool(sqrt_price);
833
834 let quote = get_decrease_spot_position_quote(
835 1_000_000_000,
836 TOKEN_A,
837 5.0,
838 Some(0),
839 TOKEN_A,
840 5_000_000_000, 800_000_000, fusion_pool,
843 Some(test_tick_arrays(fusion_pool)),
844 )
845 .unwrap();
846
847 assert_eq!(quote.decrease_percent, 200000);
848 assert_eq!(quote.estimated_amount, 4_000_000_000);
849 assert_eq!(quote.estimated_payable_debt, 160_000_000);
850 assert_eq!(quote.swap_input_amount, 802_435_931);
851 assert_eq!(quote.swap_output_amount, 160_000_000);
852 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 197_564_069);
853 assert_eq!(quote.price_impact, 0.0030356703954722095);
854
855 let quote = get_decrease_spot_position_quote(
856 6_000_000_000,
857 TOKEN_A,
858 5.0,
859 Some(0),
860 TOKEN_A,
861 5_000_000_000, 800_000_000, fusion_pool,
864 Some(test_tick_arrays(fusion_pool)),
865 )
866 .unwrap();
867
868 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
869 assert_eq!(quote.estimated_amount, 0);
870 }
871
872 #[tokio::test]
873 async fn decrease_long_position_providing_b() {
874 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
875 let fusion_pool = test_fusion_pool(sqrt_price);
876
877 let quote = get_decrease_spot_position_quote(
878 200_000_000,
879 TOKEN_B,
880 5.0,
881 Some(0),
882 TOKEN_A,
883 5_000_000_000, 800_000_000, fusion_pool,
886 Some(test_tick_arrays(fusion_pool)),
887 )
888 .unwrap();
889
890 assert_eq!(quote.estimated_amount, 4000000000);
891 assert_eq!(quote.decrease_percent, 200000);
892 assert_eq!(quote.estimated_payable_debt, 160_000_000);
893 assert_eq!(quote.swap_input_amount, 1_000_000_000);
894 assert_eq!(quote.swap_output_amount, 199_391_108);
895 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 39_391_108);
896 assert_eq!(quote.price_impact, 0.003044459999999881);
897
898 let quote = get_decrease_spot_position_quote(
899 1200_000_000,
900 TOKEN_B,
901 5.0,
902 Some(0),
903 TOKEN_A,
904 5_000_000_000, 800_000_000, fusion_pool,
907 Some(test_tick_arrays(fusion_pool)),
908 )
909 .unwrap();
910
911 assert_eq!(quote.estimated_amount, 0);
912 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
913 }
914
915 #[tokio::test]
916 async fn tradable_amount_for_1x_long_position_providing_b() {
917 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
918 let fusion_pool = test_fusion_pool(sqrt_price);
919 let tick_arrays = Some(test_tick_arrays(fusion_pool));
920
921 let collateral_token = TOKEN_B;
922 let position_token = TOKEN_A;
923 let leverage = 1.0;
924 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
925 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
926 let available_balance = 200_000_000;
927
928 let tradable_amount = get_tradable_amount(
929 collateral_token,
930 available_balance,
931 leverage,
932 position_token,
933 0,
934 protocol_fee_rate,
935 protocol_fee_rate_on_collateral,
936 fusion_pool,
937 true,
938 )
939 .unwrap();
940 assert_eq!(tradable_amount, 198403000);
941
942 let quote = get_increase_spot_position_quote(
943 tradable_amount,
944 collateral_token,
945 position_token,
946 leverage,
947 Some(0),
948 protocol_fee_rate,
949 protocol_fee_rate_on_collateral,
950 fusion_pool,
951 tick_arrays,
952 )
953 .unwrap();
954 assert_eq!(quote.collateral, available_balance);
955 }
956
957 #[tokio::test]
958 async fn tradable_amount_for_5x_long_position_providing_b() {
959 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
960 let fusion_pool = test_fusion_pool(sqrt_price);
961 let tick_arrays = Some(test_tick_arrays(fusion_pool));
962
963 let collateral_token = TOKEN_B;
964 let position_token = TOKEN_A;
965 let leverage = 5.0;
966 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
967 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
968 let available_balance = 10_000_000;
969
970 let tradable_amount = get_tradable_amount(
971 collateral_token,
972 available_balance,
973 leverage,
974 position_token,
975 0,
976 protocol_fee_rate,
977 protocol_fee_rate_on_collateral,
978 fusion_pool,
979 true,
980 )
981 .unwrap();
982 assert_eq!(tradable_amount, 47154380);
983
984 let quote = get_increase_spot_position_quote(
985 tradable_amount,
986 collateral_token,
987 position_token,
988 leverage,
989 Some(0),
990 protocol_fee_rate,
991 protocol_fee_rate_on_collateral,
992 fusion_pool,
993 tick_arrays,
994 )
995 .unwrap();
996 assert_eq!(quote.collateral, available_balance + 1);
998 }
999
1000 #[tokio::test]
1001 async fn tradable_amount_for_5x_long_position_providing_a() {
1002 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1003 let fusion_pool = test_fusion_pool(sqrt_price);
1004 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1005
1006 let collateral_token = TOKEN_A;
1007 let position_token = TOKEN_A;
1008 let leverage = 5.0;
1009 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1010 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1011 let available_balance = 1_000_000_000;
1012
1013 let tradable_amount = get_tradable_amount(
1014 collateral_token,
1015 available_balance,
1016 leverage,
1017 position_token,
1018 0,
1019 protocol_fee_rate,
1020 protocol_fee_rate_on_collateral,
1021 fusion_pool,
1022 true,
1023 )
1024 .unwrap();
1025 assert_eq!(tradable_amount, 4729626953);
1026
1027 let quote = get_increase_spot_position_quote(
1028 tradable_amount,
1029 collateral_token,
1030 position_token,
1031 leverage,
1032 Some(0),
1033 protocol_fee_rate,
1034 protocol_fee_rate_on_collateral,
1035 fusion_pool,
1036 tick_arrays,
1037 )
1038 .unwrap();
1039 assert_eq!(quote.collateral, available_balance);
1040 }
1042
1043 #[tokio::test]
1044 async fn tradable_amount_for_5x_short_position_providing_b() {
1045 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1046 let fusion_pool = test_fusion_pool(sqrt_price);
1047 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1048
1049 let collateral_token = TOKEN_B;
1050 let position_token = TOKEN_B;
1051 let leverage = 5.0;
1052 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1053 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1054 let available_balance = 200_000_000;
1055
1056 let tradable_amount = get_tradable_amount(
1057 collateral_token,
1058 available_balance,
1059 leverage,
1060 position_token,
1061 0,
1062 protocol_fee_rate,
1063 protocol_fee_rate_on_collateral,
1064 fusion_pool,
1065 true,
1066 )
1067 .unwrap();
1068 assert_eq!(tradable_amount, 945925390);
1069
1070 let quote = get_increase_spot_position_quote(
1071 tradable_amount,
1072 collateral_token,
1073 position_token,
1074 leverage,
1075 Some(0),
1076 protocol_fee_rate,
1077 protocol_fee_rate_on_collateral,
1078 fusion_pool,
1079 tick_arrays,
1080 )
1081 .unwrap();
1082 assert_eq!(quote.collateral, available_balance + 1);
1084 }
1086
1087 #[tokio::test]
1088 async fn tradable_amount_for_reducing_existing_long_position() {
1089 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1090 let fusion_pool = test_fusion_pool(sqrt_price);
1091 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1092
1093 for i in 0..2 {
1094 let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1095 let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1096 let leverage = 5.0;
1097 let position_amount = 5_000_000_000;
1098 let position_debt = 800_000_000;
1099 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1100 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1101 let available_balance = 50_000_000_000;
1102
1103 let tradable_amount = get_tradable_amount(
1104 collateral_token,
1105 available_balance,
1106 leverage,
1107 position_token,
1108 position_amount,
1109 protocol_fee_rate,
1110 protocol_fee_rate_on_collateral,
1111 fusion_pool,
1112 false,
1113 )
1114 .unwrap();
1115 assert_eq!(tradable_amount, 5_000_000_000);
1116
1117 let quote = get_decrease_spot_position_quote(
1118 tradable_amount,
1119 collateral_token,
1120 5.0,
1121 Some(0),
1122 position_token,
1123 position_amount,
1124 position_debt,
1125 fusion_pool,
1126 tick_arrays.clone(),
1127 )
1128 .unwrap();
1129
1130 assert_eq!(quote.estimated_amount, 0);
1131 }
1132 }
1133}