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::{
17 calculate_tuna_protocol_fee, CoreError, HUNDRED_PERCENT, INVALID_ARGUMENTS, JUPITER_QUOTE_REQUEST_ERROR, JUPITER_SWAP_INSTRUCTIONS_REQUEST_ERROR,
18 TOKEN_A, TOKEN_B,
19};
20use fusionamm_core::{
21 sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_get_max_amount_with_slippage_tolerance,
22 try_get_min_amount_with_slippage_tolerance, try_mul_div, FusionPoolFacade, TickArrays, TokenPair,
23};
24use jup_ag::{PrioritizationFeeLamports, QuoteConfig, SwapMode, SwapRequest};
25use libm::{ceil, round};
26use solana_instruction::AccountMeta;
27use solana_pubkey::Pubkey;
28
29pub const DEFAULT_SLIPPAGE_TOLERANCE_BPS: u16 = 100;
30
31#[cfg_attr(feature = "wasm", wasm_expose)]
32pub struct SwapInstruction {
33 pub data: Vec<u8>,
34 pub accounts: Vec<AccountMeta>,
35 pub address_lookup_table_addresses: Vec<Pubkey>,
36}
37
38#[cfg_attr(feature = "wasm", wasm_expose)]
39pub struct IncreaseSpotPositionQuoteResult {
40 pub collateral: u64,
42 pub borrow: u64,
44 pub estimated_amount: u64,
46 pub swap_input_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 pub jupiter_swap_ix: Option<SwapInstruction>,
58}
59
60pub async fn get_increase_spot_position_quote(
78 increase_amount: u64,
79 collateral_token: u8,
80 position_token: u8,
81 leverage: f64,
82 slippage_tolerance_bps: Option<u16>,
83 protocol_fee_rate: u16,
84 protocol_fee_rate_on_collateral: u16,
85 mint_a: Pubkey,
86 mint_b: Pubkey,
87 fusion_pool: FusionPoolFacade,
88 tick_arrays: Option<TickArrays>,
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 min_swap_output_amount: u64 = 0;
103 let mut price_impact: f64 = 0.0;
104 let mut jupiter_swap_ix: Option<SwapInstruction> = None;
105
106 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
107 let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
108
109 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
110 let swap_input_token_is_a = borrowed_token == TOKEN_A;
111
112 if borrowed_token == collateral_token {
113 borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
114 collateral =
115 increase_amount - fees::apply_swap_fee(fees::apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
116 collateral = fees::reverse_apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
117 collateral = fees::reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
118
119 swap_input_amount = collateral + borrow;
120 } else {
121 let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
122 let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
123
124 borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
125
126 let borrow_in_position_token_with_fees_applied = fees::apply_swap_fee(
127 fees::apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?,
128 fusion_pool.fee_rate,
129 false,
130 )?;
131
132 collateral = increase_amount - borrow_in_position_token_with_fees_applied;
133 collateral = fees::reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
134
135 swap_input_amount = borrow;
136 }
137
138 let protocol_fee = calculate_tuna_spot_position_protocol_fee(
139 collateral_token,
140 borrowed_token,
141 collateral,
142 borrow,
143 protocol_fee_rate_on_collateral,
144 protocol_fee_rate,
145 );
146
147 swap_input_amount -= if swap_input_token_is_a { protocol_fee.a } else { protocol_fee.b };
148
149 if position_token == collateral_token {
150 estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee.a } else { protocol_fee.b };
151 }
152
153 if swap_input_amount > 0 {
154 if let Some(tick_arrays) = tick_arrays {
155 let quote = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, fusion_pool, tick_arrays, None, None)?;
156 estimated_amount += quote.token_est_out;
157 min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(quote.token_est_out, slippage_tolerance_bps)?;
158 let new_price = sqrt_price_to_price(quote.next_sqrt_price.into(), 1, 1);
159 price_impact = (new_price / price - 1.0).abs();
160 } else {
161 let (input_mint, output_mint) = if swap_input_token_is_a { (mint_a, mint_b) } else { (mint_b, mint_a) };
162
163 let quote = jupiter_swap_quote(input_mint, output_mint, swap_input_amount, Some(slippage_tolerance_bps as u64)).await?;
164
165 estimated_amount += quote.out_amount;
166 price_impact = quote.price_impact_pct;
167 min_swap_output_amount = quote.other_amount_threshold;
168 jupiter_swap_ix = Some(SwapInstruction {
169 data: quote.instruction.data,
170 accounts: quote.instruction.accounts,
171 address_lookup_table_addresses: quote.instruction.address_lookup_table_addresses,
172 });
173 }
174 }
175
176 Ok(IncreaseSpotPositionQuoteResult {
177 collateral,
178 borrow,
179 estimated_amount,
180 swap_input_amount,
181 min_swap_output_amount,
182 protocol_fee_a: protocol_fee.a,
183 protocol_fee_b: protocol_fee.b,
184 price_impact,
185 jupiter_swap_ix,
186 })
187}
188
189#[cfg(feature = "wasm")]
207#[wasm_bindgen(js_name = "getIncreaseSpotPositionQuote", skip_jsdoc)]
208pub async fn wasm_get_increase_spot_position_quote(
209 increase_amount: u64,
210 collateral_token: u8,
211 position_token: u8,
212 leverage: f64,
213 slippage_tolerance_bps: Option<u16>,
214 protocol_fee_rate: u16,
215 protocol_fee_rate_on_collateral: u16,
216 mint_a: Pubkey,
217 mint_b: Pubkey,
218 fusion_pool: FusionPoolFacade,
219 tick_arrays: Option<TickArrays>,
220) -> Result<JsValue, JsValue> {
221 let result = get_increase_spot_position_quote(
222 increase_amount,
223 collateral_token,
224 position_token,
225 leverage,
226 slippage_tolerance_bps,
227 protocol_fee_rate,
228 protocol_fee_rate_on_collateral,
229 mint_a,
230 mint_b,
231 fusion_pool,
232 tick_arrays,
233 )
234 .await
235 .map_err(|e| JsValue::from_str(e))?;
236
237 let serializer = Serializer::new().serialize_maps_as_objects(true);
238 let js_value = result.serialize(&serializer).unwrap();
239
240 Ok(js_value)
241}
242
243#[cfg_attr(feature = "wasm", wasm_expose)]
244pub struct DecreaseSpotPositionQuoteResult {
245 pub decrease_percent: u32,
247 pub required_swap_amount: u64,
251 pub estimated_amount: u64,
253 pub estimated_payable_debt: u64,
255 pub estimated_collateral_to_be_withdrawn: u64,
257 pub price_impact: f64,
259 pub jupiter_swap_ix: Option<SwapInstruction>,
261}
262
263pub async fn get_decrease_spot_position_quote(
279 decrease_amount: u64,
280 collateral_token: u8,
281 leverage: f64,
282 slippage_tolerance_bps: Option<u16>,
283 position_token: u8,
284 position_amount: u64,
285 position_debt: u64,
286 mint_a: Pubkey,
287 mint_b: Pubkey,
288 fusion_pool: FusionPoolFacade,
289 tick_arrays: Option<TickArrays>,
290) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
291 if collateral_token > TOKEN_B || position_token > TOKEN_B {
292 return Err(INVALID_ARGUMENTS.into());
293 }
294
295 if leverage < 1.0 {
296 return Err(INVALID_ARGUMENTS.into());
297 }
298
299 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
300 let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
301 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
302 let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
303
304 let mut required_swap_amount: u64 = 0;
305 let mut price_impact = 0.0;
306 let mut jupiter_swap_ix: Option<SwapInstruction> = None;
307
308 let mut decrease_amount_in_position_token = if collateral_token == position_token {
309 decrease_amount
310 } else {
311 round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
312 };
313
314 decrease_amount_in_position_token = position_amount.min(decrease_amount_in_position_token);
315
316 let decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
317
318 let estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
319 let estimated_payable_debt = try_mul_div(position_debt, decrease_percent as u128, HUNDRED_PERCENT as u128, true)?;
320 let mut estimated_collateral_to_be_withdrawn = 0;
321
322 if let Some(tick_arrays) = tick_arrays {
323 let mut next_sqrt_price = fusion_pool.sqrt_price;
324
325 if collateral_token == position_token {
326 if position_debt > 0 {
327 let amount_out = position_debt * decrease_percent as u64 / HUNDRED_PERCENT as u64;
328 let swap = swap_quote_by_output_token(amount_out, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
329 next_sqrt_price = swap.next_sqrt_price;
330 required_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_tolerance_bps)?;
331 estimated_collateral_to_be_withdrawn = position_amount.saturating_sub(swap.token_est_in).saturating_sub(estimated_amount);
332 } else {
333 estimated_collateral_to_be_withdrawn = position_amount - estimated_amount;
334 }
335 } else {
336 let amount_in = position_amount - estimated_amount;
337 let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
338 next_sqrt_price = swap.next_sqrt_price;
339 required_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
340 estimated_collateral_to_be_withdrawn = swap.token_est_out.saturating_sub(estimated_payable_debt);
341 }
342
343 let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
344 price_impact = (new_price / price - 1.0).abs();
345 } else {
346 let (input_mint, output_mint) = if position_token == TOKEN_A { (mint_a, mint_b) } else { (mint_b, mint_a) };
347
348 let amount_in = if collateral_token == position_token {
349 if position_debt > 0 {
350 let mut amount_in = if position_token == TOKEN_A {
351 (position_debt as f64 / price) as u64
352 } else {
353 (position_debt as f64 * price) as u64
354 };
355 amount_in = try_get_max_amount_with_slippage_tolerance(amount_in, slippage_tolerance_bps)?;
356 amount_in = amount_in.min(position_amount);
357 estimated_collateral_to_be_withdrawn = position_amount.saturating_sub(amount_in).saturating_sub(estimated_amount);
358 amount_in
359 } else {
360 estimated_collateral_to_be_withdrawn = position_amount - estimated_amount;
361 0
362 }
363 } else {
364 position_amount - estimated_amount
365 };
366
367 if amount_in > 0 {
368 let quote = jupiter_swap_quote(input_mint, output_mint, amount_in, Some(slippage_tolerance_bps as u64)).await?;
369
370 price_impact = quote.price_impact_pct;
371 required_swap_amount = quote.other_amount_threshold;
372 jupiter_swap_ix = Some(SwapInstruction {
373 data: quote.instruction.data,
374 accounts: quote.instruction.accounts,
375 address_lookup_table_addresses: quote.instruction.address_lookup_table_addresses,
376 });
377
378 if collateral_token != position_token {
379 estimated_collateral_to_be_withdrawn = quote.out_amount.saturating_sub(estimated_payable_debt);
380 }
381 }
382 }
383
384 Ok(DecreaseSpotPositionQuoteResult {
385 decrease_percent,
386 estimated_payable_debt,
387 estimated_collateral_to_be_withdrawn,
388 required_swap_amount,
389 estimated_amount,
390 price_impact,
391 jupiter_swap_ix,
392 })
393}
394
395#[cfg(feature = "wasm")]
411#[wasm_bindgen(js_name = "getDecreaseSpotPositionQuote", skip_jsdoc)]
412pub async fn wasm_get_decrease_spot_position_quote(
413 decrease_amount: u64,
414 collateral_token: u8,
415 leverage: f64,
416 slippage_tolerance_bps: Option<u16>,
417 position_token: u8,
418 position_amount: u64,
419 position_debt: u64,
420 mint_a: Pubkey,
421 mint_b: Pubkey,
422 fusion_pool: FusionPoolFacade,
423 tick_arrays: Option<TickArrays>,
424) -> Result<JsValue, JsValue> {
425 let result = get_decrease_spot_position_quote(
426 decrease_amount,
427 collateral_token,
428 leverage,
429 slippage_tolerance_bps,
430 position_token,
431 position_amount,
432 position_debt,
433 mint_a,
434 mint_b,
435 fusion_pool,
436 tick_arrays,
437 )
438 .await
439 .map_err(|e| JsValue::from_str(e))?;
440
441 let serializer = Serializer::new().serialize_maps_as_objects(true);
442 let js_value = result.serialize(&serializer).unwrap();
443
444 Ok(js_value)
445}
446
447#[cfg_attr(feature = "wasm", wasm_expose)]
458pub fn get_spot_position_liquidation_price(position_token: u8, amount: f64, debt: f64, liquidation_threshold: f64) -> Result<f64, CoreError> {
459 if debt < 0.0 || amount < 0.0 {
460 return Err(INVALID_ARGUMENTS);
461 }
462
463 if liquidation_threshold <= 0.0 || liquidation_threshold >= 1.0 {
464 return Err(INVALID_ARGUMENTS);
465 }
466
467 if debt == 0.0 || amount == 0.0 {
468 return Ok(0.0);
469 }
470
471 if position_token == TOKEN_A {
472 Ok(debt / (amount * liquidation_threshold))
473 } else {
474 Ok((amount * liquidation_threshold) / debt)
475 }
476}
477
478#[cfg_attr(feature = "wasm", wasm_expose)]
494pub fn get_tradable_amount(
495 collateral_token: u8,
496 available_balance: u64,
497 leverage: f64,
498 position_token: u8,
499 position_amount: u64,
500 protocol_fee_rate: u16,
501 protocol_fee_rate_on_collateral: u16,
502 fusion_pool: FusionPoolFacade,
503 increase: bool,
504) -> Result<u64, CoreError> {
505 if collateral_token > TOKEN_B || position_token > TOKEN_B {
506 return Err(INVALID_ARGUMENTS.into());
507 }
508
509 if leverage < 1.0 {
510 return Err(INVALID_ARGUMENTS.into());
511 }
512
513 let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
517 let mut collateral = fees::apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
518 if collateral_token != position_token {
519 collateral = fees::apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
520 }
521
522 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);
523 let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
524 Ok(total)
525 };
526
527 let available_to_trade = if increase {
528 add_leverage(available_balance)?
529 } else {
530 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
531 let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
532
533 if collateral_token == position_token {
534 position_amount
535 } else {
536 round(position_amount as f64 * position_to_opposite_token_price) as u64
537 }
538 };
539
540 Ok(available_to_trade)
541}
542
543struct JupiterSwapResult {
544 pub instruction: SwapInstruction,
545 pub out_amount: u64,
546 pub other_amount_threshold: u64,
547 pub price_impact_pct: f64,
548}
549
550async fn jupiter_swap_quote(input_mint: Pubkey, output_mint: Pubkey, amount: u64, slippage_bps: Option<u64>) -> Result<JupiterSwapResult, CoreError> {
551 let quote_config = QuoteConfig {
552 slippage_bps,
553 swap_mode: Some(SwapMode::ExactIn),
554 dexes: None,
555 exclude_dexes: None,
556 only_direct_routes: false,
557 as_legacy_transaction: None,
558 platform_fee_bps: None,
559 max_accounts: None,
560 };
561
562 let quote = jup_ag::quote(input_mint, output_mint, amount, quote_config)
563 .await
564 .map_err(|_| JUPITER_QUOTE_REQUEST_ERROR)?;
565
566 #[allow(deprecated)]
567 let swap_request = SwapRequest {
568 user_public_key: Default::default(),
569 wrap_and_unwrap_sol: None,
570 use_shared_accounts: Some(true),
571 fee_account: None,
572 compute_unit_price_micro_lamports: None,
573 prioritization_fee_lamports: PrioritizationFeeLamports::Auto,
574 as_legacy_transaction: None,
575 use_token_ledger: None,
576 destination_token_account: None,
577 quote_response: quote.clone(),
578 };
579
580 let swap_response = jup_ag::swap_instructions(swap_request)
581 .await
582 .map_err(|_| JUPITER_SWAP_INSTRUCTIONS_REQUEST_ERROR)?;
583
584 Ok(JupiterSwapResult {
585 instruction: SwapInstruction {
586 data: swap_response.swap_instruction.data,
587 accounts: swap_response.swap_instruction.accounts,
588 address_lookup_table_addresses: swap_response.address_lookup_table_addresses,
589 },
590 out_amount: quote.out_amount,
591 other_amount_threshold: quote.other_amount_threshold,
592 price_impact_pct: quote.price_impact_pct,
593 })
594}
595
596#[cfg_attr(feature = "wasm", wasm_expose)]
597pub fn calculate_tuna_spot_position_protocol_fee(
598 collateral_token: u8,
599 borrowed_token: u8,
600 collateral: u64,
601 borrow: u64,
602 protocol_fee_rate_on_collateral: u16,
603 protocol_fee_rate: u16,
604) -> TokenPair {
605 let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
606 let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
607 let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
608 let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
609
610 let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, protocol_fee_rate_on_collateral, protocol_fee_rate);
611 let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, protocol_fee_rate_on_collateral, protocol_fee_rate);
612
613 TokenPair {
614 a: protocol_fee_a,
615 b: protocol_fee_b,
616 }
617}
618
619#[cfg(all(test, not(feature = "wasm")))]
620mod tests {
621 use super::*;
622 use crate::assert_approx_eq;
623 use fusionamm_core::{
624 get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
625 };
626 use solana_pubkey::pubkey;
627
628 const NATIVE_MINT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
629 const TUNA_MINT: Pubkey = pubkey!("TUNAfXDZEdQizTMTh3uEvNvYqJmqFHZbEJt8joP4cyx");
630
631 fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
632 let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
633 FusionPoolFacade {
634 tick_current_index,
635 fee_rate: 3000,
636 liquidity: 10000000000000,
637 sqrt_price,
638 tick_spacing: 2,
639 ..FusionPoolFacade::default()
640 }
641 }
642
643 fn test_tick(liquidity_net: i128) -> TickFacade {
644 TickFacade {
645 initialized: true,
646 liquidity_net,
647 ..TickFacade::default()
648 }
649 }
650
651 fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
652 TickArrayFacade {
653 start_tick_index,
654 ticks: [test_tick(0); TICK_ARRAY_SIZE],
655 }
656 }
657
658 fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
659 let tick_spacing = fusion_pool.tick_spacing;
660 let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
661 let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
662
663 [
664 test_tick_array(tick_array_start_index),
665 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
666 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
667 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
668 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
669 ]
670 .into()
671 }
672
673 #[test]
674 fn test_get_liquidation_price() {
675 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5.0, 0.0, 0.85), Ok(0.0));
676 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 0.0, 5.0, 0.85), Ok(0.0));
677 assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5.0, 800.0, 0.85), Ok(188.23529411764707));
678 assert_eq!(get_spot_position_liquidation_price(TOKEN_B, 1000.0, 4.0, 0.85), Ok(212.5));
679 }
680
681 #[tokio::test]
682 async fn increase_long_position_providing_token_a() {
683 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
684 let fusion_pool = test_fusion_pool(sqrt_price);
685
686 let quote = get_increase_spot_position_quote(
687 5_000_000_000,
688 TOKEN_A,
689 TOKEN_A,
690 5.0,
691 Some(0),
692 (HUNDRED_PERCENT / 100) as u16,
693 (HUNDRED_PERCENT / 200) as u16,
694 NATIVE_MINT,
695 TUNA_MINT,
696 fusion_pool,
697 Some(test_tick_arrays(fusion_pool)),
698 )
699 .await
700 .unwrap();
701
702 assert_eq!(quote.collateral, 1057165829);
703 assert_eq!(quote.borrow, 800000000);
704 assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
705 assert_eq!(quote.estimated_amount, 4_999_303_011);
706 assert_eq!(quote.protocol_fee_a, 5285829);
707 assert_eq!(quote.protocol_fee_b, 8000000);
708 assert_eq!(quote.price_impact, 0.00035316176257027543);
709 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);
710 }
711
712 #[tokio::test]
713 async fn increase_long_position_providing_token_b() {
714 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
715 let fusion_pool = test_fusion_pool(sqrt_price);
716
717 let quote = get_increase_spot_position_quote(
718 5000_000_000,
719 TOKEN_B,
720 TOKEN_A,
721 5.0,
722 Some(0),
723 (HUNDRED_PERCENT / 100) as u16,
724 (HUNDRED_PERCENT / 200) as u16,
725 NATIVE_MINT,
726 TUNA_MINT,
727 fusion_pool,
728 Some(test_tick_arrays(fusion_pool)),
729 )
730 .await
731 .unwrap();
732
733 assert_eq!(quote.collateral, 1060346869);
734 assert_eq!(quote.borrow, 4000000000);
735 assert_eq!(quote.estimated_amount, 24_972_080_293);
736 assert_eq!(quote.protocol_fee_a, 0);
737 assert_eq!(quote.protocol_fee_b, 45301734);
738 assert_eq!(quote.price_impact, 0.0022373179716579372);
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_short_position_providing_a() {
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 = get_increase_spot_position_quote(
748 5_000_000_000,
749 TOKEN_A,
750 TOKEN_B,
751 5.0,
752 Some(0),
753 (HUNDRED_PERCENT / 100) as u16,
754 (HUNDRED_PERCENT / 200) as u16,
755 NATIVE_MINT,
756 TUNA_MINT,
757 fusion_pool,
758 Some(test_tick_arrays(fusion_pool)),
759 )
760 .await
761 .unwrap();
762
763 assert_eq!(quote.collateral, 1060346869);
764 assert_eq!(quote.borrow, 4000000000);
765 assert_eq!(quote.estimated_amount, 999_776_441);
766 assert_eq!(quote.protocol_fee_a, 45301734);
767 assert_eq!(quote.protocol_fee_b, 0);
768 assert_eq!(quote.price_impact, 0.0004470636400017991);
769 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);
770 }
771
772 #[tokio::test]
773 async fn increase_short_position_providing_b() {
774 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
775 let fusion_pool = test_fusion_pool(sqrt_price);
776
777 let quote = get_increase_spot_position_quote(
778 5000_000_000,
779 TOKEN_B,
780 TOKEN_B,
781 5.0,
782 Some(0),
783 (HUNDRED_PERCENT / 100) as u16,
784 (HUNDRED_PERCENT / 200) as u16,
785 NATIVE_MINT,
786 TUNA_MINT,
787 fusion_pool,
788 Some(test_tick_arrays(fusion_pool)),
789 )
790 .await
791 .unwrap();
792
793 assert_eq!(quote.collateral, 1057165829);
794 assert_eq!(quote.borrow, 20000000000);
795 assert_eq!(quote.estimated_amount, 4996_517_564);
796 assert_eq!(quote.protocol_fee_a, 200000000);
797 assert_eq!(quote.protocol_fee_b, 5285829);
798 assert_eq!(quote.price_impact, 0.0017633175413067637);
799 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);
800 }
801
802 #[tokio::test]
803 async fn increase_quote_with_slippage() {
804 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
805 let fusion_pool = test_fusion_pool(sqrt_price);
806
807 let quote = get_increase_spot_position_quote(
809 200_000,
810 TOKEN_B,
811 TOKEN_A,
812 5.0,
813 Some(1000),
814 0,
815 0,
816 NATIVE_MINT,
817 TUNA_MINT,
818 fusion_pool,
819 Some(test_tick_arrays(fusion_pool)),
820 )
821 .await
822 .unwrap();
823 assert_eq!(quote.min_swap_output_amount, 899_994);
824
825 let quote = get_increase_spot_position_quote(
827 200_000,
828 TOKEN_B,
829 TOKEN_A,
830 5.0,
831 Some(0),
832 0,
833 0,
834 NATIVE_MINT,
835 TUNA_MINT,
836 fusion_pool,
837 Some(test_tick_arrays(fusion_pool)),
838 )
839 .await
840 .unwrap();
841 assert_eq!(quote.min_swap_output_amount, 999_994);
842 }
843
844 #[tokio::test]
845 async fn decrease_non_leveraged_long_position_providing_a() {
846 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
847 let fusion_pool = test_fusion_pool(sqrt_price);
848
849 let quote = get_decrease_spot_position_quote(
850 1_000_000_000,
851 TOKEN_A,
852 1.0,
853 Some(0),
854 TOKEN_A,
855 5_000_000_000, 0, NATIVE_MINT,
858 TUNA_MINT,
859 fusion_pool,
860 Some(test_tick_arrays(fusion_pool)),
861 )
862 .await
863 .unwrap();
864
865 assert_eq!(quote.decrease_percent, 200000);
866 assert_eq!(quote.estimated_amount, 4_000_000_000);
867 assert_eq!(quote.estimated_payable_debt, 0);
868 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 1_000_000_000);
869 assert_eq!(quote.price_impact, 0.0);
870 }
871
872 #[tokio::test]
873 async fn decrease_non_leveraged_long_position_providing_b() {
874 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
875 let fusion_pool = test_fusion_pool(sqrt_price);
876
877 let quote = get_decrease_spot_position_quote(
878 200_000_000,
879 TOKEN_B,
880 1.0,
881 Some(0),
882 TOKEN_A,
883 5_000_000_000, 0, NATIVE_MINT,
886 TUNA_MINT,
887 fusion_pool,
888 Some(test_tick_arrays(fusion_pool)),
889 )
890 .await
891 .unwrap();
892
893 assert_eq!(quote.decrease_percent, 200000);
894 assert_eq!(quote.estimated_amount, 4_000_000_000);
895 assert_eq!(quote.estimated_payable_debt, 0);
896 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 199_391_108);
897 assert_eq!(quote.price_impact, 0.00008916842709072448);
898 }
899
900 #[tokio::test]
901 async fn decrease_long_position_providing_a() {
902 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
903 let fusion_pool = test_fusion_pool(sqrt_price);
904
905 let quote = get_decrease_spot_position_quote(
906 1_000_000_000,
907 TOKEN_A,
908 5.0,
909 Some(0),
910 TOKEN_A,
911 5_000_000_000, 800_000_000, NATIVE_MINT,
914 TUNA_MINT,
915 fusion_pool,
916 Some(test_tick_arrays(fusion_pool)),
917 )
918 .await
919 .unwrap();
920
921 assert_eq!(quote.decrease_percent, 200000);
922 assert_eq!(quote.estimated_amount, 4_000_000_000);
923 assert_eq!(quote.estimated_payable_debt, 160_000_000);
924 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 197_564_069);
925 assert_eq!(quote.price_impact, 0.00007155289528004705);
926
927 let quote = get_decrease_spot_position_quote(
928 6_000_000_000,
929 TOKEN_A,
930 5.0,
931 Some(0),
932 TOKEN_A,
933 5_000_000_000, 800_000_000, NATIVE_MINT,
936 TUNA_MINT,
937 fusion_pool,
938 Some(test_tick_arrays(fusion_pool)),
939 )
940 .await
941 .unwrap();
942
943 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
944 assert_eq!(quote.estimated_amount, 0);
945 }
946
947 #[tokio::test]
948 async fn decrease_long_position_providing_b() {
949 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
950 let fusion_pool = test_fusion_pool(sqrt_price);
951
952 let quote = get_decrease_spot_position_quote(
953 200_000_000,
954 TOKEN_B,
955 5.0,
956 Some(0),
957 TOKEN_A,
958 5_000_000_000, 800_000_000, NATIVE_MINT,
961 TUNA_MINT,
962 fusion_pool,
963 Some(test_tick_arrays(fusion_pool)),
964 )
965 .await
966 .unwrap();
967
968 assert_eq!(quote.estimated_amount, 4000000000);
969 assert_eq!(quote.decrease_percent, 200000);
970 assert_eq!(quote.estimated_payable_debt, 160_000_000);
971 assert_eq!(quote.estimated_collateral_to_be_withdrawn, 39_391_108);
972 assert_eq!(quote.price_impact, 0.00008916842709072448);
973
974 let quote = get_decrease_spot_position_quote(
975 1200_000_000,
976 TOKEN_B,
977 5.0,
978 Some(0),
979 TOKEN_A,
980 5_000_000_000, 800_000_000, NATIVE_MINT,
983 TUNA_MINT,
984 fusion_pool,
985 Some(test_tick_arrays(fusion_pool)),
986 )
987 .await
988 .unwrap();
989
990 assert_eq!(quote.estimated_amount, 0);
991 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
992 }
993
994 #[tokio::test]
995 async fn tradable_amount_for_1x_long_position_providing_b() {
996 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
997 let fusion_pool = test_fusion_pool(sqrt_price);
998 let tick_arrays = Some(test_tick_arrays(fusion_pool));
999
1000 let collateral_token = TOKEN_B;
1001 let position_token = TOKEN_A;
1002 let leverage = 1.0;
1003 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1004 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1005 let available_balance = 200_000_000;
1006
1007 let tradable_amount = get_tradable_amount(
1008 collateral_token,
1009 available_balance,
1010 leverage,
1011 position_token,
1012 0,
1013 protocol_fee_rate,
1014 protocol_fee_rate_on_collateral,
1015 fusion_pool,
1016 true,
1017 )
1018 .unwrap();
1019 assert_eq!(tradable_amount, 198403000);
1020
1021 let quote = get_increase_spot_position_quote(
1022 tradable_amount,
1023 collateral_token,
1024 position_token,
1025 leverage,
1026 Some(0),
1027 protocol_fee_rate,
1028 protocol_fee_rate_on_collateral,
1029 NATIVE_MINT,
1030 TUNA_MINT,
1031 fusion_pool,
1032 tick_arrays,
1033 )
1034 .await
1035 .unwrap();
1036 assert_eq!(quote.collateral, available_balance);
1037 }
1038
1039 #[tokio::test]
1040 async fn tradable_amount_for_5x_long_position_providing_b() {
1041 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1042 let fusion_pool = test_fusion_pool(sqrt_price);
1043 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1044
1045 let collateral_token = TOKEN_B;
1046 let position_token = TOKEN_A;
1047 let leverage = 5.0;
1048 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1049 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1050 let available_balance = 10_000_000;
1051
1052 let tradable_amount = get_tradable_amount(
1053 collateral_token,
1054 available_balance,
1055 leverage,
1056 position_token,
1057 0,
1058 protocol_fee_rate,
1059 protocol_fee_rate_on_collateral,
1060 fusion_pool,
1061 true,
1062 )
1063 .unwrap();
1064 assert_eq!(tradable_amount, 47154380);
1065
1066 let quote = get_increase_spot_position_quote(
1067 tradable_amount,
1068 collateral_token,
1069 position_token,
1070 leverage,
1071 Some(0),
1072 protocol_fee_rate,
1073 protocol_fee_rate_on_collateral,
1074 NATIVE_MINT,
1075 TUNA_MINT,
1076 fusion_pool,
1077 tick_arrays,
1078 )
1079 .await
1080 .unwrap();
1081 assert_eq!(quote.collateral, available_balance + 1);
1083 }
1084
1085 #[tokio::test]
1086 async fn tradable_amount_for_5x_long_position_providing_a() {
1087 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1088 let fusion_pool = test_fusion_pool(sqrt_price);
1089 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1090
1091 let collateral_token = TOKEN_A;
1092 let position_token = TOKEN_A;
1093 let leverage = 5.0;
1094 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1095 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1096 let available_balance = 1_000_000_000;
1097
1098 let tradable_amount = get_tradable_amount(
1099 collateral_token,
1100 available_balance,
1101 leverage,
1102 position_token,
1103 0,
1104 protocol_fee_rate,
1105 protocol_fee_rate_on_collateral,
1106 fusion_pool,
1107 true,
1108 )
1109 .unwrap();
1110 assert_eq!(tradable_amount, 4729626953);
1111
1112 let quote = get_increase_spot_position_quote(
1113 tradable_amount,
1114 collateral_token,
1115 position_token,
1116 leverage,
1117 Some(0),
1118 protocol_fee_rate,
1119 protocol_fee_rate_on_collateral,
1120 NATIVE_MINT,
1121 TUNA_MINT,
1122 fusion_pool,
1123 tick_arrays,
1124 )
1125 .await
1126 .unwrap();
1127 assert_eq!(quote.collateral, available_balance);
1128 }
1130
1131 #[tokio::test]
1132 async fn tradable_amount_for_5x_short_position_providing_b() {
1133 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1134 let fusion_pool = test_fusion_pool(sqrt_price);
1135 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1136
1137 let collateral_token = TOKEN_B;
1138 let position_token = TOKEN_B;
1139 let leverage = 5.0;
1140 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1141 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1142 let available_balance = 200_000_000;
1143
1144 let tradable_amount = get_tradable_amount(
1145 collateral_token,
1146 available_balance,
1147 leverage,
1148 position_token,
1149 0,
1150 protocol_fee_rate,
1151 protocol_fee_rate_on_collateral,
1152 fusion_pool,
1153 true,
1154 )
1155 .unwrap();
1156 assert_eq!(tradable_amount, 945925390);
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 NATIVE_MINT,
1167 TUNA_MINT,
1168 fusion_pool,
1169 tick_arrays,
1170 )
1171 .await
1172 .unwrap();
1173 assert_eq!(quote.collateral, available_balance + 1);
1175 }
1177
1178 #[tokio::test]
1179 async fn tradable_amount_for_reducing_existing_long_position() {
1180 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1181 let fusion_pool = test_fusion_pool(sqrt_price);
1182 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1183
1184 for i in 0..2 {
1185 let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1186 let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1187 let leverage = 5.0;
1188 let position_amount = 5_000_000_000;
1189 let position_debt = 800_000_000;
1190 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1191 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1192 let available_balance = 50_000_000_000;
1193
1194 let tradable_amount = get_tradable_amount(
1195 collateral_token,
1196 available_balance,
1197 leverage,
1198 position_token,
1199 position_amount,
1200 protocol_fee_rate,
1201 protocol_fee_rate_on_collateral,
1202 fusion_pool,
1203 false,
1204 )
1205 .unwrap();
1206 assert_eq!(tradable_amount, 5_000_000_000);
1207
1208 let quote = get_decrease_spot_position_quote(
1209 tradable_amount,
1210 collateral_token,
1211 5.0,
1212 Some(0),
1213 position_token,
1214 position_amount,
1215 position_debt,
1216 NATIVE_MINT,
1217 TUNA_MINT,
1218 fusion_pool,
1219 tick_arrays.clone(),
1220 )
1221 .await
1222 .unwrap();
1223
1224 assert_eq!(quote.estimated_amount, 0);
1225 }
1226 }
1227}