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