defituna_core/quote/
tuna_spot_position.rs

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