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::{
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 leverage: f64,
368 position_token: u8,
369 position_amount: u64,
370 position_debt: u64,
371 price: f64,
372 pool: Option<SwapPool>,
373) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
374 if collateral_token > TOKEN_B || position_token > TOKEN_B {
375 return Err(INVALID_ARGUMENTS.into());
376 }
377
378 if leverage < 1.0 {
379 return Err(INVALID_ARGUMENTS.into());
380 }
381
382 let slippage_bps = match pool.as_ref() {
383 None => 0,
384 Some(pool) => pool.slippage_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS),
385 };
386
387 let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
388 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
389
390 let mut required_swap_amount: u64 = 0;
391
392 let mut decrease_amount_in_position_token = if collateral_token == position_token {
393 decrease_amount
394 } else {
395 round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
396 };
397
398 decrease_amount_in_position_token = position_amount.min(decrease_amount_in_position_token);
399
400 let decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
401
402 let estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
403 let estimated_payable_debt = try_mul_div(position_debt, decrease_percent as u128, HUNDRED_PERCENT as u128, true)?;
404 let mut estimated_collateral_to_be_withdrawn = 0;
405
406 let swap_exact_in: bool;
408 let mut swap_input_amount = 0;
409 let mut swap_output_amount = 0;
410
411 if collateral_token == position_token {
412 swap_exact_in = false;
413 if position_debt > 0 {
414 swap_output_amount = estimated_payable_debt;
415 if let Some(pool) = pool {
416 let swap =
417 swap_quote_by_output_token(swap_output_amount, borrowed_token == TOKEN_A, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
418 swap_input_amount = swap.token_est_in;
419 required_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_bps)?;
421 estimated_collateral_to_be_withdrawn = position_amount.saturating_sub(swap.token_est_in).saturating_sub(estimated_amount);
422 }
423 } else {
424 estimated_collateral_to_be_withdrawn = position_amount - estimated_amount;
425 }
426 } else {
427 swap_exact_in = true;
428 swap_input_amount = position_amount - estimated_amount;
429 if let Some(pool) = pool {
430 let swap = swap_quote_by_input_token(swap_input_amount, position_token == TOKEN_A, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
431 swap_output_amount = swap.token_est_out;
433 required_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_bps)?;
434 estimated_collateral_to_be_withdrawn = swap.token_est_out.saturating_sub(estimated_payable_debt);
435 }
436 }
437
438 let price_impact = if swap_input_amount > 0 {
442 if position_token == TOKEN_A {
443 (swap_input_amount as f64 - swap_output_amount as f64 / price) / swap_input_amount as f64
444 } else {
445 (swap_input_amount as f64 - swap_output_amount as f64 * price) / swap_input_amount as f64
446 }
447 } else {
448 0.0
449 };
450
451 Ok(DecreaseSpotPositionQuoteResult {
452 decrease_percent,
453 estimated_payable_debt,
454 estimated_collateral_to_be_withdrawn,
455 swap_exact_in,
456 swap_input_amount,
457 swap_output_amount,
458 required_swap_amount,
459 estimated_amount,
460 price_impact,
461 })
462}
463
464#[cfg_attr(feature = "wasm", wasm_expose)]
480pub fn get_decrease_spot_position_quote(
481 decrease_amount: u64,
482 collateral_token: u8,
483 leverage: f64,
484 slippage_bps: Option<u16>,
485 position_token: u8,
486 position_amount: u64,
487 position_debt: u64,
488 fusion_pool: FusionPoolFacade,
489 tick_arrays: TickArrays,
490) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
491 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
492 get_decrease_spot_position_quote_internal(
493 decrease_amount,
494 collateral_token,
495 leverage,
496 position_token,
497 position_amount,
498 position_debt,
499 price,
500 Some(SwapPool {
501 fusion_pool,
502 tick_arrays,
503 slippage_bps,
504 }),
505 )
506}
507
508#[cfg_attr(feature = "wasm", wasm_expose)]
522pub fn get_decrease_spot_position_estimation(
523 decrease_amount: u64,
524 collateral_token: u8,
525 leverage: f64,
526 position_token: u8,
527 position_amount: u64,
528 position_debt: u64,
529 price: f64,
530) -> Result<DecreaseSpotPositionEstimationResult, CoreError> {
531 let quote = get_decrease_spot_position_quote_internal(
532 decrease_amount,
533 collateral_token,
534 leverage,
535 position_token,
536 position_amount,
537 position_debt,
538 price,
539 None,
540 )?;
541
542 Ok(DecreaseSpotPositionEstimationResult {
543 decrease_percent: quote.decrease_percent,
544 swap_exact_in: quote.swap_exact_in,
545 swap_amount: if quote.swap_exact_in {
546 quote.swap_input_amount
547 } else {
548 quote.swap_output_amount
549 },
550 estimated_amount: quote.estimated_amount,
551 estimated_payable_debt: quote.estimated_payable_debt,
552 })
553}
554
555#[cfg_attr(feature = "wasm", wasm_expose)]
620pub fn get_spot_position_liquidation_price(position_token: u8, amount: u64, debt: u64, liquidation_threshold: u32) -> Result<f64, CoreError> {
621 if liquidation_threshold >= HUNDRED_PERCENT {
622 return Err(INVALID_ARGUMENTS);
623 }
624
625 if debt == 0 || amount == 0 {
626 return Ok(0.0);
627 }
628
629 let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
630
631 if position_token == TOKEN_A {
632 Ok(debt as f64 / (amount as f64 * liquidation_threshold_f))
633 } else {
634 Ok((amount as f64 * liquidation_threshold_f) / debt as f64)
635 }
636}
637
638#[cfg_attr(feature = "wasm", wasm_expose)]
655pub fn get_tradable_amount(
656 collateral_token: u8,
657 available_balance: u64,
658 leverage: f64,
659 position_token: u8,
660 position_amount: u64,
661 protocol_fee_rate: u16,
662 protocol_fee_rate_on_collateral: u16,
663 swap_fee_rate: u16,
664 price: f64,
665 increase: bool,
666) -> Result<u64, CoreError> {
667 if collateral_token > TOKEN_B || position_token > TOKEN_B {
668 return Err(INVALID_ARGUMENTS.into());
669 }
670
671 if leverage < 1.0 {
672 return Err(INVALID_ARGUMENTS.into());
673 }
674
675 let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
679 let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
680 if collateral_token != position_token {
681 collateral = apply_swap_fee(collateral, swap_fee_rate, false)?;
682 }
683
684 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);
685 let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
686 Ok(total)
687 };
688
689 let available_to_trade = if increase {
690 add_leverage(available_balance)?
691 } else {
692 let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
693
694 if collateral_token == position_token {
695 position_amount
696 } else {
697 round(position_amount as f64 * position_to_opposite_token_price) as u64
698 }
699 };
700
701 Ok(available_to_trade)
702}
703
704#[cfg_attr(feature = "wasm", wasm_expose)]
766pub fn calculate_tuna_spot_position_protocol_fee(
767 collateral_token: u8,
768 borrowed_token: u8,
769 collateral: u64,
770 borrow: u64,
771 protocol_fee_rate_on_collateral: u16,
772 protocol_fee_rate: u16,
773) -> TokenPair {
774 let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
775 let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
776 let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
777 let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
778
779 let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, protocol_fee_rate_on_collateral, protocol_fee_rate);
780 let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, protocol_fee_rate_on_collateral, protocol_fee_rate);
781
782 TokenPair {
783 a: protocol_fee_a,
784 b: protocol_fee_b,
785 }
786}
787
788#[cfg(all(test, not(feature = "wasm")))]
789mod tests {
790 use super::*;
791 use crate::assert_approx_eq;
792 use fusionamm_core::{
793 get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
794 };
795
796 fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
797 let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
798 FusionPoolFacade {
799 tick_current_index,
800 fee_rate: 3000,
801 liquidity: 10000000000000,
802 sqrt_price,
803 tick_spacing: 2,
804 ..FusionPoolFacade::default()
805 }
806 }
807
808 fn test_tick(liquidity_net: i128) -> TickFacade {
809 TickFacade {
810 initialized: true,
811 liquidity_net,
812 ..TickFacade::default()
813 }
814 }
815
816 fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
817 TickArrayFacade {
818 start_tick_index,
819 ticks: [test_tick(0); TICK_ARRAY_SIZE],
820 }
821 }
822
823 fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
824 let tick_spacing = fusion_pool.tick_spacing;
825 let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
826 let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
827
828 [
829 test_tick_array(tick_array_start_index),
830 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
831 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
832 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
833 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
834 ]
835 .into()
836 }
837
838 #[test]
839 fn test_get_liquidation_price() {
840 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 0, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
841 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 0, 5, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
842 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 800, HUNDRED_PERCENT * 85 / 100), Ok(188.23529411764707));
843 assert_eq!(get_spot_position_liquidation_price(TOKEN_B, 1000, 4, HUNDRED_PERCENT * 85 / 100), Ok(212.5));
844 }
845
846 #[tokio::test]
847 async fn increase_long_position_providing_token_a() {
848 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
849 let fusion_pool = test_fusion_pool(sqrt_price);
850
851 let quote = get_increase_spot_position_quote(
852 5_000_000_000,
853 TOKEN_A,
854 TOKEN_A,
855 5.0,
856 Some(0),
857 (HUNDRED_PERCENT / 100) as u16,
858 (HUNDRED_PERCENT / 200) as u16,
859 fusion_pool,
860 test_tick_arrays(fusion_pool),
861 )
862 .unwrap();
863
864 assert_eq!(quote.collateral, 1057165829);
865 assert_eq!(quote.borrow, 800000000);
866 assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
867 assert_eq!(quote.estimated_amount, 4_999_303_011);
868 assert_eq!(quote.protocol_fee_a, 5285829);
869 assert_eq!(quote.protocol_fee_b, 8000000);
870 assert_eq!(quote.price_impact, 0.0031760073232324137);
871 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);
872 }
873
874 #[tokio::test]
875 async fn increase_long_position_providing_token_b() {
876 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
877 let fusion_pool = test_fusion_pool(sqrt_price);
878
879 let quote = get_increase_spot_position_quote(
880 5000_000_000,
881 TOKEN_B,
882 TOKEN_A,
883 5.0,
884 Some(0),
885 (HUNDRED_PERCENT / 100) as u16,
886 (HUNDRED_PERCENT / 200) as u16,
887 fusion_pool,
888 test_tick_arrays(fusion_pool),
889 )
890 .unwrap();
891
892 assert_eq!(quote.collateral, 1060346869);
893 assert_eq!(quote.borrow, 4000000000);
894 assert_eq!(quote.estimated_amount, 24_972_080_293);
895 assert_eq!(quote.protocol_fee_a, 0);
896 assert_eq!(quote.protocol_fee_b, 45301734);
897 assert_eq!(quote.price_impact, 0.004113437834493303);
898 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);
899 }
900
901 #[tokio::test]
902 async fn increase_short_position_providing_a() {
903 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
904 let fusion_pool = test_fusion_pool(sqrt_price);
905
906 let quote = get_increase_spot_position_quote(
907 5_000_000_000,
908 TOKEN_A,
909 TOKEN_B,
910 5.0,
911 Some(0),
912 (HUNDRED_PERCENT / 100) as u16,
913 (HUNDRED_PERCENT / 200) as u16,
914 fusion_pool,
915 test_tick_arrays(fusion_pool),
916 )
917 .unwrap();
918
919 assert_eq!(quote.collateral, 1060346869);
920 assert_eq!(quote.borrow, 4000000000);
921 assert_eq!(quote.estimated_amount, 999_776_441);
922 assert_eq!(quote.protocol_fee_a, 45301734);
923 assert_eq!(quote.protocol_fee_b, 0);
924 assert_eq!(quote.price_impact, 0.003222888242261054);
925 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);
926 }
927
928 #[tokio::test]
929 async fn increase_short_position_providing_b() {
930 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
931 let fusion_pool = test_fusion_pool(sqrt_price);
932
933 let quote = get_increase_spot_position_quote(
934 5000_000_000,
935 TOKEN_B,
936 TOKEN_B,
937 5.0,
938 Some(0),
939 (HUNDRED_PERCENT / 100) as u16,
940 (HUNDRED_PERCENT / 200) as u16,
941 fusion_pool,
942 test_tick_arrays(fusion_pool),
943 )
944 .unwrap();
945
946 assert_eq!(quote.collateral, 1057165829);
947 assert_eq!(quote.borrow, 20000000000);
948 assert_eq!(quote.estimated_amount, 4996_517_564);
949 assert_eq!(quote.protocol_fee_a, 200000000);
950 assert_eq!(quote.protocol_fee_b, 5285829);
951 assert_eq!(quote.price_impact, 0.0038794030303030305);
952 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);
953 }
954
955 #[tokio::test]
956 async fn increase_quote_with_slippage() {
957 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
958 let fusion_pool = test_fusion_pool(sqrt_price);
959
960 let quote =
962 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();
963 assert_eq!(quote.min_swap_output_amount, 899_994);
964
965 let quote =
967 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();
968 assert_eq!(quote.min_swap_output_amount, 999_994);
969 }
970
971 #[tokio::test]
972 async fn decrease_non_leveraged_long_position_providing_a() {
973 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
974 let fusion_pool = test_fusion_pool(sqrt_price);
975
976 let quote = get_decrease_spot_position_quote(
977 1_000_000_000,
978 TOKEN_A,
979 1.0,
980 Some(0),
981 TOKEN_A,
982 5_000_000_000, 0, fusion_pool,
985 test_tick_arrays(fusion_pool),
986 )
987 .unwrap();
988
989 assert_eq!(quote.decrease_percent, 200000);
990 assert_eq!(quote.estimated_amount, 4_000_000_000);
991 assert_eq!(quote.estimated_payable_debt, 0);
992 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 1_000_000_000);
993 assert_eq!(quote.price_impact, 0.0);
994 }
995
996 #[tokio::test]
997 async fn decrease_non_leveraged_long_position_providing_b() {
998 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
999 let fusion_pool = test_fusion_pool(sqrt_price);
1000
1001 let quote = get_decrease_spot_position_quote(
1002 200_000_000,
1003 TOKEN_B,
1004 1.0,
1005 Some(0),
1006 TOKEN_A,
1007 5_000_000_000, 0, fusion_pool,
1010 test_tick_arrays(fusion_pool),
1011 )
1012 .unwrap();
1013
1014 assert_eq!(quote.decrease_percent, 200000);
1015 assert_eq!(quote.estimated_amount, 4_000_000_000);
1016 assert_eq!(quote.estimated_payable_debt, 0);
1017 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 199_391_108);
1018 assert_eq!(quote.price_impact, 0.003044459999999881);
1019 }
1020
1021 #[tokio::test]
1022 async fn decrease_long_position_providing_a() {
1023 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1024 let fusion_pool = test_fusion_pool(sqrt_price);
1025
1026 let quote = get_decrease_spot_position_quote(
1027 1_000_000_000,
1028 TOKEN_A,
1029 5.0,
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, 200000);
1040 assert_eq!(quote.estimated_amount, 4_000_000_000);
1041 assert_eq!(quote.estimated_payable_debt, 160_000_000);
1042 assert_eq!(quote.swap_exact_in, false);
1043 assert_eq!(quote.swap_input_amount, 802_435_931);
1044 assert_eq!(quote.swap_output_amount, 160_000_000);
1045 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 197_564_069);
1046 assert_eq!(quote.price_impact, 0.0030356703954722095);
1047
1048 let quote = get_decrease_spot_position_quote(
1049 6_000_000_000,
1050 TOKEN_A,
1051 5.0,
1052 Some(0),
1053 TOKEN_A,
1054 5_000_000_000, 800_000_000, fusion_pool,
1057 test_tick_arrays(fusion_pool),
1058 )
1059 .unwrap();
1060
1061 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1062 assert_eq!(quote.estimated_amount, 0);
1063 }
1064
1065 #[tokio::test]
1066 async fn decrease_long_position_providing_a_estimation() {
1067 let quote = get_decrease_spot_position_estimation(
1068 1_000_000_000,
1069 TOKEN_A,
1070 5.0,
1071 TOKEN_A,
1072 5_000_000_000, 800_000_000, 0.2,
1075 )
1076 .unwrap();
1077
1078 assert_eq!(quote.decrease_percent, 200000);
1079 assert_eq!(quote.estimated_amount, 4_000_000_000);
1080 assert_eq!(quote.estimated_payable_debt, 160_000_000);
1081 assert_eq!(quote.swap_exact_in, false);
1082 assert_eq!(quote.swap_amount, 160_000_000);
1083 }
1084
1085 #[tokio::test]
1086 async fn decrease_long_position_providing_b() {
1087 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1088 let fusion_pool = test_fusion_pool(sqrt_price);
1089
1090 let quote = get_decrease_spot_position_quote(
1091 200_000_000,
1092 TOKEN_B,
1093 5.0,
1094 Some(0),
1095 TOKEN_A,
1096 5_000_000_000, 800_000_000, fusion_pool,
1099 test_tick_arrays(fusion_pool),
1100 )
1101 .unwrap();
1102
1103 assert_eq!(quote.estimated_amount, 4000000000);
1104 assert_eq!(quote.decrease_percent, 200000);
1105 assert_eq!(quote.estimated_payable_debt, 160_000_000);
1106 assert_eq!(quote.swap_exact_in, true);
1107 assert_eq!(quote.swap_input_amount, 1_000_000_000);
1108 assert_eq!(quote.swap_output_amount, 199_391_108);
1109 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 39_391_108);
1110 assert_eq!(quote.price_impact, 0.003044459999999881);
1111
1112 let quote = get_decrease_spot_position_quote(
1113 1200_000_000,
1114 TOKEN_B,
1115 5.0,
1116 Some(0),
1117 TOKEN_A,
1118 5_000_000_000, 800_000_000, fusion_pool,
1121 test_tick_arrays(fusion_pool),
1122 )
1123 .unwrap();
1124
1125 assert_eq!(quote.estimated_amount, 0);
1126 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1127 }
1128
1129 #[tokio::test]
1130 async fn tradable_amount_for_1x_long_position_providing_b() {
1131 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1132 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1133 let fusion_pool = test_fusion_pool(sqrt_price);
1134 let tick_arrays = test_tick_arrays(fusion_pool);
1135
1136 let collateral_token = TOKEN_B;
1137 let position_token = TOKEN_A;
1138 let leverage = 1.0;
1139 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1140 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1141 let available_balance = 200_000_000;
1142
1143 let tradable_amount = get_tradable_amount(
1144 collateral_token,
1145 available_balance,
1146 leverage,
1147 position_token,
1148 0,
1149 protocol_fee_rate,
1150 protocol_fee_rate_on_collateral,
1151 fusion_pool.fee_rate,
1152 price,
1153 true,
1154 )
1155 .unwrap();
1156 assert_eq!(tradable_amount, 198403000);
1157
1158 let quote = get_increase_spot_position_quote(
1159 tradable_amount,
1160 collateral_token,
1161 position_token,
1162 leverage,
1163 Some(0),
1164 protocol_fee_rate,
1165 protocol_fee_rate_on_collateral,
1166 fusion_pool,
1167 tick_arrays,
1168 )
1169 .unwrap();
1170 assert_eq!(quote.collateral, available_balance);
1171 }
1172
1173 #[tokio::test]
1174 async fn tradable_amount_for_5x_long_position_providing_b() {
1175 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1176 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1177 let fusion_pool = test_fusion_pool(sqrt_price);
1178 let tick_arrays = test_tick_arrays(fusion_pool);
1179
1180 let collateral_token = TOKEN_B;
1181 let position_token = TOKEN_A;
1182 let leverage = 5.0;
1183 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1184 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1185 let available_balance = 10_000_000;
1186
1187 let tradable_amount = get_tradable_amount(
1188 collateral_token,
1189 available_balance,
1190 leverage,
1191 position_token,
1192 0,
1193 protocol_fee_rate,
1194 protocol_fee_rate_on_collateral,
1195 fusion_pool.fee_rate,
1196 price,
1197 true,
1198 )
1199 .unwrap();
1200 assert_eq!(tradable_amount, 47154380);
1201
1202 let quote = get_increase_spot_position_quote(
1203 tradable_amount,
1204 collateral_token,
1205 position_token,
1206 leverage,
1207 Some(0),
1208 protocol_fee_rate,
1209 protocol_fee_rate_on_collateral,
1210 fusion_pool,
1211 tick_arrays,
1212 )
1213 .unwrap();
1214 assert_eq!(quote.collateral, available_balance + 1);
1216 }
1217
1218 #[tokio::test]
1219 async fn tradable_amount_for_5x_long_position_providing_a() {
1220 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1221 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1222 let fusion_pool = test_fusion_pool(sqrt_price);
1223 let tick_arrays = test_tick_arrays(fusion_pool);
1224
1225 let collateral_token = TOKEN_A;
1226 let position_token = TOKEN_A;
1227 let leverage = 5.0;
1228 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1229 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1230 let available_balance = 1_000_000_000;
1231
1232 let tradable_amount = get_tradable_amount(
1233 collateral_token,
1234 available_balance,
1235 leverage,
1236 position_token,
1237 0,
1238 protocol_fee_rate,
1239 protocol_fee_rate_on_collateral,
1240 fusion_pool.fee_rate,
1241 price,
1242 true,
1243 )
1244 .unwrap();
1245 assert_eq!(tradable_amount, 4729626953);
1246
1247 let quote = get_increase_spot_position_quote(
1248 tradable_amount,
1249 collateral_token,
1250 position_token,
1251 leverage,
1252 Some(0),
1253 protocol_fee_rate,
1254 protocol_fee_rate_on_collateral,
1255 fusion_pool,
1256 tick_arrays,
1257 )
1258 .unwrap();
1259 assert_eq!(quote.collateral, available_balance);
1260 }
1262
1263 #[tokio::test]
1264 async fn tradable_amount_for_5x_short_position_providing_b() {
1265 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1266 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1267 let fusion_pool = test_fusion_pool(sqrt_price);
1268 let tick_arrays = test_tick_arrays(fusion_pool);
1269
1270 let collateral_token = TOKEN_B;
1271 let position_token = TOKEN_B;
1272 let leverage = 5.0;
1273 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1274 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1275 let available_balance = 200_000_000;
1276
1277 let tradable_amount = get_tradable_amount(
1278 collateral_token,
1279 available_balance,
1280 leverage,
1281 position_token,
1282 0,
1283 protocol_fee_rate,
1284 protocol_fee_rate_on_collateral,
1285 fusion_pool.fee_rate,
1286 price,
1287 true,
1288 )
1289 .unwrap();
1290 assert_eq!(tradable_amount, 945925390);
1291
1292 let quote = get_increase_spot_position_quote(
1293 tradable_amount,
1294 collateral_token,
1295 position_token,
1296 leverage,
1297 Some(0),
1298 protocol_fee_rate,
1299 protocol_fee_rate_on_collateral,
1300 fusion_pool,
1301 tick_arrays,
1302 )
1303 .unwrap();
1304 assert_eq!(quote.collateral, available_balance + 1);
1306 }
1308
1309 #[tokio::test]
1310 async fn tradable_amount_for_reducing_existing_long_position() {
1311 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1312 let price = sqrt_price_to_price(sqrt_price, 1, 1);
1313 let fusion_pool = test_fusion_pool(sqrt_price);
1314 let tick_arrays = test_tick_arrays(fusion_pool);
1315
1316 for i in 0..2 {
1317 let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1318 let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1319 let leverage = 5.0;
1320 let position_amount = 5_000_000_000;
1321 let position_debt = 800_000_000;
1322 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1323 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1324 let available_balance = 50_000_000_000;
1325
1326 let tradable_amount = get_tradable_amount(
1327 collateral_token,
1328 available_balance,
1329 leverage,
1330 position_token,
1331 position_amount,
1332 protocol_fee_rate,
1333 protocol_fee_rate_on_collateral,
1334 fusion_pool.fee_rate,
1335 price,
1336 false,
1337 )
1338 .unwrap();
1339 assert_eq!(tradable_amount, 5_000_000_000);
1340
1341 let quote = get_decrease_spot_position_quote(
1342 tradable_amount,
1343 collateral_token,
1344 5.0,
1345 Some(0),
1346 position_token,
1347 position_amount,
1348 position_debt,
1349 fusion_pool,
1350 tick_arrays.clone(),
1351 )
1352 .unwrap();
1353
1354 assert_eq!(quote.estimated_amount, 0);
1355 }
1356 }
1357}