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::{
16 CoreError, HUNDRED_PERCENT, INVALID_ARGUMENTS, JUPITER_QUOTE_REQUEST_ERROR, JUPITER_SWAP_INSTRUCTIONS_REQUEST_ERROR, TICK_ARRAYS_NOT_PROVIDED,
17 TOKEN_A, TOKEN_B,
18};
19use fusionamm_core::{
20 sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_get_max_amount_with_slippage_tolerance,
21 try_get_min_amount_with_slippage_tolerance, try_mul_div, FusionPoolFacade, TickArrays, TokenPair,
22};
23use jup_ag::{PrioritizationFeeLamports, QuoteConfig, SwapMode, SwapRequest};
24use libm::{ceil, round};
25use solana_instruction::AccountMeta;
26use solana_pubkey::Pubkey;
27
28pub const DEFAULT_SLIPPAGE_TOLERANCE_BPS: u16 = 100;
29
30#[cfg_attr(feature = "wasm", wasm_expose)]
31pub struct SwapInstruction {
32 pub data: Vec<u8>,
33 pub accounts: Vec<AccountMeta>,
34 pub address_lookup_table_addresses: Vec<Pubkey>,
35}
36
37#[cfg_attr(feature = "wasm", wasm_expose)]
38pub struct IncreaseSpotPositionQuoteResult {
39 pub collateral: u64,
41 pub borrow: u64,
43 pub estimated_amount: u64,
45 pub swap_input_amount: u64,
47 pub min_swap_output_amount: u64,
49 pub protocol_fee_a: u64,
51 pub protocol_fee_b: u64,
53 pub price_impact: f64,
55 pub jupiter_swap_ix: Option<SwapInstruction>,
57}
58
59pub async fn get_increase_spot_position_quote(
77 increase_amount: u64,
78 collateral_token: u8,
79 position_token: u8,
80 leverage: f64,
81 slippage_tolerance_bps: Option<u16>,
82 protocol_fee_rate: u16,
83 protocol_fee_rate_on_collateral: u16,
84 mint_a: Pubkey,
85 mint_b: Pubkey,
86 fusion_pool: FusionPoolFacade,
87 tick_arrays: Option<TickArrays>,
88) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
89 if collateral_token > TOKEN_B || position_token > TOKEN_B {
90 return Err(INVALID_ARGUMENTS.into());
91 }
92
93 if leverage < 1.0 {
94 return Err(INVALID_ARGUMENTS.into());
95 }
96
97 let borrow: u64;
98 let mut collateral: u64;
99 let mut estimated_amount: u64 = 0;
100 let mut swap_input_amount: u64;
101 let mut min_swap_output_amount: u64 = 0;
102 let mut price_impact: f64 = 0.0;
103 let mut jupiter_swap_ix: Option<SwapInstruction> = None;
104
105 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
106 let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
107
108 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
109 let swap_input_token_is_a = borrowed_token == TOKEN_A;
110
111 if borrowed_token == collateral_token {
112 borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
113 collateral = increase_amount - apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
114 collateral = reverse_apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
115 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
116
117 swap_input_amount = collateral + borrow;
118 } else {
119 let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
120 let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
121
122 borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
123
124 let borrow_in_position_token_with_fees_applied =
125 apply_swap_fee(apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
126
127 collateral = increase_amount - borrow_in_position_token_with_fees_applied;
128 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
129
130 swap_input_amount = borrow;
131 }
132
133 let protocol_fee =
134 calculate_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
135
136 swap_input_amount -= if swap_input_token_is_a { protocol_fee.a } else { protocol_fee.b };
137
138 if position_token == collateral_token {
139 estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee.a } else { protocol_fee.b };
140 }
141
142 if swap_input_amount > 0 {
143 if let Some(tick_arrays) = tick_arrays {
144 let quote = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, fusion_pool, tick_arrays, None, None)?;
145 estimated_amount += quote.token_est_out;
146 min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(quote.token_est_out, slippage_tolerance_bps)?;
147 let new_price = sqrt_price_to_price(quote.next_sqrt_price.into(), 1, 1);
148 price_impact = (new_price / price - 1.0).abs();
149 } else {
150 let (input_mint, output_mint) = if swap_input_token_is_a { (mint_a, mint_b) } else { (mint_b, mint_a) };
151
152 let quote = jupiter_swap_quote(input_mint, output_mint, swap_input_amount, Some(slippage_tolerance_bps as u64)).await?;
153
154 estimated_amount += quote.out_amount;
155 price_impact = quote.price_impact_pct;
156 min_swap_output_amount = quote.other_amount_threshold;
157 jupiter_swap_ix = Some(SwapInstruction {
158 data: quote.instruction.data,
159 accounts: quote.instruction.accounts,
160 address_lookup_table_addresses: quote.instruction.address_lookup_table_addresses,
161 });
162 }
163 }
164
165 Ok(IncreaseSpotPositionQuoteResult {
166 collateral,
167 borrow,
168 estimated_amount,
169 swap_input_amount,
170 min_swap_output_amount,
171 protocol_fee_a: protocol_fee.a,
172 protocol_fee_b: protocol_fee.b,
173 price_impact,
174 jupiter_swap_ix,
175 })
176}
177
178#[cfg(feature = "wasm")]
196#[wasm_bindgen(js_name = "getIncreaseSpotPositionQuote", skip_jsdoc)]
197pub async fn wasm_get_increase_spot_position_quote(
198 increase_amount: u64,
199 collateral_token: u8,
200 position_token: u8,
201 leverage: f64,
202 slippage_tolerance_bps: Option<u16>,
203 protocol_fee_rate: u16,
204 protocol_fee_rate_on_collateral: u16,
205 mint_a: Pubkey,
206 mint_b: Pubkey,
207 fusion_pool: FusionPoolFacade,
208 tick_arrays: Option<TickArrays>,
209) -> Result<JsValue, JsValue> {
210 let result = get_increase_spot_position_quote(
211 increase_amount,
212 collateral_token,
213 position_token,
214 leverage,
215 slippage_tolerance_bps,
216 protocol_fee_rate,
217 protocol_fee_rate_on_collateral,
218 mint_a,
219 mint_b,
220 fusion_pool,
221 tick_arrays,
222 )
223 .await
224 .map_err(|e| JsValue::from_str(e))?;
225
226 let serializer = Serializer::new().serialize_maps_as_objects(true);
227 let js_value = result.serialize(&serializer).unwrap();
228
229 Ok(js_value)
230}
231
232#[cfg_attr(feature = "wasm", wasm_expose)]
233pub struct DecreaseSpotPositionQuoteResult {
234 pub decrease_percent: u32,
236 pub collateral_token: u8,
238 pub position_token: u8,
240 pub collateral: u64,
242 pub borrow: u64,
244 pub decrease_acceptable_swap_amount: u64,
248 pub increase_min_swap_output_amount: u64,
250 pub estimated_amount: u64,
252 pub protocol_fee_a: u64,
254 pub protocol_fee_b: u64,
256 pub price_impact: f64,
258 pub jupiter_swap_ix: Option<SwapInstruction>,
260}
261
262pub async fn get_decrease_spot_position_quote(
281 decrease_amount: u64,
282 collateral_token: u8,
283 leverage: f64,
284 reduce_only: bool,
285 slippage_tolerance_bps: Option<u16>,
286 position_token: u8,
287 position_amount: u64,
288 position_debt: u64,
289 protocol_fee_rate: u16,
290 protocol_fee_rate_on_collateral: u16,
291 mint_a: Pubkey,
292 mint_b: Pubkey,
293 fusion_pool: FusionPoolFacade,
294 tick_arrays: Option<TickArrays>,
295) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
296 if collateral_token > TOKEN_B || position_token > TOKEN_B {
297 return Err(INVALID_ARGUMENTS.into());
298 }
299
300 if leverage < 1.0 {
301 return Err(INVALID_ARGUMENTS.into());
302 }
303
304 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
305 let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
306 let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
307 let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
308
309 let mut collateral = 0;
310 let mut borrow = 0;
311 let estimated_amount: u64;
312 let mut new_position_token = position_token;
313 let mut increase_min_swap_output_amount: u64 = 0;
314 let mut decrease_acceptable_swap_amount: u64 = 0;
315 let mut price_impact = 0.0;
316 let mut jupiter_swap_ix: Option<SwapInstruction> = None;
317
318 let mut decrease_amount_in_position_token = if collateral_token == position_token {
319 decrease_amount
320 } else {
321 round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
322 };
323
324 if reduce_only && decrease_amount_in_position_token > position_amount {
325 decrease_amount_in_position_token = position_amount;
326 }
327
328 let decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
329
330 if decrease_amount_in_position_token <= position_amount {
331 estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
332
333 if let Some(tick_arrays) = tick_arrays {
334 let mut next_sqrt_price = fusion_pool.sqrt_price;
335
336 if collateral_token == position_token {
337 if position_debt > 0 {
338 let amount_out = position_debt * decrease_percent as u64 / HUNDRED_PERCENT as u64;
339 let swap = swap_quote_by_output_token(amount_out, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
340 next_sqrt_price = swap.next_sqrt_price;
341 decrease_acceptable_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_tolerance_bps)?;
342 }
343 } else {
344 let amount_in = position_amount - estimated_amount;
345 let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
346 next_sqrt_price = swap.next_sqrt_price;
347 decrease_acceptable_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
348 }
349
350 let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
351 price_impact = (new_price / price - 1.0).abs();
352 } else {
353 let (input_mint, output_mint) = if position_token == TOKEN_A { (mint_a, mint_b) } else { (mint_b, mint_a) };
354
355 let amount_in = if collateral_token == position_token {
356 if position_debt > 0 {
357 let mut amount_in = if position_token == TOKEN_A {
358 (position_debt as f64 / price) as u64
359 } else {
360 (position_debt as f64 * price) as u64
361 };
362 amount_in = try_get_max_amount_with_slippage_tolerance(amount_in, slippage_tolerance_bps)?;
363 amount_in.min(position_amount)
364 } else {
365 0
366 }
367 } else {
368 position_amount - estimated_amount
369 };
370
371 if amount_in > 0 {
372 let quote = jupiter_swap_quote(input_mint, output_mint, amount_in, Some(slippage_tolerance_bps as u64)).await?;
373
374 price_impact = quote.price_impact_pct;
375 decrease_acceptable_swap_amount = quote.other_amount_threshold;
376 jupiter_swap_ix = Some(SwapInstruction {
377 data: quote.instruction.data,
378 accounts: quote.instruction.accounts,
379 address_lookup_table_addresses: quote.instruction.address_lookup_table_addresses,
380 });
381 }
382 }
383 } else {
384 let mut next_sqrt_price = fusion_pool.sqrt_price;
385 new_position_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
386 let increase_amount = decrease_amount_in_position_token - position_amount;
387 let mut increase_swap_output_amount: u64 = 0;
388
389 let tick_arrays = tick_arrays.ok_or(TICK_ARRAYS_NOT_PROVIDED)?;
390
391 if position_token == collateral_token {
392 borrow = ((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
401 let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
402
403 collateral = increase_amount - borrow_with_fees_applied;
404
405 let mut amount_in = 0;
407 if position_debt > 0 {
408 let swap =
409 swap_quote_by_output_token(position_debt, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
410 amount_in = swap.token_est_in;
411 decrease_acceptable_swap_amount = try_get_max_amount_with_slippage_tolerance(amount_in, slippage_tolerance_bps)?;
412 }
413
414 amount_in += collateral + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
416 if amount_in > 0 {
417 let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
418 next_sqrt_price = swap.next_sqrt_price;
419 increase_swap_output_amount = swap.token_est_out.saturating_sub(position_debt);
420 }
421
422 estimated_amount = increase_swap_output_amount;
424 } else {
425 let swap = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
433 let decrease_amount_out = swap.token_est_out;
434 decrease_acceptable_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
435
436 borrow = ((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
437 let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
438
439 collateral = increase_amount - borrow_with_fees_applied;
440 collateral = ceil(collateral as f64 * position_to_borrowed_token_price) as u64;
441
442 let amount_in = position_amount + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
444 if amount_in > 0 {
445 let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
446 next_sqrt_price = swap.next_sqrt_price;
447 increase_swap_output_amount = swap.token_est_out.saturating_sub(decrease_amount_out);
448 }
449
450 estimated_amount = increase_swap_output_amount + collateral;
452 }
453
454 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
455
456 let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
457 price_impact = (new_price / price - 1.0).abs();
458
459 increase_min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(increase_swap_output_amount, slippage_tolerance_bps)?;
460 }
461
462 let protocol_fee =
463 calculate_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
464
465 Ok(DecreaseSpotPositionQuoteResult {
466 decrease_percent,
467 collateral_token,
468 position_token: new_position_token,
469 collateral,
470 borrow,
471 decrease_acceptable_swap_amount,
472 increase_min_swap_output_amount,
473 estimated_amount,
474 protocol_fee_a: protocol_fee.a,
475 protocol_fee_b: protocol_fee.b,
476 price_impact,
477 jupiter_swap_ix,
478 })
479}
480
481#[cfg(feature = "wasm")]
500#[wasm_bindgen(js_name = "getDecreaseSpotPositionQuote", skip_jsdoc)]
501pub async fn wasm_get_decrease_spot_position_quote(
502 decrease_amount: u64,
503 collateral_token: u8,
504 leverage: f64,
505 reduce_only: bool,
506 slippage_tolerance_bps: Option<u16>,
507 position_token: u8,
508 position_amount: u64,
509 position_debt: u64,
510 protocol_fee_rate: u16,
511 protocol_fee_rate_on_collateral: u16,
512 mint_a: Pubkey,
513 mint_b: Pubkey,
514 fusion_pool: FusionPoolFacade,
515 tick_arrays: Option<TickArrays>,
516) -> Result<JsValue, JsValue> {
517 let result = get_decrease_spot_position_quote(
518 decrease_amount,
519 collateral_token,
520 leverage,
521 reduce_only,
522 slippage_tolerance_bps,
523 position_token,
524 position_amount,
525 position_debt,
526 protocol_fee_rate,
527 protocol_fee_rate_on_collateral,
528 mint_a,
529 mint_b,
530 fusion_pool,
531 tick_arrays,
532 )
533 .await
534 .map_err(|e| JsValue::from_str(e))?;
535
536 let serializer = Serializer::new().serialize_maps_as_objects(true);
537 let js_value = result.serialize(&serializer).unwrap();
538
539 Ok(js_value)
540}
541
542#[cfg_attr(feature = "wasm", wasm_expose)]
553pub fn get_liquidation_price(position_token: u8, amount: f64, debt: f64, liquidation_threshold: f64) -> Result<f64, CoreError> {
554 if debt < 0.0 || amount < 0.0 {
555 return Err(INVALID_ARGUMENTS);
556 }
557
558 if liquidation_threshold <= 0.0 || liquidation_threshold >= 1.0 {
559 return Err(INVALID_ARGUMENTS);
560 }
561
562 if debt == 0.0 || amount == 0.0 {
563 return Ok(0.0);
564 }
565
566 if position_token == TOKEN_A {
567 Ok(debt / (amount * liquidation_threshold))
568 } else {
569 Ok((amount * liquidation_threshold) / debt)
570 }
571}
572
573#[cfg_attr(feature = "wasm", wasm_expose)]
592pub fn get_tradable_amount(
593 collateral_token: u8,
594 available_balance: u64,
595 leverage: f64,
596 new_position_token: u8,
597 reduce_only: bool,
598 position_token: u8,
599 position_amount: u64,
600 position_debt: u64,
601 protocol_fee_rate: u16,
602 protocol_fee_rate_on_collateral: u16,
603 fusion_pool: FusionPoolFacade,
604 tick_arrays: Option<TickArrays>,
605) -> Result<u64, CoreError> {
606 if collateral_token > TOKEN_B || position_token > TOKEN_B {
607 return Err(INVALID_ARGUMENTS.into());
608 }
609
610 if leverage < 1.0 {
611 return Err(INVALID_ARGUMENTS.into());
612 }
613
614 if position_amount == 0 && new_position_token != position_token {
615 return Err(INVALID_ARGUMENTS.into());
616 }
617
618 let tick_arrays = tick_arrays.ok_or(INVALID_ARGUMENTS)?;
619
620 let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
624 let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
625 if collateral_token != new_position_token {
626 collateral = apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
627 }
628
629 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);
630 let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
631 Ok(total)
632 };
633
634 let available_to_trade = if new_position_token == position_token {
635 add_leverage(available_balance)?
636 } else {
637 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
638 let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
639
640 if reduce_only {
641 if collateral_token == position_token {
642 position_amount
643 } else {
644 round(position_amount as f64 * position_to_opposite_token_price) as u64
645 }
646 } else {
647 let position_amount_in_collateral_token = if collateral_token == position_token {
648 position_amount
649 } else {
650 round(position_amount as f64 * position_to_opposite_token_price) as u64
651 };
652
653 let position_collateral = if collateral_token == position_token {
654 let swap_in = if position_debt > 0 {
655 swap_quote_by_output_token(position_debt, position_token == TOKEN_B, 0, fusion_pool, tick_arrays, None, None)?.token_est_in
656 } else {
657 0
658 };
659 position_amount - swap_in
660 } else {
661 if position_amount > 0 {
662 let swap_quote = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
663 swap_quote.token_est_out - position_debt
664 } else {
665 0
666 }
667 };
668
669 position_amount_in_collateral_token + add_leverage(available_balance + position_collateral)?
671 }
672 };
673
674 Ok(available_to_trade)
675}
676
677pub fn apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
678 try_mul_div(amount, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, HUNDRED_PERCENT as u128, round_up)
679}
680
681pub fn reverse_apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
682 try_mul_div(amount, HUNDRED_PERCENT as u128, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, round_up)
683}
684
685pub fn apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
686 try_mul_div(amount, 1_000_000 - fee_rate as u128, 1_000_000, round_up)
687}
688
689pub fn reverse_apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
690 try_mul_div(amount, 1_000_000, 1_000_000 - fee_rate as u128, round_up)
691}
692
693#[cfg_attr(feature = "wasm", wasm_expose)]
694pub fn calculate_tuna_protocol_fee(
695 collateral_token: u8,
696 borrowed_token: u8,
697 collateral: u64,
698 borrow: u64,
699 protocol_fee_rate_on_collateral: u16,
700 protocol_fee_rate: u16,
701) -> TokenPair {
702 let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
703 let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
704 let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
705 let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
706
707 let protocol_fee_a = ((collateral_a as u128 * protocol_fee_rate_on_collateral as u128 + borrow_a as u128 * protocol_fee_rate as u128)
708 / HUNDRED_PERCENT as u128) as u64;
709 let protocol_fee_b = ((collateral_b as u128 * protocol_fee_rate_on_collateral as u128 + borrow_b as u128 * protocol_fee_rate as u128)
710 / HUNDRED_PERCENT as u128) as u64;
711
712 TokenPair {
713 a: protocol_fee_a,
714 b: protocol_fee_b,
715 }
716}
717
718struct JupiterSwapResult {
719 pub instruction: SwapInstruction,
720 pub out_amount: u64,
721 pub other_amount_threshold: u64,
722 pub price_impact_pct: f64,
723}
724
725async fn jupiter_swap_quote(input_mint: Pubkey, output_mint: Pubkey, amount: u64, slippage_bps: Option<u64>) -> Result<JupiterSwapResult, CoreError> {
726 let quote_config = QuoteConfig {
727 slippage_bps,
728 swap_mode: Some(SwapMode::ExactIn),
729 dexes: None,
730 exclude_dexes: None,
731 only_direct_routes: false,
732 as_legacy_transaction: None,
733 platform_fee_bps: None,
734 max_accounts: None,
735 };
736
737 let quote = jup_ag::quote(input_mint, output_mint, amount, quote_config)
738 .await
739 .map_err(|_| JUPITER_QUOTE_REQUEST_ERROR)?;
740
741 #[allow(deprecated)]
742 let swap_request = SwapRequest {
743 user_public_key: Default::default(),
744 wrap_and_unwrap_sol: None,
745 use_shared_accounts: Some(true),
746 fee_account: None,
747 compute_unit_price_micro_lamports: None,
748 prioritization_fee_lamports: PrioritizationFeeLamports::Auto,
749 as_legacy_transaction: None,
750 use_token_ledger: None,
751 destination_token_account: None,
752 quote_response: quote.clone(),
753 };
754
755 let swap_response = jup_ag::swap_instructions(swap_request)
756 .await
757 .map_err(|_| JUPITER_SWAP_INSTRUCTIONS_REQUEST_ERROR)?;
758
759 Ok(JupiterSwapResult {
760 instruction: SwapInstruction {
761 data: swap_response.swap_instruction.data,
762 accounts: swap_response.swap_instruction.accounts,
763 address_lookup_table_addresses: swap_response.address_lookup_table_addresses,
764 },
765 out_amount: quote.out_amount,
766 other_amount_threshold: quote.other_amount_threshold,
767 price_impact_pct: quote.price_impact_pct,
768 })
769}
770
771#[cfg(all(test, not(feature = "wasm")))]
772mod tests {
773 use super::*;
774 use crate::assert_approx_eq;
775 use fusionamm_core::{
776 get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
777 };
778 use solana_pubkey::pubkey;
779
780 const NATIVE_MINT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
781 const TUNA_MINT: Pubkey = pubkey!("TUNAfXDZEdQizTMTh3uEvNvYqJmqFHZbEJt8joP4cyx");
782
783 fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
784 let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
785 FusionPoolFacade {
786 tick_current_index,
787 fee_rate: 3000,
788 liquidity: 10000000000000,
789 sqrt_price,
790 tick_spacing: 2,
791 ..FusionPoolFacade::default()
792 }
793 }
794
795 fn test_tick(liquidity_net: i128) -> TickFacade {
796 TickFacade {
797 initialized: true,
798 liquidity_net,
799 ..TickFacade::default()
800 }
801 }
802
803 fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
804 TickArrayFacade {
805 start_tick_index,
806 ticks: [test_tick(0); TICK_ARRAY_SIZE],
807 }
808 }
809
810 fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
811 let tick_spacing = fusion_pool.tick_spacing;
812 let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
813 let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
814
815 [
816 test_tick_array(tick_array_start_index),
817 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
818 test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
819 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
820 test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
821 ]
822 .into()
823 }
824
825 #[test]
826 fn test_get_liquidation_price() {
827 assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 0.0, 0.85), Ok(0.0));
828 assert_eq!(get_liquidation_price(TOKEN_A, 0.0, 5.0, 0.85), Ok(0.0));
829 assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 800.0, 0.85), Ok(188.23529411764707));
830 assert_eq!(get_liquidation_price(TOKEN_B, 1000.0, 4.0, 0.85), Ok(212.5));
831 }
832
833 #[tokio::test]
834 async fn increase_long_position_providing_token_a() {
835 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
836 let fusion_pool = test_fusion_pool(sqrt_price);
837
838 let quote = get_increase_spot_position_quote(
839 5_000_000_000,
840 TOKEN_A,
841 TOKEN_A,
842 5.0,
843 Some(0),
844 (HUNDRED_PERCENT / 100) as u16,
845 (HUNDRED_PERCENT / 200) as u16,
846 NATIVE_MINT,
847 TUNA_MINT,
848 fusion_pool,
849 Some(test_tick_arrays(fusion_pool)),
850 )
851 .await
852 .unwrap();
853
854 assert_eq!(quote.collateral, 1057165829);
855 assert_eq!(quote.borrow, 800000000);
856 assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
857 assert_eq!(quote.estimated_amount, 4_999_303_011);
858 assert_eq!(quote.protocol_fee_a, 5285829);
859 assert_eq!(quote.protocol_fee_b, 8000000);
860 assert_eq!(quote.price_impact, 0.00035316176257027543);
861 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);
862 }
863
864 #[tokio::test]
865 async fn increase_long_position_providing_token_b() {
866 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
867 let fusion_pool = test_fusion_pool(sqrt_price);
868
869 let quote = get_increase_spot_position_quote(
870 5000_000_000,
871 TOKEN_B,
872 TOKEN_A,
873 5.0,
874 Some(0),
875 (HUNDRED_PERCENT / 100) as u16,
876 (HUNDRED_PERCENT / 200) as u16,
877 NATIVE_MINT,
878 TUNA_MINT,
879 fusion_pool,
880 Some(test_tick_arrays(fusion_pool)),
881 )
882 .await
883 .unwrap();
884
885 assert_eq!(quote.collateral, 1060346869);
886 assert_eq!(quote.borrow, 4000000000);
887 assert_eq!(quote.estimated_amount, 24_972_080_293);
888 assert_eq!(quote.protocol_fee_a, 0);
889 assert_eq!(quote.protocol_fee_b, 45301734);
890 assert_eq!(quote.price_impact, 0.0022373179716579372);
891 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);
892 }
893
894 #[tokio::test]
895 async fn increase_short_position_providing_a() {
896 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
897 let fusion_pool = test_fusion_pool(sqrt_price);
898
899 let quote = get_increase_spot_position_quote(
900 5_000_000_000,
901 TOKEN_A,
902 TOKEN_B,
903 5.0,
904 Some(0),
905 (HUNDRED_PERCENT / 100) as u16,
906 (HUNDRED_PERCENT / 200) as u16,
907 NATIVE_MINT,
908 TUNA_MINT,
909 fusion_pool,
910 Some(test_tick_arrays(fusion_pool)),
911 )
912 .await
913 .unwrap();
914
915 assert_eq!(quote.collateral, 1060346869);
916 assert_eq!(quote.borrow, 4000000000);
917 assert_eq!(quote.estimated_amount, 999_776_441);
918 assert_eq!(quote.protocol_fee_a, 45301734);
919 assert_eq!(quote.protocol_fee_b, 0);
920 assert_eq!(quote.price_impact, 0.0004470636400017991);
921 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);
922 }
923
924 #[tokio::test]
925 async fn increase_short_position_providing_b() {
926 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
927 let fusion_pool = test_fusion_pool(sqrt_price);
928
929 let quote = get_increase_spot_position_quote(
930 5000_000_000,
931 TOKEN_B,
932 TOKEN_B,
933 5.0,
934 Some(0),
935 (HUNDRED_PERCENT / 100) as u16,
936 (HUNDRED_PERCENT / 200) as u16,
937 NATIVE_MINT,
938 TUNA_MINT,
939 fusion_pool,
940 Some(test_tick_arrays(fusion_pool)),
941 )
942 .await
943 .unwrap();
944
945 assert_eq!(quote.collateral, 1057165829);
946 assert_eq!(quote.borrow, 20000000000);
947 assert_eq!(quote.estimated_amount, 4996_517_564);
948 assert_eq!(quote.protocol_fee_a, 200000000);
949 assert_eq!(quote.protocol_fee_b, 5285829);
950 assert_eq!(quote.price_impact, 0.0017633175413067637);
951 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);
952 }
953
954 #[tokio::test]
955 async fn increase_quote_with_slippage() {
956 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
957 let fusion_pool = test_fusion_pool(sqrt_price);
958
959 let quote = get_increase_spot_position_quote(
961 200_000,
962 TOKEN_B,
963 TOKEN_A,
964 5.0,
965 Some(1000),
966 0,
967 0,
968 NATIVE_MINT,
969 TUNA_MINT,
970 fusion_pool,
971 Some(test_tick_arrays(fusion_pool)),
972 )
973 .await
974 .unwrap();
975 assert_eq!(quote.min_swap_output_amount, 899_994);
976
977 let quote = get_increase_spot_position_quote(
979 200_000,
980 TOKEN_B,
981 TOKEN_A,
982 5.0,
983 Some(0),
984 0,
985 0,
986 NATIVE_MINT,
987 TUNA_MINT,
988 fusion_pool,
989 Some(test_tick_arrays(fusion_pool)),
990 )
991 .await
992 .unwrap();
993 assert_eq!(quote.min_swap_output_amount, 999_994);
994 }
995
996 #[tokio::test]
997 async fn decrease_non_leveraged_long_position_providing_a_reduce_only() {
998 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
999 let fusion_pool = test_fusion_pool(sqrt_price);
1000
1001 let quote = get_decrease_spot_position_quote(
1002 1_000_000_000,
1003 TOKEN_A,
1004 1.0,
1005 true,
1006 Some(0),
1007 TOKEN_A,
1008 5_000_000_000, 0, (HUNDRED_PERCENT / 100) as u16,
1011 (HUNDRED_PERCENT / 200) as u16,
1012 NATIVE_MINT,
1013 TUNA_MINT,
1014 fusion_pool,
1015 Some(test_tick_arrays(fusion_pool)),
1016 )
1017 .await
1018 .unwrap();
1019
1020 assert_eq!(quote.decrease_percent, 200000);
1021 assert_eq!(quote.collateral_token, TOKEN_A);
1022 assert_eq!(quote.position_token, TOKEN_A);
1023 assert_eq!(quote.estimated_amount, 4_000_000_000);
1024 assert_eq!(quote.protocol_fee_a, 0);
1025 assert_eq!(quote.protocol_fee_b, 0);
1026 assert_eq!(quote.price_impact, 0.0);
1027 assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
1028 }
1029
1030 #[tokio::test]
1031 async fn decrease_non_leveraged_long_position_providing_b_reduce_only() {
1032 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1033 let fusion_pool = test_fusion_pool(sqrt_price);
1034
1035 let quote = get_decrease_spot_position_quote(
1036 200_000_000,
1037 TOKEN_B,
1038 1.0,
1039 true,
1040 Some(0),
1041 TOKEN_A,
1042 5_000_000_000, 0, (HUNDRED_PERCENT / 100) as u16,
1045 (HUNDRED_PERCENT / 200) as u16,
1046 NATIVE_MINT,
1047 TUNA_MINT,
1048 fusion_pool,
1049 Some(test_tick_arrays(fusion_pool)),
1050 )
1051 .await
1052 .unwrap();
1053
1054 assert_eq!(quote.decrease_percent, 200000);
1055 assert_eq!(quote.collateral_token, TOKEN_B);
1056 assert_eq!(quote.position_token, TOKEN_A);
1057 assert_eq!(quote.estimated_amount, 4_000_000_000);
1058 assert_eq!(quote.protocol_fee_a, 0);
1059 assert_eq!(quote.protocol_fee_b, 0);
1060 assert_eq!(quote.price_impact, 0.00008916842709072448);
1061 assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
1062 }
1063
1064 #[tokio::test]
1065 async fn decrease_long_position_providing_a_reduce_only() {
1066 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1067 let fusion_pool = test_fusion_pool(sqrt_price);
1068
1069 let quote = get_decrease_spot_position_quote(
1070 1_000_000_000,
1071 TOKEN_A,
1072 5.0,
1073 true,
1074 Some(0),
1075 TOKEN_A,
1076 5_000_000_000, 800_000_000, (HUNDRED_PERCENT / 100) as u16,
1079 (HUNDRED_PERCENT / 200) as u16,
1080 NATIVE_MINT,
1081 TUNA_MINT,
1082 fusion_pool,
1083 Some(test_tick_arrays(fusion_pool)),
1084 )
1085 .await
1086 .unwrap();
1087
1088 assert_eq!(quote.decrease_percent, 200000);
1089 assert_eq!(quote.collateral_token, TOKEN_A);
1090 assert_eq!(quote.position_token, TOKEN_A);
1091 assert_eq!(quote.collateral, 0);
1092 assert_eq!(quote.borrow, 0);
1093 assert_eq!(quote.estimated_amount, 4_000_000_000);
1094 assert_eq!(quote.protocol_fee_a, 0);
1095 assert_eq!(quote.protocol_fee_b, 0);
1096 assert_eq!(quote.price_impact, 0.00007155289528004705);
1097
1098 let quote = get_decrease_spot_position_quote(
1099 6_000_000_000,
1100 TOKEN_A,
1101 5.0,
1102 true,
1103 Some(0),
1104 TOKEN_A,
1105 5_000_000_000, 800_000_000, (HUNDRED_PERCENT / 100) as u16,
1108 (HUNDRED_PERCENT / 200) as u16,
1109 NATIVE_MINT,
1110 TUNA_MINT,
1111 fusion_pool,
1112 Some(test_tick_arrays(fusion_pool)),
1113 )
1114 .await
1115 .unwrap();
1116
1117 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1118 assert_eq!(quote.collateral, 0);
1119 assert_eq!(quote.borrow, 0);
1120 assert_eq!(quote.estimated_amount, 0);
1121 }
1122
1123 #[tokio::test]
1124 async fn decrease_long_position_providing_b_reduce_only() {
1125 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1126 let fusion_pool = test_fusion_pool(sqrt_price);
1127
1128 let quote = get_decrease_spot_position_quote(
1129 200_000_000,
1130 TOKEN_B,
1131 5.0,
1132 true,
1133 Some(0),
1134 TOKEN_A,
1135 5_000_000_000, 800_000_000, (HUNDRED_PERCENT / 100) as u16,
1138 (HUNDRED_PERCENT / 200) as u16,
1139 NATIVE_MINT,
1140 TUNA_MINT,
1141 fusion_pool,
1142 Some(test_tick_arrays(fusion_pool)),
1143 )
1144 .await
1145 .unwrap();
1146
1147 assert_eq!(quote.decrease_percent, 200000);
1148 assert_eq!(quote.collateral_token, TOKEN_B);
1149 assert_eq!(quote.position_token, TOKEN_A);
1150 assert_eq!(quote.collateral, 0);
1151 assert_eq!(quote.borrow, 0);
1152 assert_eq!(quote.estimated_amount, 4000000000);
1153 assert_eq!(quote.protocol_fee_a, 0);
1154 assert_eq!(quote.protocol_fee_b, 0);
1155 assert_eq!(quote.price_impact, 0.00008916842709072448);
1156
1157 let quote = get_decrease_spot_position_quote(
1158 1200_000_000,
1159 TOKEN_B,
1160 5.0,
1161 true,
1162 Some(0),
1163 TOKEN_A,
1164 5_000_000_000, 800_000_000, (HUNDRED_PERCENT / 100) as u16,
1167 (HUNDRED_PERCENT / 200) as u16,
1168 NATIVE_MINT,
1169 TUNA_MINT,
1170 fusion_pool,
1171 Some(test_tick_arrays(fusion_pool)),
1172 )
1173 .await
1174 .unwrap();
1175
1176 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1177 assert_eq!(quote.collateral, 0);
1178 assert_eq!(quote.borrow, 0);
1179 assert_eq!(quote.estimated_amount, 0);
1180 }
1181
1182 #[tokio::test]
1183 async fn decrease_long_position_providing_a_without_leverage() {
1184 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1185 let fusion_pool = test_fusion_pool(sqrt_price);
1186
1187 let quote = get_decrease_spot_position_quote(
1188 6_000_000_000,
1189 TOKEN_A,
1190 1.0,
1191 false,
1192 Some(100),
1193 TOKEN_A,
1194 5_000_000_000, 800_000_000, (HUNDRED_PERCENT / 100) as u16,
1197 (HUNDRED_PERCENT / 200) as u16,
1198 NATIVE_MINT,
1199 TUNA_MINT,
1200 fusion_pool,
1201 Some(test_tick_arrays(fusion_pool)),
1202 )
1203 .await
1204 .unwrap();
1205
1206 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1207 assert_eq!(quote.collateral_token, TOKEN_A);
1208 assert_eq!(quote.position_token, TOKEN_B);
1209 assert_eq!(quote.collateral, 1005025125);
1210 assert_eq!(quote.borrow, 0);
1211 assert_eq!(quote.estimated_amount, 199_319_781);
1212 assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
1213 assert_eq!(quote.increase_min_swap_output_amount, 197_326_583);
1214 assert_eq!(quote.protocol_fee_a, 5025125);
1215 assert_eq!(quote.protocol_fee_b, 0);
1216 assert_eq!(quote.price_impact, 0.00044685946117650754);
1217 }
1218
1219 #[tokio::test]
1220 async fn decrease_long_position_providing_a() {
1221 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1222 let fusion_pool = test_fusion_pool(sqrt_price);
1223
1224 let quote = get_decrease_spot_position_quote(
1225 6_000_000_000,
1226 TOKEN_A,
1227 5.0,
1228 false,
1229 Some(100),
1230 TOKEN_A,
1231 5_000_000_000, 800_000_000, (HUNDRED_PERCENT / 100) as u16,
1234 (HUNDRED_PERCENT / 200) as u16,
1235 NATIVE_MINT,
1236 TUNA_MINT,
1237 fusion_pool,
1238 Some(test_tick_arrays(fusion_pool)),
1239 )
1240 .await
1241 .unwrap();
1242
1243 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1244 assert_eq!(quote.collateral_token, TOKEN_A);
1245 assert_eq!(quote.position_token, TOKEN_B);
1246 assert_eq!(quote.collateral, 211433165);
1247 assert_eq!(quote.borrow, 800000000);
1248 assert_eq!(quote.estimated_amount, 199_793_344);
1249 assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
1250 assert_eq!(quote.increase_min_swap_output_amount, 197_795_410);
1251 assert_eq!(quote.protocol_fee_a, 1057165);
1252 assert_eq!(quote.protocol_fee_b, 8000000);
1253 assert_eq!(quote.price_impact, 0.0004470711974918773);
1254 }
1255
1256 #[tokio::test]
1257 async fn decrease_long_position_providing_b() {
1258 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1259 let fusion_pool = test_fusion_pool(sqrt_price);
1260
1261 let quote = get_decrease_spot_position_quote(
1262 1200_000_000,
1263 TOKEN_B,
1264 5.0,
1265 false,
1266 Some(0),
1267 TOKEN_A,
1268 5_000_000_000, 800_000_000, (HUNDRED_PERCENT / 100) as u16,
1271 (HUNDRED_PERCENT / 200) as u16,
1272 NATIVE_MINT,
1273 TUNA_MINT,
1274 fusion_pool,
1275 Some(test_tick_arrays(fusion_pool)),
1276 )
1277 .await
1278 .unwrap();
1279
1280 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1281 assert_eq!(quote.collateral_token, TOKEN_B);
1282 assert_eq!(quote.position_token, TOKEN_B);
1283 assert_eq!(quote.collateral, 42286633);
1284 assert_eq!(quote.borrow, 800_000_000);
1285 assert_eq!(quote.estimated_amount, 199_924_036);
1286 assert_eq!(quote.decrease_acceptable_swap_amount, 996_777_780);
1287 assert_eq!(quote.increase_min_swap_output_amount, 157_848_836);
1288 assert_eq!(quote.protocol_fee_a, 0);
1289 assert_eq!(quote.protocol_fee_b, 8211433);
1290 assert_eq!(quote.price_impact, 0.0005162980632488212);
1291 }
1292
1293 #[tokio::test]
1294 async fn decrease_non_leveraged_short_position_providing_a() {
1295 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1296 let fusion_pool = test_fusion_pool(sqrt_price);
1297
1298 let quote = get_decrease_spot_position_quote(
1299 6_000_000_000,
1300 TOKEN_A,
1301 1.0,
1302 false,
1303 Some(0),
1304 TOKEN_B,
1305 1000_000_000, 0, (HUNDRED_PERCENT / 100) as u16,
1308 (HUNDRED_PERCENT / 200) as u16,
1309 NATIVE_MINT,
1310 TUNA_MINT,
1311 fusion_pool,
1312 Some(test_tick_arrays(fusion_pool)),
1313 )
1314 .await
1315 .unwrap();
1316
1317 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1318 assert_eq!(quote.collateral_token, TOKEN_A);
1319 assert_eq!(quote.position_token, TOKEN_A);
1320 assert_eq!(quote.collateral, 1005025125);
1321 assert_eq!(quote.borrow, 0);
1322 assert_eq!(quote.estimated_amount, 1_000_000_000);
1323 assert_eq!(quote.protocol_fee_a, 5025125);
1324 assert_eq!(quote.protocol_fee_b, 0);
1325 assert_eq!(quote.price_impact, 0.00044592165429757635);
1326 }
1327
1328 #[tokio::test]
1329 async fn decrease_short_position_providing_a() {
1330 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1331 let fusion_pool = test_fusion_pool(sqrt_price);
1332
1333 let quote = get_decrease_spot_position_quote(
1334 6_000_000_000,
1335 TOKEN_A,
1336 5.0,
1337 false,
1338 Some(0),
1339 TOKEN_B,
1340 1000_000_000, 4_000_000_000, (HUNDRED_PERCENT / 100) as u16,
1343 (HUNDRED_PERCENT / 200) as u16,
1344 NATIVE_MINT,
1345 TUNA_MINT,
1346 fusion_pool,
1347 Some(test_tick_arrays(fusion_pool)),
1348 )
1349 .await
1350 .unwrap();
1351
1352 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1353 assert_eq!(quote.collateral_token, TOKEN_A);
1354 assert_eq!(quote.position_token, TOKEN_A);
1355 assert_eq!(quote.collateral, 211433165);
1356 assert_eq!(quote.borrow, 160000000);
1357 assert_eq!(quote.estimated_amount, 999_620_182);
1358 assert_eq!(quote.decrease_acceptable_swap_amount, 4_983_888_901);
1359 assert_eq!(quote.increase_min_swap_output_amount, 789_244_182);
1360 assert_eq!(quote.protocol_fee_a, 2657165);
1361 assert_eq!(quote.protocol_fee_b, 0);
1362 assert_eq!(quote.price_impact, 0.00051656476403882934);
1363 }
1364
1365 #[tokio::test]
1366 async fn decrease_short_position_providing_b() {
1367 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1368 let fusion_pool = test_fusion_pool(sqrt_price);
1369
1370 let quote = get_decrease_spot_position_quote(
1371 1_200_000_000,
1372 TOKEN_B,
1373 5.0,
1374 false,
1375 Some(0),
1376 TOKEN_B,
1377 1000_000_000, 4_000_000_000, (HUNDRED_PERCENT / 100) as u16,
1380 (HUNDRED_PERCENT / 200) as u16,
1381 NATIVE_MINT,
1382 TUNA_MINT,
1383 fusion_pool,
1384 Some(test_tick_arrays(fusion_pool)),
1385 )
1386 .await
1387 .unwrap();
1388
1389 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1390 assert_eq!(quote.collateral_token, TOKEN_B);
1391 assert_eq!(quote.position_token, TOKEN_A);
1392 assert_eq!(quote.collateral, 42_286_633);
1393 assert_eq!(quote.borrow, 160000000);
1394 assert_eq!(quote.estimated_amount, 998_966_727);
1395 assert_eq!(quote.protocol_fee_a, 1600000);
1396 assert_eq!(quote.protocol_fee_b, 211433);
1397 assert_eq!(quote.price_impact, 0.00044727115960174757);
1398 }
1399
1400 #[tokio::test]
1401 async fn tradable_amount_for_1x_long_position_providing_b() {
1402 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1403 let fusion_pool = test_fusion_pool(sqrt_price);
1404 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1405
1406 let collateral_token = TOKEN_B;
1407 let position_token = TOKEN_A;
1408 let leverage = 1.0;
1409 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1410 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1411 let available_balance = 200_000_000;
1412
1413 let tradable_amount = get_tradable_amount(
1414 collateral_token,
1415 available_balance,
1416 leverage,
1417 position_token,
1418 false,
1419 position_token,
1420 0,
1421 0,
1422 protocol_fee_rate,
1423 protocol_fee_rate_on_collateral,
1424 fusion_pool,
1425 tick_arrays.clone(),
1426 )
1427 .unwrap();
1428 assert_eq!(tradable_amount, 198403000);
1429
1430 let quote = get_increase_spot_position_quote(
1431 tradable_amount,
1432 collateral_token,
1433 position_token,
1434 leverage,
1435 Some(0),
1436 protocol_fee_rate,
1437 protocol_fee_rate_on_collateral,
1438 NATIVE_MINT,
1439 TUNA_MINT,
1440 fusion_pool,
1441 tick_arrays,
1442 )
1443 .await
1444 .unwrap();
1445 assert_eq!(quote.collateral, available_balance);
1446 }
1447
1448 #[tokio::test]
1449 async fn tradable_amount_for_5x_long_position_providing_b() {
1450 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1451 let fusion_pool = test_fusion_pool(sqrt_price);
1452 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1453
1454 let collateral_token = TOKEN_B;
1455 let position_token = TOKEN_A;
1456 let leverage = 5.0;
1457 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1458 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1459 let available_balance = 10_000_000;
1460
1461 let tradable_amount = get_tradable_amount(
1462 collateral_token,
1463 available_balance,
1464 leverage,
1465 position_token,
1466 false,
1467 position_token,
1468 0,
1469 0,
1470 protocol_fee_rate,
1471 protocol_fee_rate_on_collateral,
1472 fusion_pool,
1473 tick_arrays.clone(),
1474 )
1475 .unwrap();
1476 assert_eq!(tradable_amount, 47154380);
1477
1478 let quote = get_increase_spot_position_quote(
1479 tradable_amount,
1480 collateral_token,
1481 position_token,
1482 leverage,
1483 Some(0),
1484 protocol_fee_rate,
1485 protocol_fee_rate_on_collateral,
1486 NATIVE_MINT,
1487 TUNA_MINT,
1488 fusion_pool,
1489 tick_arrays,
1490 )
1491 .await
1492 .unwrap();
1493 assert_eq!(quote.collateral, available_balance + 1);
1495 }
1496
1497 #[tokio::test]
1498 async fn tradable_amount_for_5x_long_position_providing_a() {
1499 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1500 let fusion_pool = test_fusion_pool(sqrt_price);
1501 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1502
1503 let collateral_token = TOKEN_A;
1504 let position_token = TOKEN_A;
1505 let leverage = 5.0;
1506 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1507 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1508 let available_balance = 1_000_000_000;
1509
1510 let tradable_amount = get_tradable_amount(
1511 collateral_token,
1512 available_balance,
1513 leverage,
1514 position_token,
1515 false,
1516 position_token,
1517 0,
1518 0,
1519 protocol_fee_rate,
1520 protocol_fee_rate_on_collateral,
1521 fusion_pool,
1522 tick_arrays.clone(),
1523 )
1524 .unwrap();
1525 assert_eq!(tradable_amount, 4729626953);
1526
1527 let quote = get_increase_spot_position_quote(
1528 tradable_amount,
1529 collateral_token,
1530 position_token,
1531 leverage,
1532 Some(0),
1533 protocol_fee_rate,
1534 protocol_fee_rate_on_collateral,
1535 NATIVE_MINT,
1536 TUNA_MINT,
1537 fusion_pool,
1538 tick_arrays,
1539 )
1540 .await
1541 .unwrap();
1542 assert_eq!(quote.collateral, available_balance);
1543 }
1545
1546 #[tokio::test]
1547 async fn tradable_amount_for_5x_short_position_providing_b() {
1548 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1549 let fusion_pool = test_fusion_pool(sqrt_price);
1550 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1551
1552 let collateral_token = TOKEN_B;
1553 let position_token = TOKEN_B;
1554 let leverage = 5.0;
1555 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1556 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1557 let available_balance = 200_000_000;
1558
1559 let tradable_amount = get_tradable_amount(
1560 collateral_token,
1561 available_balance,
1562 leverage,
1563 position_token,
1564 false,
1565 position_token,
1566 0,
1567 0,
1568 protocol_fee_rate,
1569 protocol_fee_rate_on_collateral,
1570 fusion_pool,
1571 tick_arrays.clone(),
1572 )
1573 .unwrap();
1574 assert_eq!(tradable_amount, 945925390);
1575
1576 let quote = get_increase_spot_position_quote(
1577 tradable_amount,
1578 collateral_token,
1579 position_token,
1580 leverage,
1581 Some(0),
1582 protocol_fee_rate,
1583 protocol_fee_rate_on_collateral,
1584 NATIVE_MINT,
1585 TUNA_MINT,
1586 fusion_pool,
1587 tick_arrays,
1588 )
1589 .await
1590 .unwrap();
1591 assert_eq!(quote.collateral, available_balance + 1);
1593 }
1595
1596 #[tokio::test]
1597 async fn tradable_amount_for_reducing_existing_long_position() {
1598 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1599 let fusion_pool = test_fusion_pool(sqrt_price);
1600 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1601
1602 for i in 0..2 {
1603 let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1604 let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1605 let leverage = 5.0;
1606 let reduce_only = true;
1607 let position_amount = 5_000_000_000;
1608 let position_debt = 800_000_000;
1609 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1610 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1611 let available_balance = 50_000_000_000;
1612
1613 let tradable_amount = get_tradable_amount(
1614 collateral_token,
1615 available_balance,
1616 leverage,
1617 if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1618 reduce_only,
1619 position_token,
1620 position_amount,
1621 position_debt,
1622 protocol_fee_rate,
1623 protocol_fee_rate_on_collateral,
1624 fusion_pool,
1625 tick_arrays.clone(),
1626 )
1627 .unwrap();
1628 assert_eq!(tradable_amount, 5_000_000_000);
1629
1630 let quote = get_decrease_spot_position_quote(
1631 tradable_amount,
1632 collateral_token,
1633 5.0,
1634 reduce_only,
1635 Some(0),
1636 position_token,
1637 position_amount,
1638 position_debt,
1639 protocol_fee_rate,
1640 protocol_fee_rate_on_collateral,
1641 NATIVE_MINT,
1642 TUNA_MINT,
1643 fusion_pool,
1644 tick_arrays.clone(),
1645 )
1646 .await
1647 .unwrap();
1648
1649 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1650 }
1651 }
1652
1653 #[tokio::test]
1654 async fn tradable_amount_when_inverting_long_position_providing_a() {
1655 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1656 let fusion_pool = test_fusion_pool(sqrt_price);
1657 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1658
1659 let collateral_token = TOKEN_A;
1660 let position_token = TOKEN_A;
1661 let leverage = 5.0;
1662 let reduce_only = false;
1663 let position_amount = 5_000_000_000;
1664 let position_debt = 800_000_000;
1665 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1666 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1667 let available_balance = 2_000_000_000;
1668
1669 let tradable_amount = get_tradable_amount(
1670 collateral_token,
1671 available_balance,
1672 leverage,
1673 if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1674 reduce_only,
1675 position_token,
1676 position_amount,
1677 position_debt,
1678 protocol_fee_rate,
1679 protocol_fee_rate_on_collateral,
1680 fusion_pool,
1681 tick_arrays.clone(),
1682 )
1683 .unwrap();
1684 assert_eq!(tradable_amount, 19_086_173_788);
1685
1686 let quote = get_decrease_spot_position_quote(
1687 tradable_amount,
1688 collateral_token,
1689 5.0,
1690 reduce_only,
1691 Some(0),
1692 position_token,
1693 position_amount,
1694 position_debt,
1695 protocol_fee_rate,
1696 protocol_fee_rate_on_collateral,
1697 NATIVE_MINT,
1698 TUNA_MINT,
1699 fusion_pool,
1700 tick_arrays.clone(),
1701 )
1702 .await
1703 .unwrap();
1704
1705 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1706 assert_eq!(quote.collateral, 2_978_284_319);
1707 }
1708
1709 #[tokio::test]
1710 async fn tradable_amount_when_inverting_long_position_providing_b() {
1711 let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1712 let fusion_pool = test_fusion_pool(sqrt_price);
1713 let tick_arrays = Some(test_tick_arrays(fusion_pool));
1714
1715 let collateral_token = TOKEN_B;
1716 let position_token = TOKEN_A;
1717 let leverage = 5.0;
1718 let reduce_only = false;
1719 let position_amount = 5_000_000_000;
1720 let position_debt = 800_000_000;
1721 let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1722 let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1723 let available_balance = 500_000_000;
1724
1725 let tradable_amount = get_tradable_amount(
1726 collateral_token,
1727 available_balance,
1728 leverage,
1729 if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1730 reduce_only,
1731 position_token,
1732 position_amount,
1733 position_debt,
1734 protocol_fee_rate,
1735 protocol_fee_rate_on_collateral,
1736 fusion_pool,
1737 tick_arrays.clone(),
1738 )
1739 .unwrap();
1740 assert_eq!(tradable_amount, 4_295_498_968);
1741
1742 let quote = get_decrease_spot_position_quote(
1743 tradable_amount,
1744 collateral_token,
1745 5.0,
1746 reduce_only,
1747 Some(0),
1748 position_token,
1749 position_amount,
1750 position_debt,
1751 protocol_fee_rate,
1752 protocol_fee_rate_on_collateral,
1753 NATIVE_MINT,
1754 TUNA_MINT,
1755 fusion_pool,
1756 tick_arrays.clone(),
1757 )
1758 .await
1759 .unwrap();
1760
1761 assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1762 assert_eq!(quote.collateral, 696_777_779);
1763 }
1764}