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