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")]
7//use 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    position_token: u8,
368    position_amount: u64,
369    position_debt: u64,
370    price: f64,
371    pool: Option<SwapPool>,
372) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
373    if collateral_token > TOKEN_B || position_token > TOKEN_B {
374        return Err(INVALID_ARGUMENTS.into());
375    }
376
377    let slippage_bps = match pool.as_ref() {
378        None => 0,
379        Some(pool) => pool.slippage_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS),
380    };
381
382    let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
383    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
384
385    let mut required_swap_amount: u64 = 0;
386
387    let mut decrease_amount_in_position_token = if collateral_token == position_token {
388        decrease_amount
389    } else {
390        round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
391    };
392
393    decrease_amount_in_position_token = position_amount.min(decrease_amount_in_position_token);
394
395    let decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
396
397    let estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
398    let estimated_payable_debt = try_mul_div(position_debt, decrease_percent as u128, HUNDRED_PERCENT as u128, true)?;
399    let mut estimated_collateral_to_be_withdrawn = 0;
400
401    //let mut next_sqrt_price = fusion_pool.sqrt_price;
402    let swap_exact_in: bool;
403    let mut swap_input_amount = 0;
404    let mut swap_output_amount = 0;
405
406    if collateral_token == position_token {
407        swap_exact_in = false;
408        if position_debt > 0 {
409            swap_output_amount = estimated_payable_debt;
410            if let Some(pool) = pool {
411                let swap =
412                    swap_quote_by_output_token(swap_output_amount, borrowed_token == TOKEN_A, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
413                swap_input_amount = swap.token_est_in;
414                //next_sqrt_price = swap.next_sqrt_price;
415                required_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_bps)?;
416                estimated_collateral_to_be_withdrawn = position_amount.saturating_sub(swap.token_est_in).saturating_sub(estimated_amount);
417            }
418        } else {
419            estimated_collateral_to_be_withdrawn = position_amount - estimated_amount;
420        }
421    } else {
422        swap_exact_in = true;
423        swap_input_amount = position_amount - estimated_amount;
424        if let Some(pool) = pool {
425            let swap = swap_quote_by_input_token(swap_input_amount, position_token == TOKEN_A, 0, pool.fusion_pool, pool.tick_arrays, None, None)?;
426            //next_sqrt_price = swap.next_sqrt_price;
427            swap_output_amount = swap.token_est_out;
428            required_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_bps)?;
429            estimated_collateral_to_be_withdrawn = swap.token_est_out.saturating_sub(estimated_payable_debt);
430        }
431    }
432
433    //let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
434    //let price_impact = (new_price / price - 1.0).abs();
435
436    let price_impact = if swap_input_amount > 0 {
437        if position_token == TOKEN_A {
438            (swap_input_amount as f64 - swap_output_amount as f64 / price) / swap_input_amount as f64
439        } else {
440            (swap_input_amount as f64 - swap_output_amount as f64 * price) / swap_input_amount as f64
441        }
442    } else {
443        0.0
444    };
445
446    Ok(DecreaseSpotPositionQuoteResult {
447        decrease_percent,
448        estimated_payable_debt,
449        estimated_collateral_to_be_withdrawn,
450        swap_exact_in,
451        swap_input_amount,
452        swap_output_amount,
453        required_swap_amount,
454        estimated_amount,
455        price_impact,
456    })
457}
458
459/// Spot position decrease quote
460///
461/// # Parameters
462/// - `decrease_amount`: Position total decrease size in the collateral_token.
463/// - `collateral_token`: Collateral token.
464/// - `slippage_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
465/// - `position_token`: Token of the existing position.
466/// - `position_amount`: Existing position amount in the position_token.
467/// - `position_debt`: Existing position debt in the token opposite to the position_token.
468/// - `fusion_pool`: Fusion pool.
469/// - `tick_arrays`: Optional five tick arrays around the current pool price.
470///
471/// # Returns
472/// - `DecreaseSpotPositionQuoteResult`: quote result
473#[cfg_attr(feature = "wasm", wasm_expose)]
474pub fn get_decrease_spot_position_quote(
475    decrease_amount: u64,
476    collateral_token: u8,
477    slippage_bps: Option<u16>,
478    position_token: u8,
479    position_amount: u64,
480    position_debt: u64,
481    fusion_pool: FusionPoolFacade,
482    tick_arrays: TickArrays,
483) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
484    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
485    get_decrease_spot_position_quote_internal(
486        decrease_amount,
487        collateral_token,
488        position_token,
489        position_amount,
490        position_debt,
491        price,
492        Some(SwapPool {
493            fusion_pool,
494            tick_arrays,
495            slippage_bps,
496        }),
497    )
498}
499
500/// Spot position decrease estimation
501///
502/// # Parameters
503/// - `decrease_amount`: Position total decrease size in the collateral_token.
504/// - `collateral_token`: Collateral token.
505/// - `position_token`: Token of the existing position.
506/// - `position_amount`: Existing position amount in the position_token.
507/// - `position_debt`: Existing position debt in the token opposite to the position_token.
508/// - `price`: Pool price.
509///
510/// # Returns
511/// - `DecreaseSpotPositionQuoteResult`: quote result
512#[cfg_attr(feature = "wasm", wasm_expose)]
513pub fn get_decrease_spot_position_estimation(
514    decrease_amount: u64,
515    collateral_token: u8,
516    position_token: u8,
517    position_amount: u64,
518    position_debt: u64,
519    price: f64,
520) -> Result<DecreaseSpotPositionEstimationResult, CoreError> {
521    let quote =
522        get_decrease_spot_position_quote_internal(decrease_amount, collateral_token, position_token, position_amount, position_debt, price, None)?;
523
524    Ok(DecreaseSpotPositionEstimationResult {
525        decrease_percent: quote.decrease_percent,
526        swap_exact_in: quote.swap_exact_in,
527        swap_amount: if quote.swap_exact_in {
528            quote.swap_input_amount
529        } else {
530            quote.swap_output_amount
531        },
532        estimated_amount: quote.estimated_amount,
533        estimated_payable_debt: quote.estimated_payable_debt,
534    })
535}
536
537/*
538/// Spot position decrease quote
539///
540/// # Parameters
541/// - `decrease_amount`: Position total decrease size in the collateral_token.
542/// - `collateral_token`: Collateral token.
543/// - `leverage`: Leverage (1.0 or higher).
544/// - `slippage_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
545/// - `position_token`: Token of the existing position.
546/// - `position_amount`: Existing position amount in the position_token.
547/// - `position_debt`: Existing position debt in the token opposite to the position_token.
548/// - `fusion_pool`: Fusion pool.
549/// - `tick_arrays`: Optional five tick arrays around the current pool price.
550///
551/// # Returns
552/// - `DecreaseSpotPositionQuoteResult`: quote result
553#[cfg(feature = "wasm")]
554#[wasm_bindgen(js_name = "getDecreaseSpotPositionQuote", skip_jsdoc)]
555pub async fn wasm_get_decrease_spot_position_quote(
556    decrease_amount: u64,
557    collateral_token: u8,
558    leverage: f64,
559    slippage_bps: Option<u16>,
560    position_token: u8,
561    position_amount: u64,
562    position_debt: u64,
563    mint_a: Pubkey,
564    mint_b: Pubkey,
565    fusion_pool: FusionPoolFacade,
566    tick_arrays: Option<TickArrays>,
567) -> Result<JsValue, JsValue> {
568    let result = get_decrease_spot_position_quote(
569        decrease_amount,
570        collateral_token,
571        leverage,
572        slippage_bps,
573        position_token,
574        position_amount,
575        position_debt,
576        mint_a,
577        mint_b,
578        fusion_pool,
579        tick_arrays,
580    )
581    .await
582    .map_err(|e| JsValue::from_str(e))?;
583
584    let serializer = Serializer::new().serialize_maps_as_objects(true);
585    let js_value = result.serialize(&serializer).unwrap();
586
587    Ok(js_value)
588}
589*/
590
591/// Returns the liquidation price
592///
593/// # Parameters
594/// - `position_token`: Token of the position
595/// - `amount`: Position total size
596/// - `debt`: Position total debt
597/// - `liquidation_threshold`: Liquidation threshold of a market
598///
599/// # Returns
600/// - `f64`: Decimal liquidation price
601#[cfg_attr(feature = "wasm", wasm_expose)]
602pub fn get_spot_position_liquidation_price(position_token: u8, amount: u64, debt: u64, liquidation_threshold: u32) -> Result<f64, CoreError> {
603    if liquidation_threshold >= HUNDRED_PERCENT {
604        return Err(INVALID_ARGUMENTS);
605    }
606
607    if debt == 0 || amount == 0 {
608        return Ok(0.0);
609    }
610
611    let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
612
613    if position_token == TOKEN_A {
614        Ok(debt as f64 / (amount as f64 * liquidation_threshold_f))
615    } else {
616        Ok((amount as f64 * liquidation_threshold_f) / debt as f64)
617    }
618}
619
620/// Calculates the maximum tradable amount in the collateral token.
621///
622/// # Parameters
623/// - `collateral_token`: Collateral token.
624/// - `available_balance`: Available wallet balance in the collateral_token.
625/// - `leverage`: Leverage (1.0 or higher).
626/// - `position_token`: Token of the existing position. Should be set to new_position_token if position_amount is zero.
627/// - `position_amount`: Existing position amount in the position_token.
628/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
629/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
630/// - 'swap_fee_rate': Pool swap fee rate.
631/// - `price`: Pool price.
632/// - `increase`: true if increasing the position
633///
634/// # Returns
635/// - `u64`: the maximum tradable amount
636#[cfg_attr(feature = "wasm", wasm_expose)]
637pub fn get_tradable_amount(
638    collateral_token: u8,
639    available_balance: u64,
640    leverage: f64,
641    position_token: u8,
642    position_amount: u64,
643    protocol_fee_rate: u16,
644    protocol_fee_rate_on_collateral: u16,
645    swap_fee_rate: u16,
646    price: f64,
647    increase: bool,
648) -> Result<u64, CoreError> {
649    if collateral_token > TOKEN_B || position_token > TOKEN_B {
650        return Err(INVALID_ARGUMENTS.into());
651    }
652
653    if leverage < 1.0 {
654        return Err(INVALID_ARGUMENTS.into());
655    }
656
657    // T = Câ‹…Fcâ‹…Fs + Bâ‹…Fbâ‹…Fs, where: Fc/Fb/Fs - collateral/borrow/swap fee multiplier
658    // B = Tâ‹…(L - 1) / L
659    // => T = Câ‹…Fcâ‹…Fs / (1 - Fbâ‹…Fsâ‹…(L - 1) / L)
660    let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
661        let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
662        if collateral_token != position_token {
663            collateral = apply_swap_fee(collateral, swap_fee_rate, false)?;
664        }
665
666        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);
667        let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
668        Ok(total)
669    };
670
671    let available_to_trade = if increase {
672        add_leverage(available_balance)?
673    } else {
674        let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
675
676        if collateral_token == position_token {
677            position_amount
678        } else {
679            round(position_amount as f64 * position_to_opposite_token_price) as u64
680        }
681    };
682
683    Ok(available_to_trade)
684}
685
686/*
687struct JupiterSwapResult {
688    pub instruction: JupiterSwapInstruction,
689    pub out_amount: u64,
690    pub other_amount_threshold: u64,
691    pub price_impact_pct: f64,
692}
693
694async fn jupiter_swap_quote(
695    tuna_position_address: Pubkey,
696    input_mint: Pubkey,
697    output_mint: Pubkey,
698    amount: u64,
699    slippage_bps: Option<u64>,
700) -> Result<JupiterSwapResult, CoreError> {
701    let quote_config = QuoteConfig {
702        slippage_bps,
703        swap_mode: Some(SwapMode::ExactIn),
704        dexes: None,
705        exclude_dexes: Some(vec!["Pump.fun Amm".to_string(), "Pump.fun".to_string()]),
706        only_direct_routes: false,
707        as_legacy_transaction: None,
708        platform_fee_bps: None,
709        max_accounts: Some(45),
710    };
711
712    let quote = jup_ag::quote(input_mint, output_mint, amount, quote_config)
713        .await
714        .map_err(|_| JUPITER_QUOTE_REQUEST_ERROR)?;
715
716    #[allow(deprecated)]
717    let swap_request = SwapRequest {
718        user_public_key: tuna_position_address,
719        wrap_and_unwrap_sol: None,
720        use_shared_accounts: Some(true),
721        fee_account: None,
722        compute_unit_price_micro_lamports: None,
723        prioritization_fee_lamports: PrioritizationFeeLamports::Auto,
724        as_legacy_transaction: None,
725        use_token_ledger: None,
726        destination_token_account: None,
727        quote_response: quote.clone(),
728    };
729
730    let swap_response = jup_ag::swap_instructions(swap_request)
731        .await
732        .map_err(|_| JUPITER_SWAP_INSTRUCTIONS_REQUEST_ERROR)?;
733
734    Ok(JupiterSwapResult {
735        instruction: JupiterSwapInstruction {
736            data: swap_response.swap_instruction.data,
737            accounts: swap_response.swap_instruction.accounts,
738            address_lookup_table_addresses: swap_response.address_lookup_table_addresses,
739        },
740        out_amount: quote.out_amount,
741        other_amount_threshold: quote.other_amount_threshold,
742        price_impact_pct: quote.price_impact_pct,
743    })
744}
745*/
746
747#[cfg_attr(feature = "wasm", wasm_expose)]
748pub fn calculate_tuna_spot_position_protocol_fee(
749    collateral_token: u8,
750    borrowed_token: u8,
751    collateral: u64,
752    borrow: u64,
753    protocol_fee_rate_on_collateral: u16,
754    protocol_fee_rate: u16,
755) -> TokenPair {
756    let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
757    let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
758    let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
759    let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
760
761    let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, protocol_fee_rate_on_collateral, protocol_fee_rate);
762    let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, protocol_fee_rate_on_collateral, protocol_fee_rate);
763
764    TokenPair {
765        a: protocol_fee_a,
766        b: protocol_fee_b,
767    }
768}
769
770#[cfg(all(test, not(feature = "wasm")))]
771mod tests {
772    use super::*;
773    use crate::assert_approx_eq;
774    use fusionamm_core::{
775        get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
776    };
777
778    fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
779        let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
780        FusionPoolFacade {
781            tick_current_index,
782            fee_rate: 3000,
783            liquidity: 10000000000000,
784            sqrt_price,
785            tick_spacing: 2,
786            ..FusionPoolFacade::default()
787        }
788    }
789
790    fn test_tick(liquidity_net: i128) -> TickFacade {
791        TickFacade {
792            initialized: true,
793            liquidity_net,
794            ..TickFacade::default()
795        }
796    }
797
798    fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
799        TickArrayFacade {
800            start_tick_index,
801            ticks: [test_tick(0); TICK_ARRAY_SIZE],
802        }
803    }
804
805    fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
806        let tick_spacing = fusion_pool.tick_spacing;
807        let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
808        let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
809
810        [
811            test_tick_array(tick_array_start_index),
812            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
813            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
814            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
815            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
816        ]
817        .into()
818    }
819
820    #[test]
821    fn test_get_liquidation_price() {
822        assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 0, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
823        assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 0, 5, HUNDRED_PERCENT * 85 / 100), Ok(0.0));
824        assert_eq!(get_spot_position_liquidation_price(TOKEN_A, 5, 800, HUNDRED_PERCENT * 85 / 100), Ok(188.23529411764707));
825        assert_eq!(get_spot_position_liquidation_price(TOKEN_B, 1000, 4, HUNDRED_PERCENT * 85 / 100), Ok(212.5));
826    }
827
828    #[tokio::test]
829    async fn increase_long_position_providing_token_a() {
830        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
831        let fusion_pool = test_fusion_pool(sqrt_price);
832
833        let quote = get_increase_spot_position_quote(
834            5_000_000_000,
835            TOKEN_A,
836            TOKEN_A,
837            5.0,
838            Some(0),
839            (HUNDRED_PERCENT / 100) as u16,
840            (HUNDRED_PERCENT / 200) as u16,
841            fusion_pool,
842            test_tick_arrays(fusion_pool),
843        )
844        .unwrap();
845
846        assert_eq!(quote.collateral, 1057165829);
847        assert_eq!(quote.borrow, 800000000);
848        assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
849        assert_eq!(quote.estimated_amount, 4_999_303_011);
850        assert_eq!(quote.protocol_fee_a, 5285829);
851        assert_eq!(quote.protocol_fee_b, 8000000);
852        assert_eq!(quote.price_impact, 0.0031760073232324137);
853        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);
854    }
855
856    #[tokio::test]
857    async fn increase_long_position_providing_token_b() {
858        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
859        let fusion_pool = test_fusion_pool(sqrt_price);
860
861        let quote = get_increase_spot_position_quote(
862            5000_000_000,
863            TOKEN_B,
864            TOKEN_A,
865            5.0,
866            Some(0),
867            (HUNDRED_PERCENT / 100) as u16,
868            (HUNDRED_PERCENT / 200) as u16,
869            fusion_pool,
870            test_tick_arrays(fusion_pool),
871        )
872        .unwrap();
873
874        assert_eq!(quote.collateral, 1060346869);
875        assert_eq!(quote.borrow, 4000000000);
876        assert_eq!(quote.estimated_amount, 24_972_080_293);
877        assert_eq!(quote.protocol_fee_a, 0);
878        assert_eq!(quote.protocol_fee_b, 45301734);
879        assert_eq!(quote.price_impact, 0.004113437834493303);
880        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);
881    }
882
883    #[tokio::test]
884    async fn increase_short_position_providing_a() {
885        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
886        let fusion_pool = test_fusion_pool(sqrt_price);
887
888        let quote = get_increase_spot_position_quote(
889            5_000_000_000,
890            TOKEN_A,
891            TOKEN_B,
892            5.0,
893            Some(0),
894            (HUNDRED_PERCENT / 100) as u16,
895            (HUNDRED_PERCENT / 200) as u16,
896            fusion_pool,
897            test_tick_arrays(fusion_pool),
898        )
899        .unwrap();
900
901        assert_eq!(quote.collateral, 1060346869);
902        assert_eq!(quote.borrow, 4000000000);
903        assert_eq!(quote.estimated_amount, 999_776_441);
904        assert_eq!(quote.protocol_fee_a, 45301734);
905        assert_eq!(quote.protocol_fee_b, 0);
906        assert_eq!(quote.price_impact, 0.003222888242261054);
907        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);
908    }
909
910    #[tokio::test]
911    async fn increase_short_position_providing_b() {
912        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
913        let fusion_pool = test_fusion_pool(sqrt_price);
914
915        let quote = get_increase_spot_position_quote(
916            5000_000_000,
917            TOKEN_B,
918            TOKEN_B,
919            5.0,
920            Some(0),
921            (HUNDRED_PERCENT / 100) as u16,
922            (HUNDRED_PERCENT / 200) as u16,
923            fusion_pool,
924            test_tick_arrays(fusion_pool),
925        )
926        .unwrap();
927
928        assert_eq!(quote.collateral, 1057165829);
929        assert_eq!(quote.borrow, 20000000000);
930        assert_eq!(quote.estimated_amount, 4996_517_564);
931        assert_eq!(quote.protocol_fee_a, 200000000);
932        assert_eq!(quote.protocol_fee_b, 5285829);
933        assert_eq!(quote.price_impact, 0.0038794030303030305);
934        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);
935    }
936
937    #[tokio::test]
938    async fn increase_quote_with_slippage() {
939        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
940        let fusion_pool = test_fusion_pool(sqrt_price);
941
942        // with slippage 10%
943        let quote =
944            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();
945        assert_eq!(quote.min_swap_output_amount, 899_994);
946
947        // without slippage
948        let quote =
949            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();
950        assert_eq!(quote.min_swap_output_amount, 999_994);
951    }
952
953    #[tokio::test]
954    async fn decrease_non_leveraged_long_position_providing_a() {
955        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
956        let fusion_pool = test_fusion_pool(sqrt_price);
957
958        let quote = get_decrease_spot_position_quote(
959            1_000_000_000,
960            TOKEN_A,
961            Some(0),
962            TOKEN_A,
963            5_000_000_000, // A
964            0,             // B
965            fusion_pool,
966            test_tick_arrays(fusion_pool),
967        )
968        .unwrap();
969
970        assert_eq!(quote.decrease_percent, 200000);
971        assert_eq!(quote.estimated_amount, 4_000_000_000);
972        assert_eq!(quote.estimated_payable_debt, 0);
973        assert_eq!(quote.estimated_collateral_to_be_withdrawn, 1_000_000_000);
974        assert_eq!(quote.price_impact, 0.0);
975    }
976
977    #[tokio::test]
978    async fn decrease_non_leveraged_long_position_providing_b() {
979        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
980        let fusion_pool = test_fusion_pool(sqrt_price);
981
982        let quote = get_decrease_spot_position_quote(
983            200_000_000,
984            TOKEN_B,
985            Some(0),
986            TOKEN_A,
987            5_000_000_000, // A
988            0,             // B
989            fusion_pool,
990            test_tick_arrays(fusion_pool),
991        )
992        .unwrap();
993
994        assert_eq!(quote.decrease_percent, 200000);
995        assert_eq!(quote.estimated_amount, 4_000_000_000);
996        assert_eq!(quote.estimated_payable_debt, 0);
997        assert_eq!(quote.estimated_collateral_to_be_withdrawn, 199_391_108);
998        assert_eq!(quote.price_impact, 0.003044459999999881);
999    }
1000
1001    #[tokio::test]
1002    async fn decrease_long_position_providing_a() {
1003        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1004        let fusion_pool = test_fusion_pool(sqrt_price);
1005
1006        let quote = get_decrease_spot_position_quote(
1007            1_000_000_000,
1008            TOKEN_A,
1009            Some(0),
1010            TOKEN_A,
1011            5_000_000_000, // A
1012            800_000_000,   // B
1013            fusion_pool,
1014            test_tick_arrays(fusion_pool),
1015        )
1016        .unwrap();
1017
1018        assert_eq!(quote.decrease_percent, 200000);
1019        assert_eq!(quote.estimated_amount, 4_000_000_000);
1020        assert_eq!(quote.estimated_payable_debt, 160_000_000);
1021        assert_eq!(quote.swap_exact_in, false);
1022        assert_eq!(quote.swap_input_amount, 802_435_931);
1023        assert_eq!(quote.swap_output_amount, 160_000_000);
1024        assert_eq!(quote.estimated_collateral_to_be_withdrawn, 197_564_069);
1025        assert_eq!(quote.price_impact, 0.0030356703954722095);
1026
1027        let quote = get_decrease_spot_position_quote(
1028            6_000_000_000,
1029            TOKEN_A,
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, HUNDRED_PERCENT);
1040        assert_eq!(quote.estimated_amount, 0);
1041    }
1042
1043    #[tokio::test]
1044    async fn decrease_long_position_providing_a_estimation() {
1045        let quote = get_decrease_spot_position_estimation(
1046            1_000_000_000,
1047            TOKEN_A,
1048            TOKEN_A,
1049            5_000_000_000, // A
1050            800_000_000,   // B
1051            0.2,
1052        )
1053        .unwrap();
1054
1055        assert_eq!(quote.decrease_percent, 200000);
1056        assert_eq!(quote.estimated_amount, 4_000_000_000);
1057        assert_eq!(quote.estimated_payable_debt, 160_000_000);
1058        assert_eq!(quote.swap_exact_in, false);
1059        assert_eq!(quote.swap_amount, 160_000_000);
1060    }
1061
1062    #[tokio::test]
1063    async fn decrease_long_position_providing_b() {
1064        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1065        let fusion_pool = test_fusion_pool(sqrt_price);
1066
1067        let quote = get_decrease_spot_position_quote(
1068            200_000_000,
1069            TOKEN_B,
1070            Some(0),
1071            TOKEN_A,
1072            5_000_000_000, // A
1073            800_000_000,   // B
1074            fusion_pool,
1075            test_tick_arrays(fusion_pool),
1076        )
1077        .unwrap();
1078
1079        assert_eq!(quote.estimated_amount, 4000000000);
1080        assert_eq!(quote.decrease_percent, 200000);
1081        assert_eq!(quote.estimated_payable_debt, 160_000_000);
1082        assert_eq!(quote.swap_exact_in, true);
1083        assert_eq!(quote.swap_input_amount, 1_000_000_000);
1084        assert_eq!(quote.swap_output_amount, 199_391_108);
1085        assert_eq!(quote.estimated_collateral_to_be_withdrawn, 39_391_108);
1086        assert_eq!(quote.price_impact, 0.003044459999999881);
1087
1088        let quote = get_decrease_spot_position_quote(
1089            1200_000_000,
1090            TOKEN_B,
1091            Some(0),
1092            TOKEN_A,
1093            5_000_000_000, // A
1094            800_000_000,   // B
1095            fusion_pool,
1096            test_tick_arrays(fusion_pool),
1097        )
1098        .unwrap();
1099
1100        assert_eq!(quote.estimated_amount, 0);
1101        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1102    }
1103
1104    #[tokio::test]
1105    async fn tradable_amount_for_1x_long_position_providing_b() {
1106        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1107        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1108        let fusion_pool = test_fusion_pool(sqrt_price);
1109        let tick_arrays = test_tick_arrays(fusion_pool);
1110
1111        let collateral_token = TOKEN_B;
1112        let position_token = TOKEN_A;
1113        let leverage = 1.0;
1114        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1115        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1116        let available_balance = 200_000_000;
1117
1118        let tradable_amount = get_tradable_amount(
1119            collateral_token,
1120            available_balance,
1121            leverage,
1122            position_token,
1123            0,
1124            protocol_fee_rate,
1125            protocol_fee_rate_on_collateral,
1126            fusion_pool.fee_rate,
1127            price,
1128            true,
1129        )
1130        .unwrap();
1131        assert_eq!(tradable_amount, 198403000);
1132
1133        let quote = get_increase_spot_position_quote(
1134            tradable_amount,
1135            collateral_token,
1136            position_token,
1137            leverage,
1138            Some(0),
1139            protocol_fee_rate,
1140            protocol_fee_rate_on_collateral,
1141            fusion_pool,
1142            tick_arrays,
1143        )
1144        .unwrap();
1145        assert_eq!(quote.collateral, available_balance);
1146    }
1147
1148    #[tokio::test]
1149    async fn tradable_amount_for_5x_long_position_providing_b() {
1150        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1151        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1152        let fusion_pool = test_fusion_pool(sqrt_price);
1153        let tick_arrays = test_tick_arrays(fusion_pool);
1154
1155        let collateral_token = TOKEN_B;
1156        let position_token = TOKEN_A;
1157        let leverage = 5.0;
1158        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1159        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1160        let available_balance = 10_000_000;
1161
1162        let tradable_amount = get_tradable_amount(
1163            collateral_token,
1164            available_balance,
1165            leverage,
1166            position_token,
1167            0,
1168            protocol_fee_rate,
1169            protocol_fee_rate_on_collateral,
1170            fusion_pool.fee_rate,
1171            price,
1172            true,
1173        )
1174        .unwrap();
1175        assert_eq!(tradable_amount, 47154380);
1176
1177        let quote = get_increase_spot_position_quote(
1178            tradable_amount,
1179            collateral_token,
1180            position_token,
1181            leverage,
1182            Some(0),
1183            protocol_fee_rate,
1184            protocol_fee_rate_on_collateral,
1185            fusion_pool,
1186            tick_arrays,
1187        )
1188        .unwrap();
1189        // TODO: fix precision error
1190        assert_eq!(quote.collateral, available_balance + 1);
1191    }
1192
1193    #[tokio::test]
1194    async fn tradable_amount_for_5x_long_position_providing_a() {
1195        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1196        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1197        let fusion_pool = test_fusion_pool(sqrt_price);
1198        let tick_arrays = test_tick_arrays(fusion_pool);
1199
1200        let collateral_token = TOKEN_A;
1201        let position_token = TOKEN_A;
1202        let leverage = 5.0;
1203        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1204        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1205        let available_balance = 1_000_000_000;
1206
1207        let tradable_amount = get_tradable_amount(
1208            collateral_token,
1209            available_balance,
1210            leverage,
1211            position_token,
1212            0,
1213            protocol_fee_rate,
1214            protocol_fee_rate_on_collateral,
1215            fusion_pool.fee_rate,
1216            price,
1217            true,
1218        )
1219        .unwrap();
1220        assert_eq!(tradable_amount, 4729626953);
1221
1222        let quote = get_increase_spot_position_quote(
1223            tradable_amount,
1224            collateral_token,
1225            position_token,
1226            leverage,
1227            Some(0),
1228            protocol_fee_rate,
1229            protocol_fee_rate_on_collateral,
1230            fusion_pool,
1231            tick_arrays,
1232        )
1233        .unwrap();
1234        assert_eq!(quote.collateral, available_balance);
1235        //assert_eq!(quote.estimated_amount, tradable_amount);
1236    }
1237
1238    #[tokio::test]
1239    async fn tradable_amount_for_5x_short_position_providing_b() {
1240        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1241        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1242        let fusion_pool = test_fusion_pool(sqrt_price);
1243        let tick_arrays = test_tick_arrays(fusion_pool);
1244
1245        let collateral_token = TOKEN_B;
1246        let position_token = TOKEN_B;
1247        let leverage = 5.0;
1248        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1249        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1250        let available_balance = 200_000_000;
1251
1252        let tradable_amount = get_tradable_amount(
1253            collateral_token,
1254            available_balance,
1255            leverage,
1256            position_token,
1257            0,
1258            protocol_fee_rate,
1259            protocol_fee_rate_on_collateral,
1260            fusion_pool.fee_rate,
1261            price,
1262            true,
1263        )
1264        .unwrap();
1265        assert_eq!(tradable_amount, 945925390);
1266
1267        let quote = get_increase_spot_position_quote(
1268            tradable_amount,
1269            collateral_token,
1270            position_token,
1271            leverage,
1272            Some(0),
1273            protocol_fee_rate,
1274            protocol_fee_rate_on_collateral,
1275            fusion_pool,
1276            tick_arrays,
1277        )
1278        .unwrap();
1279        // TODO: fix precision error
1280        assert_eq!(quote.collateral, available_balance + 1);
1281        //assert_eq!(quote.estimated_amount, tradable_amount);
1282    }
1283
1284    #[tokio::test]
1285    async fn tradable_amount_for_reducing_existing_long_position() {
1286        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1287        let price = sqrt_price_to_price(sqrt_price, 1, 1);
1288        let fusion_pool = test_fusion_pool(sqrt_price);
1289        let tick_arrays = test_tick_arrays(fusion_pool);
1290
1291        for i in 0..2 {
1292            let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1293            let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1294            let leverage = 5.0;
1295            let position_amount = 5_000_000_000;
1296            let position_debt = 800_000_000;
1297            let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1298            let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1299            let available_balance = 50_000_000_000;
1300
1301            let tradable_amount = get_tradable_amount(
1302                collateral_token,
1303                available_balance,
1304                leverage,
1305                position_token,
1306                position_amount,
1307                protocol_fee_rate,
1308                protocol_fee_rate_on_collateral,
1309                fusion_pool.fee_rate,
1310                price,
1311                false,
1312            )
1313            .unwrap();
1314            assert_eq!(tradable_amount, 5_000_000_000);
1315
1316            let quote = get_decrease_spot_position_quote(
1317                tradable_amount,
1318                collateral_token,
1319                Some(0),
1320                position_token,
1321                position_amount,
1322                position_debt,
1323                fusion_pool,
1324                tick_arrays.clone(),
1325            )
1326            .unwrap();
1327
1328            assert_eq!(quote.estimated_amount, 0);
1329        }
1330    }
1331}