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")]
7#[cfg(feature = "wasm")]
11use wasm_bindgen::prelude::wasm_bindgen;
12use crate::{
16 apply_swap_fee, apply_tuna_protocol_fee, calculate_tuna_protocol_fee, reverse_apply_swap_fee, reverse_apply_tuna_protocol_fee, HUNDRED_PERCENT,
17 INVALID_ARGUMENTS, TOKEN_A, TOKEN_B,
18};
19use fusionamm_core::{
20 sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_get_max_amount_with_slippage_tolerance,
21 try_get_min_amount_with_slippage_tolerance, try_mul_div, CoreError, FusionPoolFacade, TickArrays, TokenPair,
22};
23use libm::{ceil, round};
24
25pub const DEFAULT_SLIPPAGE_TOLERANCE_BPS: u16 = 100;
26
27#[cfg_attr(feature = "wasm", wasm_expose)]
37pub struct IncreaseSpotPositionQuoteResult {
38 pub collateral: u64,
40 pub borrow: u64,
42 pub estimated_amount: u64,
44 pub swap_input_amount: u64,
46 pub swap_output_amount: u64,
48 pub min_swap_output_amount: u64,
50 pub protocol_fee_a: u64,
52 pub protocol_fee_b: u64,
54 pub price_impact: f64,
56}
57
58#[cfg_attr(feature = "wasm", wasm_expose)]
59pub struct IncreaseSpotPositionEstimationResult {
60 pub collateral: u64,
62 pub borrow: u64,
64 pub estimated_amount: u64,
66 pub swap_input_amount: u64,
68 pub protocol_fee_a: u64,
70 pub protocol_fee_b: u64,
72}
73
74struct SwapPool {
75 pub fusion_pool: FusionPoolFacade,
76 pub tick_arrays: TickArrays,
77 pub slippage_bps: Option<u16>,
78}
79
80fn get_increase_spot_position_quote_internal(
81 increase_amount: u64,
82 collateral_token: u8,
83 position_token: u8,
84 leverage: f64,
85 protocol_fee_rate: u16,
86 protocol_fee_rate_on_collateral: u16,
87 price: f64,
88 pool: Option<SwapPool>,
89) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
90 if collateral_token > TOKEN_B || position_token > TOKEN_B {
91 return Err(INVALID_ARGUMENTS.into());
92 }
93
94 if leverage < 1.0 {
95 return Err(INVALID_ARGUMENTS.into());
96 }
97
98 let borrow: u64;
99 let mut collateral: u64;
100 let mut estimated_amount: u64 = 0;
101 let mut swap_input_amount: u64;
102 let mut swap_output_amount: u64 = 0;
103 let mut min_swap_output_amount: u64 = 0;
104 let mut price_impact: f64 = 0.0;
105
106 let (fee_rate, slippage_bps) = match pool.as_ref() {
107 None => (0, 0),
108 Some(pool) => (pool.fusion_pool.fee_rate, pool.slippage_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS)),
109 };
110
111 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
112 let swap_input_token_is_a = borrowed_token == TOKEN_A;
113
114 if borrowed_token == collateral_token {
115 borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
116 collateral = increase_amount - apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fee_rate, false)?;
117 collateral = reverse_apply_swap_fee(collateral, fee_rate, false)?;
118 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
119
120 swap_input_amount = collateral + borrow;
121 } else {
122 let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
123 let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
124
125 borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
126
127 let borrow_in_position_token_with_fees_applied =
128 apply_swap_fee(apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?, fee_rate, false)?;
129
130 collateral = increase_amount - borrow_in_position_token_with_fees_applied;
131 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
132
133 swap_input_amount = borrow;
134 }
135
136 let protocol_fee = calculate_tuna_spot_position_protocol_fee(
137 collateral_token,
138 borrowed_token,
139 collateral,
140 borrow,
141 protocol_fee_rate_on_collateral,
142 protocol_fee_rate,
143 );
144
145 swap_input_amount -= if swap_input_token_is_a { protocol_fee.a } else { protocol_fee.b };
146
147 if position_token == collateral_token {
148 estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee.a } else { protocol_fee.b };
149 }
150
151 if swap_input_amount > 0 {
152 if let Some(pool) = pool {
153 let quote = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
154 estimated_amount += quote.token_est_out;
155 swap_output_amount = quote.token_est_out;
156 min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(swap_output_amount, slippage_bps)?;
157 price_impact = if swap_input_token_is_a {
160 (swap_input_amount as f64 - swap_output_amount as f64 / price) / swap_input_amount as f64
161 } else {
162 (swap_input_amount as f64 - swap_output_amount as f64 * price) / swap_input_amount as f64
163 }
164 }
165 }
166
167 Ok(IncreaseSpotPositionQuoteResult {
168 collateral,
169 borrow,
170 estimated_amount,
171 swap_input_amount,
172 swap_output_amount,
173 min_swap_output_amount,
174 protocol_fee_a: protocol_fee.a,
175 protocol_fee_b: protocol_fee.b,
176 price_impact,
177 })
178}
179
180#[cfg_attr(feature = "wasm", wasm_expose)]
196pub fn get_increase_spot_position_quote(
197 increase_amount: u64,
198 collateral_token: u8,
199 position_token: u8,
200 leverage: f64,
201 slippage_bps: Option<u16>,
202 protocol_fee_rate: u16,
203 protocol_fee_rate_on_collateral: u16,
204 fusion_pool: FusionPoolFacade,
205 tick_arrays: TickArrays,
206) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
207 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
208
209 get_increase_spot_position_quote_internal(
210 increase_amount,
211 collateral_token,
212 position_token,
213 leverage,
214 protocol_fee_rate,
215 protocol_fee_rate_on_collateral,
216 price,
217 Some(SwapPool {
218 fusion_pool,
219 tick_arrays,
220 slippage_bps,
221 }),
222 )
223}
224
225#[cfg_attr(feature = "wasm", wasm_expose)]
239pub fn get_increase_spot_position_estimation(
240 increase_amount: u64,
241 collateral_token: u8,
242 position_token: u8,
243 leverage: f64,
244 protocol_fee_rate: u16,
245 protocol_fee_rate_on_collateral: u16,
246 price: f64,
247) -> Result<IncreaseSpotPositionEstimationResult, CoreError> {
248 let quote = get_increase_spot_position_quote_internal(
249 increase_amount,
250 collateral_token,
251 position_token,
252 leverage,
253 protocol_fee_rate,
254 protocol_fee_rate_on_collateral,
255 price,
256 None,
257 )?;
258
259 Ok(IncreaseSpotPositionEstimationResult {
260 collateral: quote.collateral,
261 borrow: quote.borrow,
262 estimated_amount: quote.estimated_amount,
263 swap_input_amount: quote.swap_input_amount,
264 protocol_fee_a: quote.protocol_fee_a,
265 protocol_fee_b: quote.protocol_fee_b,
266 })
267}
268
269#[cfg_attr(feature = "wasm", wasm_expose)]
326pub struct DecreaseSpotPositionQuoteResult {
327 pub decrease_percent: u32,
329 pub swap_exact_in: bool,
331 pub swap_input_amount: u64,
333 pub swap_output_amount: u64,
335 pub required_swap_amount: u64,
340 pub estimated_amount: u64,
342 pub estimated_payable_debt: u64,
344 pub estimated_collateral_to_be_withdrawn: u64,
346 pub price_impact: f64,
348}
349
350#[cfg_attr(feature = "wasm", wasm_expose)]
351pub struct DecreaseSpotPositionEstimationResult {
352 pub decrease_percent: u32,
354 pub swap_exact_in: bool,
356 pub swap_amount: u64,
358 pub estimated_amount: u64,
360 pub estimated_payable_debt: u64,
362}
363
364fn get_decrease_spot_position_quote_internal(
365 decrease_amount: u64,
366 collateral_token: u8,
367 position_token: u8,
368 position_amount: u64,
369 position_debt: u64,
370 price: f64,
371 pool: Option<SwapPool>,
372) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
373 if collateral_token > TOKEN_B || position_token > TOKEN_B {
374 return Err(INVALID_ARGUMENTS.into());
375 }
376
377 let slippage_bps = match pool.as_ref() {
378 None => 0,
379 Some(pool) => pool.slippage_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS),
380 };
381
382 let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
383 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
384
385 let mut required_swap_amount: u64 = 0;
386
387 let mut decrease_amount_in_position_token = if collateral_token == position_token {
388 decrease_amount
389 } else {
390 round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
391 };
392
393 decrease_amount_in_position_token = position_amount.min(decrease_amount_in_position_token);
394
395 let decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
396
397 let estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
398 let estimated_payable_debt = try_mul_div(position_debt, decrease_percent as u128, HUNDRED_PERCENT as u128, true)?;
399 let mut estimated_collateral_to_be_withdrawn = 0;
400
401 let swap_exact_in: bool;
403 let mut swap_input_amount = 0;
404 let mut swap_output_amount = 0;
405
406 if collateral_token == position_token {
407 swap_exact_in = false;
408 if position_debt > 0 {
409 swap_output_amount = estimated_payable_debt;
410 if let Some(pool) = pool {
411 let swap =
412 swap_quote_by_output_token(swap_output_amount, borrowed_token == TOKEN_A, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
413 swap_input_amount = swap.token_est_in;
414 required_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_bps)?;
416 estimated_collateral_to_be_withdrawn = position_amount.saturating_sub(swap.token_est_in).saturating_sub(estimated_amount);
417 }
418 } else {
419 estimated_collateral_to_be_withdrawn = position_amount - estimated_amount;
420 }
421 } else {
422 swap_exact_in = true;
423 swap_input_amount = position_amount - estimated_amount;
424 if let Some(pool) = pool {
425 let swap = swap_quote_by_input_token(swap_input_amount, position_token == TOKEN_A, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
426 swap_output_amount = swap.token_est_out;
428 required_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_bps)?;
429 estimated_collateral_to_be_withdrawn = swap.token_est_out.saturating_sub(estimated_payable_debt);
430 }
431 }
432
433 let price_impact = if swap_input_amount > 0 {
437 if position_token == TOKEN_A {
438 (swap_input_amount as f64 - swap_output_amount as f64 / price) / swap_input_amount as f64
439 } else {
440 (swap_input_amount as f64 - swap_output_amount as f64 * price) / swap_input_amount as f64
441 }
442 } else {
443 0.0
444 };
445
446 Ok(DecreaseSpotPositionQuoteResult {
447 decrease_percent,
448 estimated_payable_debt,
449 estimated_collateral_to_be_withdrawn,
450 swap_exact_in,
451 swap_input_amount,
452 swap_output_amount,
453 required_swap_amount,
454 estimated_amount,
455 price_impact,
456 })
457}
458
459#[cfg_attr(feature = "wasm", wasm_expose)]
474pub fn get_decrease_spot_position_quote(
475 decrease_amount: u64,
476 collateral_token: u8,
477 slippage_bps: Option<u16>,
478 position_token: u8,
479 position_amount: u64,
480 position_debt: u64,
481 fusion_pool: FusionPoolFacade,
482 tick_arrays: TickArrays,
483) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
484 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
485 get_decrease_spot_position_quote_internal(
486 decrease_amount,
487 collateral_token,
488 position_token,
489 position_amount,
490 position_debt,
491 price,
492 Some(SwapPool {
493 fusion_pool,
494 tick_arrays,
495 slippage_bps,
496 }),
497 )
498}
499
500#[cfg_attr(feature = "wasm", wasm_expose)]
513pub fn get_decrease_spot_position_estimation(
514 decrease_amount: u64,
515 collateral_token: u8,
516 position_token: u8,
517 position_amount: u64,
518 position_debt: u64,
519 price: f64,
520) -> Result<DecreaseSpotPositionEstimationResult, CoreError> {
521 let quote =
522 get_decrease_spot_position_quote_internal(decrease_amount, collateral_token, position_token, position_amount, position_debt, price, None)?;
523
524 Ok(DecreaseSpotPositionEstimationResult {
525 decrease_percent: quote.decrease_percent,
526 swap_exact_in: quote.swap_exact_in,
527 swap_amount: if quote.swap_exact_in {
528 quote.swap_input_amount
529 } else {
530 quote.swap_output_amount
531 },
532 estimated_amount: quote.estimated_amount,
533 estimated_payable_debt: quote.estimated_payable_debt,
534 })
535}
536
537#[cfg_attr(feature = "wasm", wasm_expose)]
602pub fn get_spot_position_liquidation_price(position_token: u8, amount: u64, debt: u64, liquidation_threshold: u32) -> Result<f64, CoreError> {
603 if liquidation_threshold >= HUNDRED_PERCENT {
604 return Err(INVALID_ARGUMENTS);
605 }
606
607 if debt == 0 || amount == 0 {
608 return Ok(0.0);
609 }
610
611 let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
612
613 if position_token == TOKEN_A {
614 Ok(debt as f64 / (amount as f64 * liquidation_threshold_f))
615 } else {
616 Ok((amount as f64 * liquidation_threshold_f) / debt as f64)
617 }
618}
619
620#[cfg_attr(feature = "wasm", wasm_expose)]
637pub fn get_tradable_amount(
638 collateral_token: u8,
639 available_balance: u64,
640 leverage: f64,
641 position_token: u8,
642 position_amount: u64,
643 protocol_fee_rate: u16,
644 protocol_fee_rate_on_collateral: u16,
645 swap_fee_rate: u16,
646 price: f64,
647 increase: bool,
648) -> Result<u64, CoreError> {
649 if collateral_token > TOKEN_B || position_token > TOKEN_B {
650 return Err(INVALID_ARGUMENTS.into());
651 }
652
653 if leverage < 1.0 {
654 return Err(INVALID_ARGUMENTS.into());
655 }
656
657 let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
661 let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
662 if collateral_token != position_token {
663 collateral = apply_swap_fee(collateral, swap_fee_rate, false)?;
664 }
665
666 let fee_multiplier = (1.0 - protocol_fee_rate as f64 / HUNDRED_PERCENT as f64) * (1.0 - swap_fee_rate as f64 / 1_000_000.0);
667 let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
668 Ok(total)
669 };
670
671 let available_to_trade = if increase {
672 add_leverage(available_balance)?
673 } else {
674 let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
675
676 if collateral_token == position_token {
677 position_amount
678 } else {
679 round(position_amount as f64 * position_to_opposite_token_price) as u64
680 }
681 };
682
683 Ok(available_to_trade)
684}
685
686#[cfg_attr(feature = "wasm", wasm_expose)]
748pub fn calculate_tuna_spot_position_protocol_fee(
749 collateral_token: u8,
750 borrowed_token: u8,
751 collateral: u64,
752 borrow: u64,
753 protocol_fee_rate_on_collateral: u16,
754 protocol_fee_rate: u16,
755) -> TokenPair {
756 let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
757 let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
758 let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
759 let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
760
761 let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, protocol_fee_rate_on_collateral, protocol_fee_rate);
762 let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, protocol_fee_rate_on_collateral, protocol_fee_rate);
763
764 TokenPair {
765 a: protocol_fee_a,
766 b: protocol_fee_b,
767 }
768}
769
770#[cfg(all(test, not(feature = "wasm")))]
771mod tests {
772 use super::*;
773 use crate::assert_approx_eq;
774 use fusionamm_core::{
775 get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
776 };
777
778 fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
779 let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
780 FusionPoolFacade {
781 tick_current_index,
782 fee_rate: 3000,
783 liquidity: 10000000000000,
784 sqrt_price,
785 tick_spacing: 2,
786 ..FusionPoolFacade::default()
787 }
788 }
789
790 fn test_tick(liquidity_net: i128) -> TickFacade {
791 TickFacade {
792 initialized: true,
793 liquidity_net,
794 ..TickFacade::default()
795 }
796 }
797
798 fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
799 TickArrayFacade {
800 start_tick_index,
801 ticks: [test_tick(0); TICK_ARRAY_SIZE],
802 }
803 }
804
805 fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
806 let tick_spacing = fusion_pool.tick_spacing;
807 let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
808 let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
809
810 [
811 test_tick_array(tick_array_start_index),
812 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
813 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
814 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
815 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
816 ]
817 .into()
818 }
819
820 #[test]
821 fn test_get_liquidation_price() {
822 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 0, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
823 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 0, 5, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
824 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 800, HUNDRED_PERCENT * 85 / 100), Ok(188.23529411764707));
825 assert_eq!(get_spot_position_liquidation_price(TOKEN_B, 1000, 4, HUNDRED_PERCENT * 85 / 100), Ok(212.5));
826 }
827
828 #[tokio::test]
829 async fn increase_long_position_providing_token_a() {
830 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
831 let fusion_pool = test_fusion_pool(sqrt_price);
832
833 let quote = get_increase_spot_position_quote(
834 5_000_000_000,
835 TOKEN_A,
836 TOKEN_A,
837 5.0,
838 Some(0),
839 (HUNDRED_PERCENT / 100) as u16,
840 (HUNDRED_PERCENT / 200) as u16,
841 fusion_pool,
842 test_tick_arrays(fusion_pool),
843 )
844 .unwrap();
845
846 assert_eq!(quote.collateral, 1057165829);
847 assert_eq!(quote.borrow, 800000000);
848 assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
849 assert_eq!(quote.estimated_amount, 4_999_303_011);
850 assert_eq!(quote.protocol_fee_a, 5285829);
851 assert_eq!(quote.protocol_fee_b, 8000000);
852 assert_eq!(quote.price_impact, 0.0031760073232324137);
853 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);
854 }
855
856 #[tokio::test]
857 async fn increase_long_position_providing_token_b() {
858 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
859 let fusion_pool = test_fusion_pool(sqrt_price);
860
861 let quote = get_increase_spot_position_quote(
862 5000_000_000,
863 TOKEN_B,
864 TOKEN_A,
865 5.0,
866 Some(0),
867 (HUNDRED_PERCENT / 100) as u16,
868 (HUNDRED_PERCENT / 200) as u16,
869 fusion_pool,
870 test_tick_arrays(fusion_pool),
871 )
872 .unwrap();
873
874 assert_eq!(quote.collateral, 1060346869);
875 assert_eq!(quote.borrow, 4000000000);
876 assert_eq!(quote.estimated_amount, 24_972_080_293);
877 assert_eq!(quote.protocol_fee_a, 0);
878 assert_eq!(quote.protocol_fee_b, 45301734);
879 assert_eq!(quote.price_impact, 0.004113437834493303);
880 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);
881 }
882
883 #[tokio::test]
884 async fn increase_short_position_providing_a() {
885 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
886 let fusion_pool = test_fusion_pool(sqrt_price);
887
888 let quote = get_increase_spot_position_quote(
889 5_000_000_000,
890 TOKEN_A,
891 TOKEN_B,
892 5.0,
893 Some(0),
894 (HUNDRED_PERCENT / 100) as u16,
895 (HUNDRED_PERCENT / 200) as u16,
896 fusion_pool,
897 test_tick_arrays(fusion_pool),
898 )
899 .unwrap();
900
901 assert_eq!(quote.collateral, 1060346869);
902 assert_eq!(quote.borrow, 4000000000);
903 assert_eq!(quote.estimated_amount, 999_776_441);
904 assert_eq!(quote.protocol_fee_a, 45301734);
905 assert_eq!(quote.protocol_fee_b, 0);
906 assert_eq!(quote.price_impact, 0.003222888242261054);
907 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);
908 }
909
910 #[tokio::test]
911 async fn increase_short_position_providing_b() {
912 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
913 let fusion_pool = test_fusion_pool(sqrt_price);
914
915 let quote = get_increase_spot_position_quote(
916 5000_000_000,
917 TOKEN_B,
918 TOKEN_B,
919 5.0,
920 Some(0),
921 (HUNDRED_PERCENT / 100) as u16,
922 (HUNDRED_PERCENT / 200) as u16,
923 fusion_pool,
924 test_tick_arrays(fusion_pool),
925 )
926 .unwrap();
927
928 assert_eq!(quote.collateral, 1057165829);
929 assert_eq!(quote.borrow, 20000000000);
930 assert_eq!(quote.estimated_amount, 4996_517_564);
931 assert_eq!(quote.protocol_fee_a, 200000000);
932 assert_eq!(quote.protocol_fee_b, 5285829);
933 assert_eq!(quote.price_impact, 0.0038794030303030305);
934 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);
935 }
936
937 #[tokio::test]
938 async fn increase_quote_with_slippage() {
939 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
940 let fusion_pool = test_fusion_pool(sqrt_price);
941
942 let quote =
944 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();
945 assert_eq!(quote.min_swap_output_amount, 899_994);
946
947 let quote =
949 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();
950 assert_eq!(quote.min_swap_output_amount, 999_994);
951 }
952
953 #[tokio::test]
954 async fn decrease_non_leveraged_long_position_providing_a() {
955 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
956 let fusion_pool = test_fusion_pool(sqrt_price);
957
958 let quote = get_decrease_spot_position_quote(
959 1_000_000_000,
960 TOKEN_A,
961 Some(0),
962 TOKEN_A,
963 5_000_000_000, 0, fusion_pool,
966 test_tick_arrays(fusion_pool),
967 )
968 .unwrap();
969
970 assert_eq!(quote.decrease_percent, 200000);
971 assert_eq!(quote.estimated_amount, 4_000_000_000);
972 assert_eq!(quote.estimated_payable_debt, 0);
973 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 1_000_000_000);
974 assert_eq!(quote.price_impact, 0.0);
975 }
976
977 #[tokio::test]
978 async fn decrease_non_leveraged_long_position_providing_b() {
979 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
980 let fusion_pool = test_fusion_pool(sqrt_price);
981
982 let quote = get_decrease_spot_position_quote(
983 200_000_000,
984 TOKEN_B,
985 Some(0),
986 TOKEN_A,
987 5_000_000_000, 0, fusion_pool,
990 test_tick_arrays(fusion_pool),
991 )
992 .unwrap();
993
994 assert_eq!(quote.decrease_percent, 200000);
995 assert_eq!(quote.estimated_amount, 4_000_000_000);
996 assert_eq!(quote.estimated_payable_debt, 0);
997 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 199_391_108);
998 assert_eq!(quote.price_impact, 0.003044459999999881);
999 }
1000
1001 #[tokio::test]
1002 async fn decrease_long_position_providing_a() {
1003 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1004 let fusion_pool = test_fusion_pool(sqrt_price);
1005
1006 let quote = get_decrease_spot_position_quote(
1007 1_000_000_000,
1008 TOKEN_A,
1009 Some(0),
1010 TOKEN_A,
1011 5_000_000_000, 800_000_000, fusion_pool,
1014 test_tick_arrays(fusion_pool),
1015 )
1016 .unwrap();
1017
1018 assert_eq!(quote.decrease_percent, 200000);
1019 assert_eq!(quote.estimated_amount, 4_000_000_000);
1020 assert_eq!(quote.estimated_payable_debt, 160_000_000);
1021 assert_eq!(quote.swap_exact_in, false);
1022 assert_eq!(quote.swap_input_amount, 802_435_931);
1023 assert_eq!(quote.swap_output_amount, 160_000_000);
1024 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 197_564_069);
1025 assert_eq!(quote.price_impact, 0.0030356703954722095);
1026
1027 let quote = get_decrease_spot_position_quote(
1028 6_000_000_000,
1029 TOKEN_A,
1030 Some(0),
1031 TOKEN_A,
1032 5_000_000_000, 800_000_000, fusion_pool,
1035 test_tick_arrays(fusion_pool),
1036 )
1037 .unwrap();
1038
1039 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1040 assert_eq!(quote.estimated_amount, 0);
1041 }
1042
1043 #[tokio::test]
1044 async fn decrease_long_position_providing_a_estimation() {
1045 let quote = get_decrease_spot_position_estimation(
1046 1_000_000_000,
1047 TOKEN_A,
1048 TOKEN_A,
1049 5_000_000_000, 800_000_000, 0.2,
1052 )
1053 .unwrap();
1054
1055 assert_eq!(quote.decrease_percent, 200000);
1056 assert_eq!(quote.estimated_amount, 4_000_000_000);
1057 assert_eq!(quote.estimated_payable_debt, 160_000_000);
1058 assert_eq!(quote.swap_exact_in, false);
1059 assert_eq!(quote.swap_amount, 160_000_000);
1060 }
1061
1062 #[tokio::test]
1063 async fn decrease_long_position_providing_b() {
1064 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1065 let fusion_pool = test_fusion_pool(sqrt_price);
1066
1067 let quote = get_decrease_spot_position_quote(
1068 200_000_000,
1069 TOKEN_B,
1070 Some(0),
1071 TOKEN_A,
1072 5_000_000_000, 800_000_000, fusion_pool,
1075 test_tick_arrays(fusion_pool),
1076 )
1077 .unwrap();
1078
1079 assert_eq!(quote.estimated_amount, 4000000000);
1080 assert_eq!(quote.decrease_percent, 200000);
1081 assert_eq!(quote.estimated_payable_debt, 160_000_000);
1082 assert_eq!(quote.swap_exact_in, true);
1083 assert_eq!(quote.swap_input_amount, 1_000_000_000);
1084 assert_eq!(quote.swap_output_amount, 199_391_108);
1085 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 39_391_108);
1086 assert_eq!(quote.price_impact, 0.003044459999999881);
1087
1088 let quote = get_decrease_spot_position_quote(
1089 1200_000_000,
1090 TOKEN_B,
1091 Some(0),
1092 TOKEN_A,
1093 5_000_000_000, 800_000_000, fusion_pool,
1096 test_tick_arrays(fusion_pool),
1097 )
1098 .unwrap();
1099
1100 assert_eq!(quote.estimated_amount, 0);
1101 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1102 }
1103
1104 #[tokio::test]
1105 async fn tradable_amount_for_1x_long_position_providing_b() {
1106 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1107 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1108 let fusion_pool = test_fusion_pool(sqrt_price);
1109 let tick_arrays = test_tick_arrays(fusion_pool);
1110
1111 let collateral_token = TOKEN_B;
1112 let position_token = TOKEN_A;
1113 let leverage = 1.0;
1114 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1115 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1116 let available_balance = 200_000_000;
1117
1118 let tradable_amount = get_tradable_amount(
1119 collateral_token,
1120 available_balance,
1121 leverage,
1122 position_token,
1123 0,
1124 protocol_fee_rate,
1125 protocol_fee_rate_on_collateral,
1126 fusion_pool.fee_rate,
1127 price,
1128 true,
1129 )
1130 .unwrap();
1131 assert_eq!(tradable_amount, 198403000);
1132
1133 let quote = get_increase_spot_position_quote(
1134 tradable_amount,
1135 collateral_token,
1136 position_token,
1137 leverage,
1138 Some(0),
1139 protocol_fee_rate,
1140 protocol_fee_rate_on_collateral,
1141 fusion_pool,
1142 tick_arrays,
1143 )
1144 .unwrap();
1145 assert_eq!(quote.collateral, available_balance);
1146 }
1147
1148 #[tokio::test]
1149 async fn tradable_amount_for_5x_long_position_providing_b() {
1150 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1151 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1152 let fusion_pool = test_fusion_pool(sqrt_price);
1153 let tick_arrays = test_tick_arrays(fusion_pool);
1154
1155 let collateral_token = TOKEN_B;
1156 let position_token = TOKEN_A;
1157 let leverage = 5.0;
1158 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1159 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1160 let available_balance = 10_000_000;
1161
1162 let tradable_amount = get_tradable_amount(
1163 collateral_token,
1164 available_balance,
1165 leverage,
1166 position_token,
1167 0,
1168 protocol_fee_rate,
1169 protocol_fee_rate_on_collateral,
1170 fusion_pool.fee_rate,
1171 price,
1172 true,
1173 )
1174 .unwrap();
1175 assert_eq!(tradable_amount, 47154380);
1176
1177 let quote = get_increase_spot_position_quote(
1178 tradable_amount,
1179 collateral_token,
1180 position_token,
1181 leverage,
1182 Some(0),
1183 protocol_fee_rate,
1184 protocol_fee_rate_on_collateral,
1185 fusion_pool,
1186 tick_arrays,
1187 )
1188 .unwrap();
1189 assert_eq!(quote.collateral, available_balance + 1);
1191 }
1192
1193 #[tokio::test]
1194 async fn tradable_amount_for_5x_long_position_providing_a() {
1195 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1196 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1197 let fusion_pool = test_fusion_pool(sqrt_price);
1198 let tick_arrays = test_tick_arrays(fusion_pool);
1199
1200 let collateral_token = TOKEN_A;
1201 let position_token = TOKEN_A;
1202 let leverage = 5.0;
1203 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1204 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1205 let available_balance = 1_000_000_000;
1206
1207 let tradable_amount = get_tradable_amount(
1208 collateral_token,
1209 available_balance,
1210 leverage,
1211 position_token,
1212 0,
1213 protocol_fee_rate,
1214 protocol_fee_rate_on_collateral,
1215 fusion_pool.fee_rate,
1216 price,
1217 true,
1218 )
1219 .unwrap();
1220 assert_eq!(tradable_amount, 4729626953);
1221
1222 let quote = get_increase_spot_position_quote(
1223 tradable_amount,
1224 collateral_token,
1225 position_token,
1226 leverage,
1227 Some(0),
1228 protocol_fee_rate,
1229 protocol_fee_rate_on_collateral,
1230 fusion_pool,
1231 tick_arrays,
1232 )
1233 .unwrap();
1234 assert_eq!(quote.collateral, available_balance);
1235 }
1237
1238 #[tokio::test]
1239 async fn tradable_amount_for_5x_short_position_providing_b() {
1240 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1241 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1242 let fusion_pool = test_fusion_pool(sqrt_price);
1243 let tick_arrays = test_tick_arrays(fusion_pool);
1244
1245 let collateral_token = TOKEN_B;
1246 let position_token = TOKEN_B;
1247 let leverage = 5.0;
1248 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1249 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1250 let available_balance = 200_000_000;
1251
1252 let tradable_amount = get_tradable_amount(
1253 collateral_token,
1254 available_balance,
1255 leverage,
1256 position_token,
1257 0,
1258 protocol_fee_rate,
1259 protocol_fee_rate_on_collateral,
1260 fusion_pool.fee_rate,
1261 price,
1262 true,
1263 )
1264 .unwrap();
1265 assert_eq!(tradable_amount, 945925390);
1266
1267 let quote = get_increase_spot_position_quote(
1268 tradable_amount,
1269 collateral_token,
1270 position_token,
1271 leverage,
1272 Some(0),
1273 protocol_fee_rate,
1274 protocol_fee_rate_on_collateral,
1275 fusion_pool,
1276 tick_arrays,
1277 )
1278 .unwrap();
1279 assert_eq!(quote.collateral, available_balance + 1);
1281 }
1283
1284 #[tokio::test]
1285 async fn tradable_amount_for_reducing_existing_long_position() {
1286 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1287 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1288 let fusion_pool = test_fusion_pool(sqrt_price);
1289 let tick_arrays = test_tick_arrays(fusion_pool);
1290
1291 for i in 0..2 {
1292 let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1293 let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1294 let leverage = 5.0;
1295 let position_amount = 5_000_000_000;
1296 let position_debt = 800_000_000;
1297 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1298 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1299 let available_balance = 50_000_000_000;
1300
1301 let tradable_amount = get_tradable_amount(
1302 collateral_token,
1303 available_balance,
1304 leverage,
1305 position_token,
1306 position_amount,
1307 protocol_fee_rate,
1308 protocol_fee_rate_on_collateral,
1309 fusion_pool.fee_rate,
1310 price,
1311 false,
1312 )
1313 .unwrap();
1314 assert_eq!(tradable_amount, 5_000_000_000);
1315
1316 let quote = get_decrease_spot_position_quote(
1317 tradable_amount,
1318 collateral_token,
1319 Some(0),
1320 position_token,
1321 position_amount,
1322 position_debt,
1323 fusion_pool,
1324 tick_arrays.clone(),
1325 )
1326 .unwrap();
1327
1328 assert_eq!(quote.estimated_amount, 0);
1329 }
1330 }
1331}