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