use crate::{ sqrt_price_to_tick_index, tick_index_to_sqrt_price, try_apply_swap_fee, try_apply_transfer_fee, try_get_amount_delta_a, try_get_amount_delta_b, try_get_max_amount_with_slippage_tolerance, try_get_min_amount_with_slippage_tolerance, try_get_next_sqrt_price_from_a, try_get_next_sqrt_price_from_b, try_reverse_apply_swap_fee, try_reverse_apply_transfer_fee, AdaptiveFeeInfo, CoreError, ExactInSwapQuote, ExactOutSwapQuote, FeeRateManager, OracleFacade, TickArraySequence, TickArrays, TickFacade, TransferFee, WhirlpoolFacade, AMOUNT_EXCEEDS_MAX_U64, ARITHMETIC_OVERFLOW, INVALID_ADAPTIVE_FEE_INFO, INVALID_SQRT_PRICE_LIMIT_DIRECTION, MAX_SQRT_PRICE, MIN_SQRT_PRICE, SQRT_PRICE_LIMIT_OUT_OF_BOUNDS, ZERO_TRADABLE_AMOUNT, }; #[cfg(feature = "wasm")] use orca_whirlpools_macros::wasm_expose; /// Computes the exact input or output amount for a swap transaction. /// /// # Arguments /// - `token_in`: The input token amount. /// - `specified_token_a`: If `true`, the input token is token A. Otherwise, it is token B. /// - `slippage_tolerance`: The slippage tolerance in basis points. /// - `whirlpool`: The whirlpool state. /// - `oracle`: The oracle data for the whirlpool. /// - `tick_arrays`: The tick arrays needed for the swap. /// - `timestamp`: The timestamp for the swap. /// - `transfer_fee_a`: The transfer fee for token A. /// - `transfer_fee_b`: The transfer fee for token B. /// /// # Returns /// The exact input or output amount for the swap transaction. #[allow(clippy::too_many_arguments)] #[cfg_attr(feature = "wasm", wasm_expose)] pub fn swap_quote_by_input_token( token_in: u64, specified_token_a: bool, slippage_tolerance_bps: u16, whirlpool: WhirlpoolFacade, oracle: Option, tick_arrays: TickArrays, timestamp: u64, transfer_fee_a: Option, transfer_fee_b: Option, ) -> Result { let (transfer_fee_in, transfer_fee_out) = if specified_token_a { (transfer_fee_a, transfer_fee_b) } else { (transfer_fee_b, transfer_fee_a) }; let token_in_after_fee = try_apply_transfer_fee(token_in.into(), transfer_fee_in.unwrap_or_default())?; let tick_sequence = TickArraySequence::new(tick_arrays.into(), whirlpool.tick_spacing)?; let swap_result = compute_swap( token_in_after_fee.into(), 0, whirlpool, tick_sequence, specified_token_a, true, timestamp, oracle.map(|oracle| oracle.into()), )?; let (token_in_after_fees, token_est_out_before_fee) = if specified_token_a { (swap_result.token_a, swap_result.token_b) } else { (swap_result.token_b, swap_result.token_a) }; let token_in = try_reverse_apply_transfer_fee(token_in_after_fees, transfer_fee_in.unwrap_or_default())?; let token_est_out = try_apply_transfer_fee( token_est_out_before_fee, transfer_fee_out.unwrap_or_default(), )?; let token_min_out = try_get_min_amount_with_slippage_tolerance(token_est_out, slippage_tolerance_bps)?; Ok(ExactInSwapQuote { token_in, token_est_out, token_min_out, trade_fee: swap_result.trade_fee, trade_fee_rate_min: swap_result.applied_fee_rate_min, trade_fee_rate_max: swap_result.applied_fee_rate_max, }) } /// Computes the exact input or output amount for a swap transaction. /// /// # Arguments /// - `token_out`: The output token amount. /// - `specified_token_a`: If `true`, the output token is token A. Otherwise, it is token B. /// - `slippage_tolerance`: The slippage tolerance in basis points. /// - `whirlpool`: The whirlpool state. /// - `oracle`: The oracle data for the whirlpool. /// - `tick_arrays`: The tick arrays needed for the swap. /// - `timestamp`: The timestamp for the swap. /// - `transfer_fee_a`: The transfer fee for token A. /// - `transfer_fee_b`: The transfer fee for token B. /// /// # Returns /// The exact input or output amount for the swap transaction. #[allow(clippy::too_many_arguments)] #[cfg_attr(feature = "wasm", wasm_expose)] pub fn swap_quote_by_output_token( token_out: u64, specified_token_a: bool, slippage_tolerance_bps: u16, whirlpool: WhirlpoolFacade, oracle: Option, tick_arrays: TickArrays, timestamp: u64, transfer_fee_a: Option, transfer_fee_b: Option, ) -> Result { let (transfer_fee_in, transfer_fee_out) = if specified_token_a { (transfer_fee_b, transfer_fee_a) } else { (transfer_fee_a, transfer_fee_b) }; let token_out_before_fee = try_reverse_apply_transfer_fee(token_out, transfer_fee_out.unwrap_or_default())?; let tick_sequence = TickArraySequence::new(tick_arrays.into(), whirlpool.tick_spacing)?; let swap_result = compute_swap( token_out_before_fee.into(), 0, whirlpool, tick_sequence, !specified_token_a, false, timestamp, oracle.map(|oracle| oracle.into()), )?; let (token_out_before_fee, token_est_in_after_fee) = if specified_token_a { (swap_result.token_a, swap_result.token_b) } else { (swap_result.token_b, swap_result.token_a) }; let token_out = try_apply_transfer_fee(token_out_before_fee, transfer_fee_out.unwrap_or_default())?; let token_est_in = try_reverse_apply_transfer_fee( token_est_in_after_fee, transfer_fee_in.unwrap_or_default(), )?; let token_max_in = try_get_max_amount_with_slippage_tolerance(token_est_in, slippage_tolerance_bps)?; Ok(ExactOutSwapQuote { token_out, token_est_in, token_max_in, trade_fee: swap_result.trade_fee, trade_fee_rate_min: swap_result.applied_fee_rate_min, trade_fee_rate_max: swap_result.applied_fee_rate_max, }) } pub struct SwapResult { pub token_a: u64, pub token_b: u64, pub trade_fee: u64, pub applied_fee_rate_min: u32, pub applied_fee_rate_max: u32, } /// Computes the amounts of tokens A and B based on the current Whirlpool state and tick sequence. /// /// # Arguments /// - `token_amount`: The input or output amount specified for the swap. Must be non-zero. /// - `sqrt_price_limit`: The price limit for the swap represented as a square root. /// If set to `0`, it defaults to the minimum or maximum sqrt price based on the direction of the swap. /// - `whirlpool`: The current state of the Whirlpool AMM, including liquidity, price, and tick information. /// - `tick_sequence`: A sequence of ticks used to determine price levels during the swap process. /// - `a_to_b`: Indicates the direction of the swap: /// - `true`: Swap from token A to token B. /// - `false`: Swap from token B to token A. /// - `specified_input`: Determines if the input amount is specified: /// - `true`: `token_amount` represents the input amount. /// - `false`: `token_amount` represents the output amount. /// - `timestamp`: A timestamp used to calculate the adaptive fee rate. /// - `adaptive_fee_info`: An optional `AdaptiveFeeInfo` struct containing information about the adaptive fee rate. /// # Returns /// A `Result` containing a `SwapResult` struct if the swap is successful, or an `ErrorCode` if the computation fails. /// # Notes /// - This function doesn't take into account slippage tolerance. /// - This function doesn't take into account transfer fee extension. #[allow(clippy::too_many_arguments)] pub fn compute_swap( token_amount: u64, sqrt_price_limit: u128, whirlpool: WhirlpoolFacade, tick_sequence: TickArraySequence, a_to_b: bool, specified_input: bool, timestamp: u64, adaptive_fee_info: Option, ) -> Result { let sqrt_price_limit = if sqrt_price_limit == 0 { if a_to_b { MIN_SQRT_PRICE } else { MAX_SQRT_PRICE } } else { sqrt_price_limit }; if !(MIN_SQRT_PRICE..=MAX_SQRT_PRICE).contains(&sqrt_price_limit) { return Err(SQRT_PRICE_LIMIT_OUT_OF_BOUNDS); } if a_to_b && sqrt_price_limit >= whirlpool.sqrt_price || !a_to_b && sqrt_price_limit <= whirlpool.sqrt_price { return Err(INVALID_SQRT_PRICE_LIMIT_DIRECTION); } if token_amount == 0 { return Err(ZERO_TRADABLE_AMOUNT); } let mut amount_remaining = token_amount; let mut amount_calculated = 0u64; let mut current_sqrt_price = whirlpool.sqrt_price; let mut current_tick_index = whirlpool.tick_current_index; let mut current_liquidity = whirlpool.liquidity; let mut trade_fee = 0u64; let base_fee_rate = whirlpool.fee_rate; let mut applied_fee_rate_min: Option = None; let mut applied_fee_rate_max: Option = None; if whirlpool.is_initialized_with_adaptive_fee() != adaptive_fee_info.is_some() { return Err(INVALID_ADAPTIVE_FEE_INFO); } let mut fee_rate_manager = FeeRateManager::new( a_to_b, whirlpool.tick_current_index, // note: -1 shift is acceptable timestamp, base_fee_rate, &adaptive_fee_info, )?; while amount_remaining > 0 && sqrt_price_limit != current_sqrt_price { let (next_tick, next_tick_index) = if a_to_b { tick_sequence.prev_initialized_tick(current_tick_index)? } else { tick_sequence.next_initialized_tick(current_tick_index)? }; let next_tick_sqrt_price: u128 = tick_index_to_sqrt_price(next_tick_index.into()).into(); let target_sqrt_price = if a_to_b { next_tick_sqrt_price.max(sqrt_price_limit) } else { next_tick_sqrt_price.min(sqrt_price_limit) }; loop { fee_rate_manager.update_volatility_accumulator(); let total_fee_rate = fee_rate_manager.get_total_fee_rate(); applied_fee_rate_min = Some( applied_fee_rate_min .unwrap_or(total_fee_rate) .min(total_fee_rate), ); applied_fee_rate_max = Some( applied_fee_rate_max .unwrap_or(total_fee_rate) .max(total_fee_rate), ); let (bounded_sqrt_price_target, adaptive_fee_update_skipped) = fee_rate_manager .get_bounded_sqrt_price_target(target_sqrt_price, current_liquidity); let step_quote = compute_swap_step( amount_remaining, total_fee_rate, current_liquidity, current_sqrt_price, bounded_sqrt_price_target, a_to_b, specified_input, )?; trade_fee += step_quote.fee_amount; if specified_input { amount_remaining = amount_remaining .checked_sub(step_quote.amount_in) .ok_or(ARITHMETIC_OVERFLOW)? .checked_sub(step_quote.fee_amount) .ok_or(ARITHMETIC_OVERFLOW)?; amount_calculated = amount_calculated .checked_add(step_quote.amount_out) .ok_or(ARITHMETIC_OVERFLOW)?; } else { amount_remaining = amount_remaining .checked_sub(step_quote.amount_out) .ok_or(ARITHMETIC_OVERFLOW)?; amount_calculated = amount_calculated .checked_add(step_quote.amount_in) .ok_or(ARITHMETIC_OVERFLOW)? .checked_add(step_quote.fee_amount) .ok_or(ARITHMETIC_OVERFLOW)?; } if step_quote.next_sqrt_price == next_tick_sqrt_price { current_liquidity = get_next_liquidity(current_liquidity, next_tick, a_to_b); current_tick_index = if a_to_b { next_tick_index - 1 } else { next_tick_index } } else if step_quote.next_sqrt_price != current_sqrt_price { current_tick_index = sqrt_price_to_tick_index(step_quote.next_sqrt_price.into()).into(); } current_sqrt_price = step_quote.next_sqrt_price; if !adaptive_fee_update_skipped { fee_rate_manager.advance_tick_group(); } else { fee_rate_manager.advance_tick_group_after_skip( current_sqrt_price, next_tick_sqrt_price, next_tick_index, ); } // do while loop if amount_remaining == 0 || current_sqrt_price == target_sqrt_price { break; } } } let swapped_amount = token_amount - amount_remaining; let token_a = if a_to_b == specified_input { swapped_amount } else { amount_calculated }; let token_b = if a_to_b == specified_input { amount_calculated } else { swapped_amount }; fee_rate_manager.update_major_swap_timestamp( timestamp, whirlpool.sqrt_price, current_sqrt_price, ); Ok(SwapResult { token_a, token_b, trade_fee, applied_fee_rate_min: applied_fee_rate_min.unwrap_or(base_fee_rate as u32), applied_fee_rate_max: applied_fee_rate_max.unwrap_or(base_fee_rate as u32), }) } // Private functions fn get_next_liquidity( current_liquidity: u128, next_tick: Option<&TickFacade>, a_to_b: bool, ) -> u128 { let liquidity_net = next_tick.map(|tick| tick.liquidity_net).unwrap_or(0); let liquidity_net_unsigned = liquidity_net.unsigned_abs(); if a_to_b { if liquidity_net < 0 { current_liquidity + liquidity_net_unsigned } else { current_liquidity - liquidity_net_unsigned } } else if liquidity_net < 0 { current_liquidity - liquidity_net_unsigned } else { current_liquidity + liquidity_net_unsigned } } struct SwapStepQuote { amount_in: u64, amount_out: u64, next_sqrt_price: u128, fee_amount: u64, } fn compute_swap_step( amount_remaining: u64, fee_rate: u32, current_liquidity: u128, current_sqrt_price: u128, target_sqrt_price: u128, a_to_b: bool, specified_input: bool, ) -> Result { // Any error that is not AMOUNT_EXCEEDS_MAX_U64 is not recoverable let initial_amount_fixed_delta = try_get_amount_fixed_delta( current_sqrt_price, target_sqrt_price, current_liquidity, a_to_b, specified_input, ); let is_initial_amount_fixed_overflow = initial_amount_fixed_delta == Err(AMOUNT_EXCEEDS_MAX_U64); let amount_calculated = if specified_input { try_apply_swap_fee(amount_remaining.into(), fee_rate)? } else { amount_remaining }; let next_sqrt_price = if !is_initial_amount_fixed_overflow && initial_amount_fixed_delta? <= amount_calculated { target_sqrt_price } else { try_get_next_sqrt_price( current_sqrt_price, current_liquidity, amount_calculated, a_to_b, specified_input, )? }; let is_max_swap = next_sqrt_price == target_sqrt_price; let amount_unfixed_delta = try_get_amount_unfixed_delta( current_sqrt_price, next_sqrt_price, current_liquidity, a_to_b, specified_input, )?; // If the swap is not at the max, we need to readjust the amount of the fixed token we are using let amount_fixed_delta = if !is_max_swap || is_initial_amount_fixed_overflow { try_get_amount_fixed_delta( current_sqrt_price, next_sqrt_price, current_liquidity, a_to_b, specified_input, )? } else { initial_amount_fixed_delta? }; let (amount_in, mut amount_out) = if specified_input { (amount_fixed_delta, amount_unfixed_delta) } else { (amount_unfixed_delta, amount_fixed_delta) }; // Cap output amount if using output if !specified_input && amount_out > amount_remaining { amount_out = amount_remaining; } let fee_amount = if specified_input && !is_max_swap { amount_remaining - amount_in } else { let pre_fee_amount = try_reverse_apply_swap_fee(amount_in.into(), fee_rate)?; pre_fee_amount - amount_in }; Ok(SwapStepQuote { amount_in, amount_out, next_sqrt_price, fee_amount, }) } fn try_get_amount_fixed_delta( current_sqrt_price: u128, target_sqrt_price: u128, current_liquidity: u128, a_to_b: bool, specified_input: bool, ) -> Result { if a_to_b == specified_input { try_get_amount_delta_a( current_sqrt_price.into(), target_sqrt_price.into(), current_liquidity.into(), specified_input, ) } else { try_get_amount_delta_b( current_sqrt_price.into(), target_sqrt_price.into(), current_liquidity.into(), specified_input, ) } } fn try_get_amount_unfixed_delta( current_sqrt_price: u128, target_sqrt_price: u128, current_liquidity: u128, a_to_b: bool, specified_input: bool, ) -> Result { if specified_input == a_to_b { try_get_amount_delta_b( current_sqrt_price.into(), target_sqrt_price.into(), current_liquidity.into(), !specified_input, ) } else { try_get_amount_delta_a( current_sqrt_price.into(), target_sqrt_price.into(), current_liquidity.into(), !specified_input, ) } } fn try_get_next_sqrt_price( current_sqrt_price: u128, current_liquidity: u128, amount_calculated: u64, a_to_b: bool, specified_input: bool, ) -> Result { if specified_input == a_to_b { try_get_next_sqrt_price_from_a( current_sqrt_price.into(), current_liquidity.into(), amount_calculated.into(), specified_input, ) .map(|x| x.into()) } else { try_get_next_sqrt_price_from_b( current_sqrt_price.into(), current_liquidity.into(), amount_calculated.into(), specified_input, ) .map(|x| x.into()) } } #[cfg(all(test, not(feature = "wasm")))] mod tests { use crate::{TickArrayFacade, TICK_ARRAY_SIZE}; use super::*; fn test_whirlpool(sqrt_price: u128, sufficient_liq: bool) -> WhirlpoolFacade { let tick_current_index = sqrt_price_to_tick_index(sqrt_price); let liquidity = if sufficient_liq { 100000000 } else { 265000 }; WhirlpoolFacade { tick_current_index, fee_rate: 3000, liquidity, sqrt_price, fee_tier_index_seed: [2, 0], tick_spacing: 2, ..WhirlpoolFacade::default() } } fn test_tick(positive: bool) -> TickFacade { let liquidity_net = if positive { 1000 } else { -1000 }; TickFacade { initialized: true, liquidity_net, ..TickFacade::default() } } fn test_tick_array(start_tick_index: i32) -> TickArrayFacade { let positive_liq_net = start_tick_index < 0; TickArrayFacade { start_tick_index, ticks: [test_tick(positive_liq_net); TICK_ARRAY_SIZE], } } fn test_tick_arrays() -> TickArrays { [ test_tick_array(0), test_tick_array(176), test_tick_array(352), test_tick_array(-176), test_tick_array(-352), ] .into() } fn now() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() } #[test] fn test_exact_in_a_to_b_simple() { let result = swap_quote_by_input_token( 1000, true, 1000, test_whirlpool(1 << 64, true), None, test_tick_arrays(), now(), None, None, ) .unwrap(); assert_eq!(result.token_in, 1000); assert_eq!(result.token_est_out, 996); assert_eq!(result.token_min_out, 896); assert_eq!(result.trade_fee, 3); } #[test] fn test_exact_in_a_to_b() { let result = swap_quote_by_input_token( 1000, true, 1000, test_whirlpool(1 << 64, false), None, test_tick_arrays(), now(), None, None, ) .unwrap(); assert_eq!(result.token_in, 1000); assert_eq!(result.token_est_out, 920); assert_eq!(result.token_min_out, 828); assert_eq!(result.trade_fee, 38); } #[test] fn test_exact_in_b_to_a_simple() { let result = swap_quote_by_input_token( 1000, false, 1000, test_whirlpool(1 << 64, true), None, test_tick_arrays(), now(), None, None, ) .unwrap(); assert_eq!(result.token_in, 1000); assert_eq!(result.token_est_out, 996); assert_eq!(result.token_min_out, 896); assert_eq!(result.trade_fee, 3); } #[test] fn test_exact_in_b_to_a() { let result = swap_quote_by_input_token( 1000, false, 1000, test_whirlpool(1 << 64, false), None, test_tick_arrays(), now(), None, None, ) .unwrap(); assert_eq!(result.token_in, 1000); assert_eq!(result.token_est_out, 918); assert_eq!(result.token_min_out, 826); assert_eq!(result.trade_fee, 39); } #[test] fn test_exact_out_a_to_b_simple() { let result = swap_quote_by_output_token( 1000, false, 1000, test_whirlpool(1 << 64, true), None, test_tick_arrays(), now(), None, None, ) .unwrap(); assert_eq!(result.token_out, 1000); assert_eq!(result.token_est_in, 1005); assert_eq!(result.token_max_in, 1106); assert_eq!(result.trade_fee, 4); } #[test] fn test_exact_out_a_to_b() { let result = swap_quote_by_output_token( 1000, false, 1000, test_whirlpool(1 << 64, false), None, test_tick_arrays(), now(), None, None, ) .unwrap(); assert_eq!(result.token_out, 1000); assert_eq!(result.token_est_in, 1088); assert_eq!(result.token_max_in, 1197); assert_eq!(result.trade_fee, 42); } #[test] fn test_exact_out_b_to_a_simple() { let result = swap_quote_by_output_token( 1000, true, 1000, test_whirlpool(1 << 64, true), None, test_tick_arrays(), now(), None, None, ) .unwrap(); assert_eq!(result.token_out, 1000); assert_eq!(result.token_est_in, 1005); assert_eq!(result.token_max_in, 1106); assert_eq!(result.trade_fee, 4); } #[test] fn test_exact_out_b_to_a() { let result = swap_quote_by_output_token( 1000, true, 1000, test_whirlpool(1 << 64, false), None, test_tick_arrays(), now(), None, None, ) .unwrap(); assert_eq!(result.token_out, 1000); assert_eq!(result.token_est_in, 1088); assert_eq!(result.token_max_in, 1197); assert_eq!(result.trade_fee, 42); } #[test] fn test_swap_quote_throws_if_tick_array_sequence_holds_insufficient_liquidity() { let result_3428 = swap_quote_by_input_token( 3428, true, 0, test_whirlpool(1 << 64, false), None, test_tick_arrays(), now(), None, None, ) .unwrap(); let result_3429 = swap_quote_by_input_token( 3429, true, 0, test_whirlpool(1 << 64, false), None, test_tick_arrays(), now(), None, None, ); assert_eq!(result_3428.token_in, 3428); assert!(matches!(result_3429, Err(INVALID_TICK_ARRAY_SEQUENCE))); } mod adaptive_fee { use crate::{AdaptiveFeeConstantsFacade, AdaptiveFeeVariablesFacade}; use super::*; fn test_whirlpool_with_adaptive_fee(sqrt_price: u128, liquidity: u128) -> WhirlpoolFacade { let tick_current_index = sqrt_price_to_tick_index(sqrt_price); WhirlpoolFacade { tick_current_index, fee_rate: 1000, liquidity, sqrt_price, fee_tier_index_seed: [128 + 64, 0], // u16::from_le_bytes(fee_tier_index_seed) != tick_spacing tick_spacing: 64, ..WhirlpoolFacade::default() } } fn test_oracle( trade_enable_timestamp: u64, last_reference_update_timestamp: u64, last_major_swap_timestamp: u64, volatility_reference: u32, tick_group_index_reference: i32, volatility_accumulator: u32, ) -> OracleFacade { let adaptive_fee_constants = AdaptiveFeeConstantsFacade { filter_period: 30, decay_period: 600, adaptive_fee_control_factor: 5_000, reduction_factor: 500, max_volatility_accumulator: 88 * 3 * 10_000, tick_group_size: 64, major_swap_threshold_ticks: 64, }; let adaptive_fee_variables = AdaptiveFeeVariablesFacade { last_reference_update_timestamp, last_major_swap_timestamp, volatility_reference, tick_group_index_reference, volatility_accumulator, }; OracleFacade { trade_enable_timestamp, adaptive_fee_constants, adaptive_fee_variables, } } fn test_empty_tick_array(start_tick_index: i32) -> TickArrayFacade { TickArrayFacade { start_tick_index, ticks: [TickFacade::default(); TICK_ARRAY_SIZE], } } fn test_empty_tick_arrays() -> [TickArrayFacade; 5] { [ test_empty_tick_array(0), test_empty_tick_array(5632), test_empty_tick_array(11264), test_empty_tick_array(-5632), test_empty_tick_array(-11264), ] } #[test] fn test_exact_in_a_to_b_simple() { let now = now(); let result = swap_quote_by_input_token( 150_000, true, 1000, test_whirlpool_with_adaptive_fee(tick_index_to_sqrt_price(0), 1_000_000), Some(test_oracle(now, now, now, 0, 0, 0)), test_empty_tick_arrays().into(), // full-range liquidity now, None, None, ) .unwrap(); assert_eq!(result.token_in, 150_000); assert_eq!(result.token_est_out, 122_534); assert_eq!(result.token_min_out, 110_280); assert_eq!(result.trade_fee, 10312); } #[test] fn test_exact_in_a_to_b_decay() { let now = now(); let result = swap_quote_by_input_token( 150_000, true, 1000, test_whirlpool_with_adaptive_fee(tick_index_to_sqrt_price(0), 1_000_000), Some(test_oracle(now, now - 600, now - 600, 100_000, 0, 100_000)), test_empty_tick_arrays().into(), // full-range liquidity now, None, None, ) .unwrap(); assert_eq!(result.token_in, 150_000); assert_eq!(result.token_est_out, 122_534); assert_eq!(result.token_min_out, 110_280); assert_eq!(result.trade_fee, 10312); } #[test] fn test_exact_in_b_to_a_simple() { let now = now(); let mut tick_arrays = test_empty_tick_arrays(); tick_arrays[0].ticks[22] = TickFacade { initialized: true, liquidity_net: -500_000, ..TickFacade::default() }; let result = swap_quote_by_input_token( 150_000, false, 1000, test_whirlpool_with_adaptive_fee(tick_index_to_sqrt_price(0), 1_000_000 + 500_000), Some(test_oracle(now, now, now, 0, 0, 0)), tick_arrays.into(), now, None, None, ) .unwrap(); assert_eq!(result.token_in, 150_000); assert_eq!(result.token_est_out, 129_866); assert_eq!(result.token_min_out, 116_879); assert_eq!(result.trade_fee, 7_456); } } // TODO: add more complex tests that // * only fill partially // * transfer fee }