Skip to main content

defituna_core/quote/
tuna_spot_position.rs

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")]
9//use serde_wasm_bindgen::Serializer;
10#[cfg(feature = "wasm")]
11use wasm_bindgen::prelude::wasm_bindgen;
12//#[cfg(feature = "wasm")]
13//use wasm_bindgen::JsValue;
14
15use crate::{
16    apply_swap_fee, apply_tuna_protocol_fee, calculate_tuna_protocol_fee, reverse_apply_swap_fee, reverse_apply_tuna_protocol_fee, HUNDRED_PERCENT,
17    INVALID_ARGUMENTS, TOKEN_A, TOKEN_B,
18};
19use fusionamm_core::{
20    sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_get_max_amount_with_slippage_tolerance,
21    try_get_min_amount_with_slippage_tolerance, try_mul_div, CoreError, FusionPoolFacade, TickArrays, TokenPair,
22};
23use libm::{ceil, round};
24
25pub const DEFAULT_SLIPPAGE_TOLERANCE_BPS: u16 = 100;
26
27/*
28#[cfg_attr(feature = "wasm", wasm_expose)]
29pub struct JupiterSwapInstruction {
30    pub data: Vec<u8>,
31    pub accounts: Vec<AccountMeta>,
32    pub address_lookup_table_addresses: Vec<Pubkey>,
33}
34*/
35
36#[cfg_attr(feature = "wasm", wasm_expose)]
37pub struct IncreaseSpotPositionQuoteResult {
38    /** Required collateral amount */
39    pub collateral: u64,
40    /** Required amount to borrow */
41    pub borrow: u64,
42    /** Estimated position size in the position token. */
43    pub estimated_amount: u64,
44    /** Swap input amount. */
45    pub swap_input_amount: u64,
46    /** Swap output amount. */
47    pub swap_output_amount: u64,
48    /** Minimum swap output amount according to the provided slippage. */
49    pub min_swap_output_amount: u64,
50    /** Protocol fee in token A */
51    pub protocol_fee_a: u64,
52    /** Protocol fee in token B */
53    pub protocol_fee_b: u64,
54    /** Price impact in percents (100% = 1.0) */
55    pub price_impact: f64,
56}
57
58#[cfg_attr(feature = "wasm", wasm_expose)]
59pub struct IncreaseSpotPositionEstimationResult {
60    /** Required collateral amount */
61    pub collateral: u64,
62    /** Required amount to borrow */
63    pub borrow: u64,
64    /** Estimated position size in the position token. */
65    pub estimated_amount: u64,
66    /** Swap input amount. */
67    pub swap_input_amount: u64,
68    /** Protocol fee in token A */
69    pub protocol_fee_a: u64,
70    /** Protocol fee in token B */
71    pub protocol_fee_b: u64,
72}
73
74struct SwapPool {
75    pub fusion_pool: FusionPoolFacade,
76    pub tick_arrays: TickArrays,
77    pub slippage_bps: Option<u16>,
78}
79
80fn get_increase_spot_position_quote_internal(
81    increase_amount: u64,
82    collateral_token: u8,
83    position_token: u8,
84    leverage: f64,
85    protocol_fee_rate: u16,
86    protocol_fee_rate_on_collateral: u16,
87    price: f64,
88    pool: Option<SwapPool>,
89) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
90    if collateral_token > TOKEN_B || position_token > TOKEN_B {
91        return Err(INVALID_ARGUMENTS.into());
92    }
93
94    if leverage < 1.0 {
95        return Err(INVALID_ARGUMENTS.into());
96    }
97
98    let borrow: u64;
99    let mut collateral: u64;
100    let mut estimated_amount: u64 = 0;
101    let mut swap_input_amount: u64;
102    let mut swap_output_amount: u64 = 0;
103    let mut min_swap_output_amount: u64 = 0;
104    let mut price_impact: f64 = 0.0;
105
106    let (fee_rate, slippage_bps) = match pool.as_ref() {
107        None => (0, 0),
108        Some(pool) => (pool.fusion_pool.fee_rate, pool.slippage_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS)),
109    };
110
111    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
112    let swap_input_token_is_a = borrowed_token == TOKEN_A;
113
114    if borrowed_token == collateral_token {
115        borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
116        collateral = increase_amount - apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fee_rate, false)?;
117        collateral = reverse_apply_swap_fee(collateral, fee_rate, false)?;
118        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
119
120        swap_input_amount = collateral + borrow;
121    } else {
122        let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
123        let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
124
125        borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
126
127        let borrow_in_position_token_with_fees_applied =
128            apply_swap_fee(apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?, fee_rate, false)?;
129
130        collateral = increase_amount - borrow_in_position_token_with_fees_applied;
131        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
132
133        swap_input_amount = borrow;
134    }
135
136    let protocol_fee = calculate_tuna_spot_position_protocol_fee(
137        collateral_token,
138        borrowed_token,
139        collateral,
140        borrow,
141        protocol_fee_rate_on_collateral,
142        protocol_fee_rate,
143    );
144
145    swap_input_amount -= if swap_input_token_is_a { protocol_fee.a } else { protocol_fee.b };
146
147    if position_token == collateral_token {
148        estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee.a } else { protocol_fee.b };
149    }
150
151    if swap_input_amount > 0 {
152        if let Some(pool) = pool {
153            let quote = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
154            estimated_amount += quote.token_est_out;
155            swap_output_amount = quote.token_est_out;
156            min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(swap_output_amount, slippage_bps)?;
157            //let new_price = sqrt_price_to_price(quote.next_sqrt_price.into(), 1, 1);
158            //price_impact = (new_price / price - 1.0).abs();
159            price_impact = if swap_input_token_is_a {
160                (swap_input_amount as f64 - swap_output_amount as f64 / price) / swap_input_amount as f64
161            } else {
162                (swap_input_amount as f64 - swap_output_amount as f64 * price) / swap_input_amount as f64
163            }
164        }
165    }
166
167    Ok(IncreaseSpotPositionQuoteResult {
168        collateral,
169        borrow,
170        estimated_amount,
171        swap_input_amount,
172        swap_output_amount,
173        min_swap_output_amount,
174        protocol_fee_a: protocol_fee.a,
175        protocol_fee_b: protocol_fee.b,
176        price_impact,
177    })
178}
179
180/// Spot position increase quote
181///
182/// # Parameters
183/// - `increase_amount`: Position total size in the collateral_token.
184/// - `collateral_token`: Collateral token.
185/// - `position_token`: Token of the position.
186/// - `leverage`: Leverage (1.0 or higher).
187/// - `slippage_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
188/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
189/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
190/// - `fusion_pool`: Fusion pool.
191/// - `tick_arrays`: Optional five tick arrays around the current pool price. If not provided, the quote will be calculated using the Jupiter Aggregator.
192///
193/// # Returns
194/// - `IncreaseSpotPositionQuoteResult`: quote result
195#[cfg_attr(feature = "wasm", wasm_expose)]
196pub fn get_increase_spot_position_quote(
197    increase_amount: u64,
198    collateral_token: u8,
199    position_token: u8,
200    leverage: f64,
201    slippage_bps: Option<u16>,
202    protocol_fee_rate: u16,
203    protocol_fee_rate_on_collateral: u16,
204    fusion_pool: FusionPoolFacade,
205    tick_arrays: TickArrays,
206) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
207    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
208
209    get_increase_spot_position_quote_internal(
210        increase_amount,
211        collateral_token,
212        position_token,
213        leverage,
214        protocol_fee_rate,
215        protocol_fee_rate_on_collateral,
216        price,
217        Some(SwapPool {
218            fusion_pool,
219            tick_arrays,
220            slippage_bps,
221        }),
222    )
223}
224
225/// Spot position increase estimation
226///
227/// # Parameters
228/// - `increase_amount`: Position total size in the collateral_token.
229/// - `collateral_token`: Collateral token.
230/// - `position_token`: Token of the position.
231/// - `leverage`: Leverage (1.0 or higher).
232/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
233/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
234/// - `price`: Pool price.
235///
236/// # Returns
237/// - `IncreaseSpotPositionEstimationResult`: quote result
238#[cfg_attr(feature = "wasm", wasm_expose)]
239pub fn get_increase_spot_position_estimation(
240    increase_amount: u64,
241    collateral_token: u8,
242    position_token: u8,
243    leverage: f64,
244    protocol_fee_rate: u16,
245    protocol_fee_rate_on_collateral: u16,
246    price: f64,
247) -> Result<IncreaseSpotPositionEstimationResult, CoreError> {
248    let quote = get_increase_spot_position_quote_internal(
249        increase_amount,
250        collateral_token,
251        position_token,
252        leverage,
253        protocol_fee_rate,
254        protocol_fee_rate_on_collateral,
255        price,
256        None,
257    )?;
258
259    Ok(IncreaseSpotPositionEstimationResult {
260        collateral: quote.collateral,
261        borrow: quote.borrow,
262        estimated_amount: quote.estimated_amount,
263        swap_input_amount: quote.swap_input_amount,
264        protocol_fee_a: quote.protocol_fee_a,
265        protocol_fee_b: quote.protocol_fee_b,
266    })
267}
268
269/*
270
271/// Spot position increase quote
272///
273/// # Parameters
274/// - `increase_amount`: Position total size in the collateral_token.
275/// - `collateral_token`: Collateral token.
276/// - `position_token`: Token of the position.
277/// - `leverage`: Leverage (1.0 or higher).
278/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
279/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
280/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
281/// - `mint_a`: Token A mint address
282/// - `mint_b`: Token B mint address
283/// - `fusion_pool`: Fusion pool.
284/// - `tick_arrays`: Optional five tick arrays around the current pool price. If not provided, the quote will be calculated using the Jupiter Aggregator.
285///
286/// # Returns
287/// - `IncreaseSpotPositionQuoteResult`: quote result
288#[cfg(feature = "wasm")]
289#[wasm_bindgen(js_name = "getIncreaseSpotPositionQuote", skip_jsdoc)]
290pub async fn wasm_get_increase_spot_position_quote(
291    increase_amount: u64,
292    collateral_token: u8,
293    position_token: u8,
294    leverage: f64,
295    slippage_tolerance_bps: Option<u16>,
296    protocol_fee_rate: u16,
297    protocol_fee_rate_on_collateral: u16,
298    mint_a: Pubkey,
299    mint_b: Pubkey,
300    fusion_pool: FusionPoolFacade,
301) -> Result<JsValue, JsValue> {
302    let result = get_increase_spot_position_quote(
303        increase_amount,
304        collateral_token,
305        position_token,
306        leverage,
307        slippage_tolerance_bps,
308        protocol_fee_rate,
309        protocol_fee_rate_on_collateral,
310        mint_a,
311        mint_b,
312        fusion_pool,
313        tick_arrays,
314    )
315    .await
316    .map_err(|e| JsValue::from_str(e))?;
317
318    let serializer = Serializer::new().serialize_maps_as_objects(true);
319    let js_value = result.serialize(&serializer).unwrap();
320
321    Ok(js_value)
322}
323*/
324
325#[cfg_attr(feature = "wasm", wasm_expose)]
326pub struct DecreaseSpotPositionQuoteResult {
327    /** Position decrease percentage */
328    pub decrease_percent: u32,
329    /** Exact in or exact out swap. */
330    pub swap_exact_in: bool,
331    /** Swap input amount. */
332    pub swap_input_amount: u64,
333    /** Swap output amount according. */
334    pub swap_output_amount: u64,
335    /** if swap_exact_in is false, it's the maximum acceptable swap input amount for position decrease according to the provided slippage.
336     *  if swap_exact_in is true, it's the minimum swap output amount.
337     *  This value is passed directly to the spot position modify instruction.
338     */
339    pub required_swap_amount: u64,
340    /** Estimated total amount of the adjusted position. */
341    pub estimated_amount: u64,
342    /** Estimated value of a debt that will be repaid. */
343    pub estimated_payable_debt: u64,
344    /** Estimated collateral that will be withdrawn from the position. */
345    pub estimated_collateral_to_be_withdrawn: u64,
346    /** Price impact in percents (100% = 1.0) */
347    pub price_impact: f64,
348}
349
350#[cfg_attr(feature = "wasm", wasm_expose)]
351pub struct DecreaseSpotPositionEstimationResult {
352    /** Position decrease percentage. */
353    pub decrease_percent: u32,
354    /** Exact in or exact out swap. */
355    pub swap_exact_in: bool,
356    /** Swap input or output amount. */
357    pub swap_amount: u64,
358    /** Estimated total amount of the adjusted position. */
359    pub estimated_amount: u64,
360    /** Estimated value of a debt that will be repaid. */
361    pub estimated_payable_debt: u64,
362}
363
364fn get_decrease_spot_position_quote_internal(
365    decrease_amount: u64,
366    collateral_token: u8,
367    leverage: f64,
368    position_token: u8,
369    position_amount: u64,
370    position_debt: u64,
371    price: f64,
372    pool: Option<SwapPool>,
373) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
374    if collateral_token > TOKEN_B || position_token > TOKEN_B {
375        return Err(INVALID_ARGUMENTS.into());
376    }
377
378    if leverage < 1.0 {
379        return Err(INVALID_ARGUMENTS.into());
380    }
381
382    let slippage_bps = match pool.as_ref() {
383        None => 0,
384        Some(pool) => pool.slippage_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS),
385    };
386
387    let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
388    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
389
390    let mut required_swap_amount: u64 = 0;
391
392    let mut decrease_amount_in_position_token = if collateral_token == position_token {
393        decrease_amount
394    } else {
395        round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
396    };
397
398    decrease_amount_in_position_token = position_amount.min(decrease_amount_in_position_token);
399
400    let decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
401
402    let estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
403    let estimated_payable_debt = try_mul_div(position_debt, decrease_percent as u128, HUNDRED_PERCENT as u128, true)?;
404    let mut estimated_collateral_to_be_withdrawn = 0;
405
406    //let mut next_sqrt_price = fusion_pool.sqrt_price;
407    let swap_exact_in: bool;
408    let mut swap_input_amount = 0;
409    let mut swap_output_amount = 0;
410
411    if collateral_token == position_token {
412        swap_exact_in = false;
413        if position_debt > 0 {
414            swap_output_amount = estimated_payable_debt;
415            if let Some(pool) = pool {
416                let swap =
417                    swap_quote_by_output_token(swap_output_amount, borrowed_token == TOKEN_A, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
418                swap_input_amount = swap.token_est_in;
419                //next_sqrt_price = swap.next_sqrt_price;
420                required_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_bps)?;
421                estimated_collateral_to_be_withdrawn = position_amount.saturating_sub(swap.token_est_in).saturating_sub(estimated_amount);
422            }
423        } else {
424            estimated_collateral_to_be_withdrawn = position_amount - estimated_amount;
425        }
426    } else {
427        swap_exact_in = true;
428        swap_input_amount = position_amount - estimated_amount;
429        if let Some(pool) = pool {
430            let swap = swap_quote_by_input_token(swap_input_amount, position_token == TOKEN_A, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
431            //next_sqrt_price = swap.next_sqrt_price;
432            swap_output_amount = swap.token_est_out;
433            required_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_bps)?;
434            estimated_collateral_to_be_withdrawn = swap.token_est_out.saturating_sub(estimated_payable_debt);
435        }
436    }
437
438    //let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
439    //let price_impact = (new_price / price - 1.0).abs();
440
441    let price_impact = if swap_input_amount > 0 {
442        if position_token == TOKEN_A {
443            (swap_input_amount as f64 - swap_output_amount as f64 / price) / swap_input_amount as f64
444        } else {
445            (swap_input_amount as f64 - swap_output_amount as f64 * price) / swap_input_amount as f64
446        }
447    } else {
448        0.0
449    };
450
451    Ok(DecreaseSpotPositionQuoteResult {
452        decrease_percent,
453        estimated_payable_debt,
454        estimated_collateral_to_be_withdrawn,
455        swap_exact_in,
456        swap_input_amount,
457        swap_output_amount,
458        required_swap_amount,
459        estimated_amount,
460        price_impact,
461    })
462}
463
464/// Spot position decrease quote
465///
466/// # Parameters
467/// - `decrease_amount`: Position total decrease size in the collateral_token.
468/// - `collateral_token`: Collateral token.
469/// - `leverage`: Leverage (1.0 or higher).
470/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
471/// - `position_token`: Token of the existing position.
472/// - `position_amount`: Existing position amount in the position_token.
473/// - `position_debt`: Existing position debt in the token opposite to the position_token.
474/// - `fusion_pool`: Fusion pool.
475/// - `tick_arrays`: Optional five tick arrays around the current pool price.
476///
477/// # Returns
478/// - `DecreaseSpotPositionQuoteResult`: quote result
479#[cfg_attr(feature = "wasm", wasm_expose)]
480pub fn get_decrease_spot_position_quote(
481    decrease_amount: u64,
482    collateral_token: u8,
483    leverage: f64,
484    slippage_bps: Option<u16>,
485    position_token: u8,
486    position_amount: u64,
487    position_debt: u64,
488    fusion_pool: FusionPoolFacade,
489    tick_arrays: TickArrays,
490) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
491    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
492    get_decrease_spot_position_quote_internal(
493        decrease_amount,
494        collateral_token,
495        leverage,
496        position_token,
497        position_amount,
498        position_debt,
499        price,
500        Some(SwapPool {
501            fusion_pool,
502            tick_arrays,
503            slippage_bps,
504        }),
505    )
506}
507
508/// Spot position decrease estimation
509///
510/// # Parameters
511/// - `decrease_amount`: Position total decrease size in the collateral_token.
512/// - `collateral_token`: Collateral token.
513/// - `leverage`: Leverage (1.0 or higher).
514/// - `position_token`: Token of the existing position.
515/// - `position_amount`: Existing position amount in the position_token.
516/// - `position_debt`: Existing position debt in the token opposite to the position_token.
517/// - `price`: Pool price.
518///
519/// # Returns
520/// - `DecreaseSpotPositionQuoteResult`: quote result
521#[cfg_attr(feature = "wasm", wasm_expose)]
522pub fn get_decrease_spot_position_estimation(
523    decrease_amount: u64,
524    collateral_token: u8,
525    leverage: f64,
526    position_token: u8,
527    position_amount: u64,
528    position_debt: u64,
529    price: f64,
530) -> Result<DecreaseSpotPositionEstimationResult, CoreError> {
531    let quote = get_decrease_spot_position_quote_internal(
532        decrease_amount,
533        collateral_token,
534        leverage,
535        position_token,
536        position_amount,
537        position_debt,
538        price,
539        None,
540    )?;
541
542    Ok(DecreaseSpotPositionEstimationResult {
543        decrease_percent: quote.decrease_percent,
544        swap_exact_in: quote.swap_exact_in,
545        swap_amount: if quote.swap_exact_in {
546            quote.swap_input_amount
547        } else {
548            quote.swap_output_amount
549        },
550        estimated_amount: quote.estimated_amount,
551        estimated_payable_debt: quote.estimated_payable_debt,
552    })
553}
554
555/*
556/// Spot position decrease quote
557///
558/// # Parameters
559/// - `decrease_amount`: Position total decrease size in the collateral_token.
560/// - `collateral_token`: Collateral token.
561/// - `leverage`: Leverage (1.0 or higher).
562/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
563/// - `position_token`: Token of the existing position.
564/// - `position_amount`: Existing position amount in the position_token.
565/// - `position_debt`: Existing position debt in the token opposite to the position_token.
566/// - `fusion_pool`: Fusion pool.
567/// - `tick_arrays`: Optional five tick arrays around the current pool price.
568///
569/// # Returns
570/// - `DecreaseSpotPositionQuoteResult`: quote result
571#[cfg(feature = "wasm")]
572#[wasm_bindgen(js_name = "getDecreaseSpotPositionQuote", skip_jsdoc)]
573pub async fn wasm_get_decrease_spot_position_quote(
574    decrease_amount: u64,
575    collateral_token: u8,
576    leverage: f64,
577    slippage_tolerance_bps: Option<u16>,
578    position_token: u8,
579    position_amount: u64,
580    position_debt: u64,
581    mint_a: Pubkey,
582    mint_b: Pubkey,
583    fusion_pool: FusionPoolFacade,
584    tick_arrays: Option<TickArrays>,
585) -> Result<JsValue, JsValue> {
586    let result = get_decrease_spot_position_quote(
587        decrease_amount,
588        collateral_token,
589        leverage,
590        slippage_tolerance_bps,
591        position_token,
592        position_amount,
593        position_debt,
594        mint_a,
595        mint_b,
596        fusion_pool,
597        tick_arrays,
598    )
599    .await
600    .map_err(|e| JsValue::from_str(e))?;
601
602    let serializer = Serializer::new().serialize_maps_as_objects(true);
603    let js_value = result.serialize(&serializer).unwrap();
604
605    Ok(js_value)
606}
607*/
608
609/// Returns the liquidation price
610///
611/// # Parameters
612/// - `position_token`: Token of the position
613/// - `amount`: Position total size
614/// - `debt`: Position total debt
615/// - `liquidation_threshold`: Liquidation threshold of a market
616///
617/// # Returns
618/// - `f64`: Decimal liquidation price
619#[cfg_attr(feature = "wasm", wasm_expose)]
620pub fn get_spot_position_liquidation_price(position_token: u8, amount: u64, debt: u64, liquidation_threshold: u32) -> Result<f64, CoreError> {
621    if liquidation_threshold >= HUNDRED_PERCENT {
622        return Err(INVALID_ARGUMENTS);
623    }
624
625    if debt == 0 || amount == 0 {
626        return Ok(0.0);
627    }
628
629    let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
630
631    if position_token == TOKEN_A {
632        Ok(debt as f64 / (amount as f64 * liquidation_threshold_f))
633    } else {
634        Ok((amount as f64 * liquidation_threshold_f) / debt as f64)
635    }
636}
637
638/// Calculates the maximum tradable amount in the collateral token.
639///
640/// # Parameters
641/// - `collateral_token`: Collateral token.
642/// - `available_balance`: Available wallet balance in the collateral_token.
643/// - `leverage`: Leverage (1.0 or higher).
644/// - `position_token`: Token of the existing position. Should be set to new_position_token if position_amount is zero.
645/// - `position_amount`: Existing position amount in the position_token.
646/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
647/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
648/// - 'swap_fee_rate': Pool swap fee rate.
649/// - `price`: Pool price.
650/// - `increase`: true if increasing the position
651///
652/// # Returns
653/// - `u64`: the maximum tradable amount
654#[cfg_attr(feature = "wasm", wasm_expose)]
655pub fn get_tradable_amount(
656    collateral_token: u8,
657    available_balance: u64,
658    leverage: f64,
659    position_token: u8,
660    position_amount: u64,
661    protocol_fee_rate: u16,
662    protocol_fee_rate_on_collateral: u16,
663    swap_fee_rate: u16,
664    price: f64,
665    increase: bool,
666) -> Result<u64, CoreError> {
667    if collateral_token > TOKEN_B || position_token > TOKEN_B {
668        return Err(INVALID_ARGUMENTS.into());
669    }
670
671    if leverage < 1.0 {
672        return Err(INVALID_ARGUMENTS.into());
673    }
674
675    // T = Câ‹…Fcâ‹…Fs + Bâ‹…Fbâ‹…Fs, where: Fc/Fb/Fs - collateral/borrow/swap fee multiplier
676    // B = Tâ‹…(L - 1) / L
677    // => T = Câ‹…Fcâ‹…Fs / (1 - Fbâ‹…Fsâ‹…(L - 1) / L)
678    let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
679        let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
680        if collateral_token != position_token {
681            collateral = apply_swap_fee(collateral, swap_fee_rate, false)?;
682        }
683
684        let fee_multiplier = (1.0 - protocol_fee_rate as f64 / HUNDRED_PERCENT as f64) * (1.0 - swap_fee_rate as f64 / 1_000_000.0);
685        let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
686        Ok(total)
687    };
688
689    let available_to_trade = if increase {
690        add_leverage(available_balance)?
691    } else {
692        let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
693
694        if collateral_token == position_token {
695            position_amount
696        } else {
697            round(position_amount as f64 * position_to_opposite_token_price) as u64
698        }
699    };
700
701    Ok(available_to_trade)
702}
703
704/*
705struct JupiterSwapResult {
706    pub instruction: JupiterSwapInstruction,
707    pub out_amount: u64,
708    pub other_amount_threshold: u64,
709    pub price_impact_pct: f64,
710}
711
712async fn jupiter_swap_quote(
713    tuna_position_address: Pubkey,
714    input_mint: Pubkey,
715    output_mint: Pubkey,
716    amount: u64,
717    slippage_bps: Option<u64>,
718) -> Result<JupiterSwapResult, CoreError> {
719    let quote_config = QuoteConfig {
720        slippage_bps,
721        swap_mode: Some(SwapMode::ExactIn),
722        dexes: None,
723        exclude_dexes: Some(vec!["Pump.fun Amm".to_string(), "Pump.fun".to_string()]),
724        only_direct_routes: false,
725        as_legacy_transaction: None,
726        platform_fee_bps: None,
727        max_accounts: Some(45),
728    };
729
730    let quote = jup_ag::quote(input_mint, output_mint, amount, quote_config)
731        .await
732        .map_err(|_| JUPITER_QUOTE_REQUEST_ERROR)?;
733
734    #[allow(deprecated)]
735    let swap_request = SwapRequest {
736        user_public_key: tuna_position_address,
737        wrap_and_unwrap_sol: None,
738        use_shared_accounts: Some(true),
739        fee_account: None,
740        compute_unit_price_micro_lamports: None,
741        prioritization_fee_lamports: PrioritizationFeeLamports::Auto,
742        as_legacy_transaction: None,
743        use_token_ledger: None,
744        destination_token_account: None,
745        quote_response: quote.clone(),
746    };
747
748    let swap_response = jup_ag::swap_instructions(swap_request)
749        .await
750        .map_err(|_| JUPITER_SWAP_INSTRUCTIONS_REQUEST_ERROR)?;
751
752    Ok(JupiterSwapResult {
753        instruction: JupiterSwapInstruction {
754            data: swap_response.swap_instruction.data,
755            accounts: swap_response.swap_instruction.accounts,
756            address_lookup_table_addresses: swap_response.address_lookup_table_addresses,
757        },
758        out_amount: quote.out_amount,
759        other_amount_threshold: quote.other_amount_threshold,
760        price_impact_pct: quote.price_impact_pct,
761    })
762}
763*/
764
765#[cfg_attr(feature = "wasm", wasm_expose)]
766pub fn calculate_tuna_spot_position_protocol_fee(
767    collateral_token: u8,
768    borrowed_token: u8,
769    collateral: u64,
770    borrow: u64,
771    protocol_fee_rate_on_collateral: u16,
772    protocol_fee_rate: u16,
773) -> TokenPair {
774    let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
775    let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
776    let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
777    let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
778
779    let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, protocol_fee_rate_on_collateral, protocol_fee_rate);
780    let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, protocol_fee_rate_on_collateral, protocol_fee_rate);
781
782    TokenPair {
783        a: protocol_fee_a,
784        b: protocol_fee_b,
785    }
786}
787
788#[cfg(all(test, not(feature = "wasm")))]
789mod tests {
790    use super::*;
791    use crate::assert_approx_eq;
792    use fusionamm_core::{
793        get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
794    };
795
796    fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
797        let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
798        FusionPoolFacade {
799            tick_current_index,
800            fee_rate: 3000,
801            liquidity: 10000000000000,
802            sqrt_price,
803            tick_spacing: 2,
804            ..FusionPoolFacade::default()
805        }
806    }
807
808    fn test_tick(liquidity_net: i128) -> TickFacade {
809        TickFacade {
810            initialized: true,
811            liquidity_net,
812            ..TickFacade::default()
813        }
814    }
815
816    fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
817        TickArrayFacade {
818            start_tick_index,
819            ticks: [test_tick(0); TICK_ARRAY_SIZE],
820        }
821    }
822
823    fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
824        let tick_spacing = fusion_pool.tick_spacing;
825        let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
826        let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
827
828        [
829            test_tick_array(tick_array_start_index),
830            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
831            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
832            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
833            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
834        ]
835        .into()
836    }
837
838    #[test]
839    fn test_get_liquidation_price() {
840        assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 0, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
841        assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 0, 5, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
842        assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 800, HUNDRED_PERCENT * 85 / 100), Ok(188.23529411764707));
843        assert_eq!(get_spot_position_liquidation_price(TOKEN_B, 1000, 4, HUNDRED_PERCENT * 85 / 100), Ok(212.5));
844    }
845
846    #[tokio::test]
847    async fn increase_long_position_providing_token_a() {
848        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
849        let fusion_pool = test_fusion_pool(sqrt_price);
850
851        let quote = get_increase_spot_position_quote(
852            5_000_000_000,
853            TOKEN_A,
854            TOKEN_A,
855            5.0,
856            Some(0),
857            (HUNDRED_PERCENT / 100) as u16,
858            (HUNDRED_PERCENT / 200) as u16,
859            fusion_pool,
860            test_tick_arrays(fusion_pool),
861        )
862        .unwrap();
863
864        assert_eq!(quote.collateral, 1057165829);
865        assert_eq!(quote.borrow, 800000000);
866        assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
867        assert_eq!(quote.estimated_amount, 4_999_303_011);
868        assert_eq!(quote.protocol_fee_a, 5285829);
869        assert_eq!(quote.protocol_fee_b, 8000000);
870        assert_eq!(quote.price_impact, 0.0031760073232324137);
871        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 * 1000.0) / 200.0), 5.0, 0.1);
872    }
873
874    #[tokio::test]
875    async fn increase_long_position_providing_token_b() {
876        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
877        let fusion_pool = test_fusion_pool(sqrt_price);
878
879        let quote = get_increase_spot_position_quote(
880            5000_000_000,
881            TOKEN_B,
882            TOKEN_A,
883            5.0,
884            Some(0),
885            (HUNDRED_PERCENT / 100) as u16,
886            (HUNDRED_PERCENT / 200) as u16,
887            fusion_pool,
888            test_tick_arrays(fusion_pool),
889        )
890        .unwrap();
891
892        assert_eq!(quote.collateral, 1060346869);
893        assert_eq!(quote.borrow, 4000000000);
894        assert_eq!(quote.estimated_amount, 24_972_080_293);
895        assert_eq!(quote.protocol_fee_a, 0);
896        assert_eq!(quote.protocol_fee_b, 45301734);
897        assert_eq!(quote.price_impact, 0.004113437834493303);
898        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 * 1000.0) / 200.0), 5.0, 0.1);
899    }
900
901    #[tokio::test]
902    async fn increase_short_position_providing_a() {
903        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
904        let fusion_pool = test_fusion_pool(sqrt_price);
905
906        let quote = get_increase_spot_position_quote(
907            5_000_000_000,
908            TOKEN_A,
909            TOKEN_B,
910            5.0,
911            Some(0),
912            (HUNDRED_PERCENT / 100) as u16,
913            (HUNDRED_PERCENT / 200) as u16,
914            fusion_pool,
915            test_tick_arrays(fusion_pool),
916        )
917        .unwrap();
918
919        assert_eq!(quote.collateral, 1060346869);
920        assert_eq!(quote.borrow, 4000000000);
921        assert_eq!(quote.estimated_amount, 999_776_441);
922        assert_eq!(quote.protocol_fee_a, 45301734);
923        assert_eq!(quote.protocol_fee_b, 0);
924        assert_eq!(quote.price_impact, 0.003222888242261054);
925        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 5.0, 0.1);
926    }
927
928    #[tokio::test]
929    async fn increase_short_position_providing_b() {
930        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
931        let fusion_pool = test_fusion_pool(sqrt_price);
932
933        let quote = get_increase_spot_position_quote(
934            5000_000_000,
935            TOKEN_B,
936            TOKEN_B,
937            5.0,
938            Some(0),
939            (HUNDRED_PERCENT / 100) as u16,
940            (HUNDRED_PERCENT / 200) as u16,
941            fusion_pool,
942            test_tick_arrays(fusion_pool),
943        )
944        .unwrap();
945
946        assert_eq!(quote.collateral, 1057165829);
947        assert_eq!(quote.borrow, 20000000000);
948        assert_eq!(quote.estimated_amount, 4996_517_564);
949        assert_eq!(quote.protocol_fee_a, 200000000);
950        assert_eq!(quote.protocol_fee_b, 5285829);
951        assert_eq!(quote.price_impact, 0.0038794030303030305);
952        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 5.0, 0.1);
953    }
954
955    #[tokio::test]
956    async fn increase_quote_with_slippage() {
957        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
958        let fusion_pool = test_fusion_pool(sqrt_price);
959
960        // with slippage 10%
961        let quote =
962            get_increase_spot_position_quote(200_000, TOKEN_B, TOKEN_A, 5.0, Some(1000), 0, 0, fusion_pool, test_tick_arrays(fusion_pool)).unwrap();
963        assert_eq!(quote.min_swap_output_amount, 899_994);
964
965        // without slippage
966        let quote =
967            get_increase_spot_position_quote(200_000, TOKEN_B, TOKEN_A, 5.0, Some(0), 0, 0, fusion_pool, test_tick_arrays(fusion_pool)).unwrap();
968        assert_eq!(quote.min_swap_output_amount, 999_994);
969    }
970
971    #[tokio::test]
972    async fn decrease_non_leveraged_long_position_providing_a() {
973        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
974        let fusion_pool = test_fusion_pool(sqrt_price);
975
976        let quote = get_decrease_spot_position_quote(
977            1_000_000_000,
978            TOKEN_A,
979            1.0,
980            Some(0),
981            TOKEN_A,
982            5_000_000_000, // A
983            0,             // B
984            fusion_pool,
985            test_tick_arrays(fusion_pool),
986        )
987        .unwrap();
988
989        assert_eq!(quote.decrease_percent, 200000);
990        assert_eq!(quote.estimated_amount, 4_000_000_000);
991        assert_eq!(quote.estimated_payable_debt, 0);
992        assert_eq!(quote.estimated_collateral_to_be_withdrawn, 1_000_000_000);
993        assert_eq!(quote.price_impact, 0.0);
994    }
995
996    #[tokio::test]
997    async fn decrease_non_leveraged_long_position_providing_b() {
998        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
999        let fusion_pool = test_fusion_pool(sqrt_price);
1000
1001        let quote = get_decrease_spot_position_quote(
1002            200_000_000,
1003            TOKEN_B,
1004            1.0,
1005            Some(0),
1006            TOKEN_A,
1007            5_000_000_000, // A
1008            0,             // B
1009            fusion_pool,
1010            test_tick_arrays(fusion_pool),
1011        )
1012        .unwrap();
1013
1014        assert_eq!(quote.decrease_percent, 200000);
1015        assert_eq!(quote.estimated_amount, 4_000_000_000);
1016        assert_eq!(quote.estimated_payable_debt, 0);
1017        assert_eq!(quote.estimated_collateral_to_be_withdrawn, 199_391_108);
1018        assert_eq!(quote.price_impact, 0.003044459999999881);
1019    }
1020
1021    #[tokio::test]
1022    async fn decrease_long_position_providing_a() {
1023        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1024        let fusion_pool = test_fusion_pool(sqrt_price);
1025
1026        let quote = get_decrease_spot_position_quote(
1027            1_000_000_000,
1028            TOKEN_A,
1029            5.0,
1030            Some(0),
1031            TOKEN_A,
1032            5_000_000_000, // A
1033            800_000_000,   // B
1034            fusion_pool,
1035            test_tick_arrays(fusion_pool),
1036        )
1037        .unwrap();
1038
1039        assert_eq!(quote.decrease_percent, 200000);
1040        assert_eq!(quote.estimated_amount, 4_000_000_000);
1041        assert_eq!(quote.estimated_payable_debt, 160_000_000);
1042        assert_eq!(quote.swap_exact_in, false);
1043        assert_eq!(quote.swap_input_amount, 802_435_931);
1044        assert_eq!(quote.swap_output_amount, 160_000_000);
1045        assert_eq!(quote.estimated_collateral_to_be_withdrawn, 197_564_069);
1046        assert_eq!(quote.price_impact, 0.0030356703954722095);
1047
1048        let quote = get_decrease_spot_position_quote(
1049            6_000_000_000,
1050            TOKEN_A,
1051            5.0,
1052            Some(0),
1053            TOKEN_A,
1054            5_000_000_000, // A
1055            800_000_000,   // B
1056            fusion_pool,
1057            test_tick_arrays(fusion_pool),
1058        )
1059        .unwrap();
1060
1061        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1062        assert_eq!(quote.estimated_amount, 0);
1063    }
1064
1065    #[tokio::test]
1066    async fn decrease_long_position_providing_a_estimation() {
1067        let quote = get_decrease_spot_position_estimation(
1068            1_000_000_000,
1069            TOKEN_A,
1070            5.0,
1071            TOKEN_A,
1072            5_000_000_000, // A
1073            800_000_000,   // B
1074            0.2,
1075        )
1076        .unwrap();
1077
1078        assert_eq!(quote.decrease_percent, 200000);
1079        assert_eq!(quote.estimated_amount, 4_000_000_000);
1080        assert_eq!(quote.estimated_payable_debt, 160_000_000);
1081        assert_eq!(quote.swap_exact_in, false);
1082        assert_eq!(quote.swap_amount, 160_000_000);
1083    }
1084
1085    #[tokio::test]
1086    async fn decrease_long_position_providing_b() {
1087        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1088        let fusion_pool = test_fusion_pool(sqrt_price);
1089
1090        let quote = get_decrease_spot_position_quote(
1091            200_000_000,
1092            TOKEN_B,
1093            5.0,
1094            Some(0),
1095            TOKEN_A,
1096            5_000_000_000, // A
1097            800_000_000,   // B
1098            fusion_pool,
1099            test_tick_arrays(fusion_pool),
1100        )
1101        .unwrap();
1102
1103        assert_eq!(quote.estimated_amount, 4000000000);
1104        assert_eq!(quote.decrease_percent, 200000);
1105        assert_eq!(quote.estimated_payable_debt, 160_000_000);
1106        assert_eq!(quote.swap_exact_in, true);
1107        assert_eq!(quote.swap_input_amount, 1_000_000_000);
1108        assert_eq!(quote.swap_output_amount, 199_391_108);
1109        assert_eq!(quote.estimated_collateral_to_be_withdrawn, 39_391_108);
1110        assert_eq!(quote.price_impact, 0.003044459999999881);
1111
1112        let quote = get_decrease_spot_position_quote(
1113            1200_000_000,
1114            TOKEN_B,
1115            5.0,
1116            Some(0),
1117            TOKEN_A,
1118            5_000_000_000, // A
1119            800_000_000,   // B
1120            fusion_pool,
1121            test_tick_arrays(fusion_pool),
1122        )
1123        .unwrap();
1124
1125        assert_eq!(quote.estimated_amount, 0);
1126        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1127    }
1128
1129    #[tokio::test]
1130    async fn tradable_amount_for_1x_long_position_providing_b() {
1131        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1132        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1133        let fusion_pool = test_fusion_pool(sqrt_price);
1134        let tick_arrays = test_tick_arrays(fusion_pool);
1135
1136        let collateral_token = TOKEN_B;
1137        let position_token = TOKEN_A;
1138        let leverage = 1.0;
1139        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1140        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1141        let available_balance = 200_000_000;
1142
1143        let tradable_amount = get_tradable_amount(
1144            collateral_token,
1145            available_balance,
1146            leverage,
1147            position_token,
1148            0,
1149            protocol_fee_rate,
1150            protocol_fee_rate_on_collateral,
1151            fusion_pool.fee_rate,
1152            price,
1153            true,
1154        )
1155        .unwrap();
1156        assert_eq!(tradable_amount, 198403000);
1157
1158        let quote = get_increase_spot_position_quote(
1159            tradable_amount,
1160            collateral_token,
1161            position_token,
1162            leverage,
1163            Some(0),
1164            protocol_fee_rate,
1165            protocol_fee_rate_on_collateral,
1166            fusion_pool,
1167            tick_arrays,
1168        )
1169        .unwrap();
1170        assert_eq!(quote.collateral, available_balance);
1171    }
1172
1173    #[tokio::test]
1174    async fn tradable_amount_for_5x_long_position_providing_b() {
1175        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1176        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1177        let fusion_pool = test_fusion_pool(sqrt_price);
1178        let tick_arrays = test_tick_arrays(fusion_pool);
1179
1180        let collateral_token = TOKEN_B;
1181        let position_token = TOKEN_A;
1182        let leverage = 5.0;
1183        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1184        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1185        let available_balance = 10_000_000;
1186
1187        let tradable_amount = get_tradable_amount(
1188            collateral_token,
1189            available_balance,
1190            leverage,
1191            position_token,
1192            0,
1193            protocol_fee_rate,
1194            protocol_fee_rate_on_collateral,
1195            fusion_pool.fee_rate,
1196            price,
1197            true,
1198        )
1199        .unwrap();
1200        assert_eq!(tradable_amount, 47154380);
1201
1202        let quote = get_increase_spot_position_quote(
1203            tradable_amount,
1204            collateral_token,
1205            position_token,
1206            leverage,
1207            Some(0),
1208            protocol_fee_rate,
1209            protocol_fee_rate_on_collateral,
1210            fusion_pool,
1211            tick_arrays,
1212        )
1213        .unwrap();
1214        // TODO: fix precision error
1215        assert_eq!(quote.collateral, available_balance + 1);
1216    }
1217
1218    #[tokio::test]
1219    async fn tradable_amount_for_5x_long_position_providing_a() {
1220        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1221        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1222        let fusion_pool = test_fusion_pool(sqrt_price);
1223        let tick_arrays = test_tick_arrays(fusion_pool);
1224
1225        let collateral_token = TOKEN_A;
1226        let position_token = TOKEN_A;
1227        let leverage = 5.0;
1228        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1229        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1230        let available_balance = 1_000_000_000;
1231
1232        let tradable_amount = get_tradable_amount(
1233            collateral_token,
1234            available_balance,
1235            leverage,
1236            position_token,
1237            0,
1238            protocol_fee_rate,
1239            protocol_fee_rate_on_collateral,
1240            fusion_pool.fee_rate,
1241            price,
1242            true,
1243        )
1244        .unwrap();
1245        assert_eq!(tradable_amount, 4729626953);
1246
1247        let quote = get_increase_spot_position_quote(
1248            tradable_amount,
1249            collateral_token,
1250            position_token,
1251            leverage,
1252            Some(0),
1253            protocol_fee_rate,
1254            protocol_fee_rate_on_collateral,
1255            fusion_pool,
1256            tick_arrays,
1257        )
1258        .unwrap();
1259        assert_eq!(quote.collateral, available_balance);
1260        //assert_eq!(quote.estimated_amount, tradable_amount);
1261    }
1262
1263    #[tokio::test]
1264    async fn tradable_amount_for_5x_short_position_providing_b() {
1265        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1266        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1267        let fusion_pool = test_fusion_pool(sqrt_price);
1268        let tick_arrays = test_tick_arrays(fusion_pool);
1269
1270        let collateral_token = TOKEN_B;
1271        let position_token = TOKEN_B;
1272        let leverage = 5.0;
1273        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1274        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1275        let available_balance = 200_000_000;
1276
1277        let tradable_amount = get_tradable_amount(
1278            collateral_token,
1279            available_balance,
1280            leverage,
1281            position_token,
1282            0,
1283            protocol_fee_rate,
1284            protocol_fee_rate_on_collateral,
1285            fusion_pool.fee_rate,
1286            price,
1287            true,
1288        )
1289        .unwrap();
1290        assert_eq!(tradable_amount, 945925390);
1291
1292        let quote = get_increase_spot_position_quote(
1293            tradable_amount,
1294            collateral_token,
1295            position_token,
1296            leverage,
1297            Some(0),
1298            protocol_fee_rate,
1299            protocol_fee_rate_on_collateral,
1300            fusion_pool,
1301            tick_arrays,
1302        )
1303        .unwrap();
1304        // TODO: fix precision error
1305        assert_eq!(quote.collateral, available_balance + 1);
1306        //assert_eq!(quote.estimated_amount, tradable_amount);
1307    }
1308
1309    #[tokio::test]
1310    async fn tradable_amount_for_reducing_existing_long_position() {
1311        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1312        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1313        let fusion_pool = test_fusion_pool(sqrt_price);
1314        let tick_arrays = test_tick_arrays(fusion_pool);
1315
1316        for i in 0..2 {
1317            let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1318            let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1319            let leverage = 5.0;
1320            let position_amount = 5_000_000_000;
1321            let position_debt = 800_000_000;
1322            let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1323            let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1324            let available_balance = 50_000_000_000;
1325
1326            let tradable_amount = get_tradable_amount(
1327                collateral_token,
1328                available_balance,
1329                leverage,
1330                position_token,
1331                position_amount,
1332                protocol_fee_rate,
1333                protocol_fee_rate_on_collateral,
1334                fusion_pool.fee_rate,
1335                price,
1336                false,
1337            )
1338            .unwrap();
1339            assert_eq!(tradable_amount, 5_000_000_000);
1340
1341            let quote = get_decrease_spot_position_quote(
1342                tradable_amount,
1343                collateral_token,
1344                5.0,
1345                Some(0),
1346                position_token,
1347                position_amount,
1348                position_debt,
1349                fusion_pool,
1350                tick_arrays.clone(),
1351            )
1352            .unwrap();
1353
1354            assert_eq!(quote.estimated_amount, 0);
1355        }
1356    }
1357}