#![allow(dead_code)]
use core::cmp::min;
use ethnum::I256;
#[cfg(feature = "wasm")]
use riptide_amm_macros::wasm_expose;
use borsh::{BorshDeserialize, BorshSerialize};
use super::{
error::{CoreError, AMOUNT_EXCEEDS_MAX_U32, ARITHMETIC_OVERFLOW},
quote::{Price, Quote, QuoteType},
token::{a_to_b, b_to_a, deviation_per_m},
U128,
};
mod amm;
mod book;
mod flat;
mod skew;
mod spread;
pub use amm::LiquidityType;
pub use book::BookSpacingType;
pub use skew::{SkewExponent, SkewMode};
use amm::{amm_liquidity, amm_price};
use book::{book_liquidity, new_book_liquidity, BOOK_LIQUIDITY_LEVELS};
use flat::flat_liquidity;
use skew::apply_skew_to_liquidity;
use spread::spread_liquidity;
pub(crate) const LIQUIDITY_LEVELS: usize = 32;
pub const ORACLE_DATA_LEN: usize = 276;
pub const SKEW_LEN: usize = 32;
pub const ORACLE_PAYLOAD_LEN: usize = 512;
pub const SKEW_OFFSET: usize = ORACLE_PAYLOAD_LEN - SKEW_LEN;
#[allow(clippy::len_without_is_empty)]
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "wasm", wasm_expose)]
pub struct SingleSideLiquidity {
items: [(u128, u64); LIQUIDITY_LEVELS],
len: usize,
}
impl SingleSideLiquidity {
pub fn new() -> Self {
Self {
items: [(0, 0); LIQUIDITY_LEVELS],
len: 0,
}
}
pub fn from_slice(slice: &[(u128, u64)]) -> Self {
let mut items = [(0, 0); LIQUIDITY_LEVELS];
items[..slice.len()].copy_from_slice(slice);
Self {
items,
len: slice.len(),
}
}
pub fn push(&mut self, item: (u128, u64)) -> bool {
if self.len < 32 {
self.items[self.len] = item;
self.len += 1;
true
} else {
false
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn as_slice(&self) -> &[(u128, u64)] {
&self.items[..self.len]
}
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub const PER_CENT_DENOMINATOR: i8 = 100;
#[cfg_attr(feature = "wasm", wasm_expose)]
pub const BPS_DENOMINATOR: i16 = 10000;
#[cfg_attr(feature = "wasm", wasm_expose)]
pub const PER_M_DENOMINATOR: i32 = 1_000_000;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(true, derive(BorshDeserialize, BorshSerialize))]
#[cfg_attr(feature = "wasm", wasm_expose)]
pub enum OracleData {
Empty,
FlatPrice {
price_q64_64: u128,
},
SimpleSpread {
price_q64_64: u128,
bid_spread_per_m: i32,
ask_spread_per_m: i32,
},
OrderBook {
price_q64_64: u128,
spacing: BookSpacingType,
bid_liquidity_per_m: [u32; BOOK_LIQUIDITY_LEVELS],
ask_liquidity_per_m: [u32; BOOK_LIQUIDITY_LEVELS],
},
AutomatedMarketMaker {
liquidity_type: LiquidityType,
bid_spread_per_m: i32,
ask_spread_per_m: i32,
},
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct OraclePayload {
pub data: OracleData,
pub skew: SkewMode,
}
impl BorshDeserialize for OraclePayload {
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
const _: () = assert!(ORACLE_DATA_LEN + SKEW_LEN <= ORACLE_PAYLOAD_LEN);
let mut buf = [0u8; ORACLE_PAYLOAD_LEN];
reader.read_exact(&mut buf)?;
let mut data_slice = &buf[..ORACLE_DATA_LEN];
let data = OracleData::deserialize(&mut data_slice)?;
let mut skew_slice = &buf[SKEW_OFFSET..SKEW_OFFSET + SKEW_LEN];
let skew = SkewMode::deserialize(&mut skew_slice)?;
Ok(Self { data, skew })
}
}
#[inline(never)]
fn build_base_liquidity(
data: &OracleData,
quote_type: QuoteType,
reserves_a: u64,
reserves_b: u64,
) -> Result<SingleSideLiquidity, CoreError> {
match data {
OracleData::Empty => Ok(SingleSideLiquidity::new()),
OracleData::FlatPrice { price_q64_64 } => {
flat_liquidity(*price_q64_64, quote_type, reserves_a, reserves_b)
}
OracleData::SimpleSpread {
price_q64_64,
bid_spread_per_m,
ask_spread_per_m,
} => spread_liquidity(
*price_q64_64,
*bid_spread_per_m,
*ask_spread_per_m,
quote_type,
reserves_a,
reserves_b,
),
OracleData::OrderBook {
price_q64_64,
spacing,
bid_liquidity_per_m,
ask_liquidity_per_m,
} => book_liquidity(
quote_type,
*price_q64_64,
*spacing,
bid_liquidity_per_m,
ask_liquidity_per_m,
reserves_a,
reserves_b,
),
OracleData::AutomatedMarketMaker {
liquidity_type,
bid_spread_per_m,
ask_spread_per_m,
} => amm_liquidity(
*liquidity_type,
*bid_spread_per_m,
*ask_spread_per_m,
quote_type,
reserves_a,
reserves_b,
),
}
}
pub(crate) fn build_liquidity(
payload: &OraclePayload,
quote_type: QuoteType,
reserves_a: u64,
reserves_b: u64,
) -> Result<SingleSideLiquidity, CoreError> {
let liquidity = build_base_liquidity(&payload.data, quote_type, reserves_a, reserves_b)?;
let price = match &payload.data {
OracleData::Empty | OracleData::AutomatedMarketMaker { .. } => return Ok(liquidity),
OracleData::FlatPrice { price_q64_64 }
| OracleData::SimpleSpread { price_q64_64, .. }
| OracleData::OrderBook { price_q64_64, .. } => *price_q64_64,
};
let deviation = deviation_per_m(U128::from(price), reserves_a, reserves_b)?;
let skew_per_m = payload.skew.compute_skew_per_m(deviation, quote_type)?;
apply_skew_to_liquidity(liquidity, skew_per_m, quote_type)
}
pub(crate) fn build_price(
liquidity: &SingleSideLiquidity,
oracle: &OracleData,
quote_type: QuoteType,
reserves_a: u64,
reserves_b: u64,
) -> Result<Price, CoreError> {
let best_price = liquidity
.as_slice()
.iter()
.find(|(_, liquidity)| *liquidity > 0)
.map(|(price, _)| *price)
.unwrap_or(0);
let oracle_price = match oracle {
OracleData::Empty => 0,
OracleData::FlatPrice { price_q64_64 } => *price_q64_64,
OracleData::SimpleSpread { price_q64_64, .. } => *price_q64_64,
OracleData::OrderBook { price_q64_64, .. } => *price_q64_64,
OracleData::AutomatedMarketMaker { liquidity_type, .. } => {
amm_price(*liquidity_type, reserves_a, reserves_b)?
}
};
let diff = if quote_type.a_to_b() {
I256::from(oracle_price)
.checked_sub(I256::from(best_price))
.ok_or(ARITHMETIC_OVERFLOW)?
} else {
I256::from(best_price)
.checked_sub(I256::from(oracle_price))
.ok_or(ARITHMETIC_OVERFLOW)?
};
let spread = if best_price > 0 {
diff.checked_mul(I256::from(PER_M_DENOMINATOR))
.ok_or(ARITHMETIC_OVERFLOW)?
.checked_div(I256::from(oracle_price))
.ok_or(ARITHMETIC_OVERFLOW)?
.try_into()
.map_err(|_| AMOUNT_EXCEEDS_MAX_U32)?
} else {
0
};
Ok(Price {
oracle_price_q64_64: oracle_price,
best_price_q64_64: best_price,
spread_per_m: spread,
})
}
pub(crate) fn consume_liquidity(
amount: u64,
quote_type: QuoteType,
liquidity: &SingleSideLiquidity,
) -> Result<Quote, CoreError> {
let mut remaining_amount = amount;
let mut other_amount: u64 = 0;
for (price, liquidity) in liquidity.as_slice() {
if *price == 0 || *liquidity == 0 {
continue;
}
let (step_specified_amount, step_other_amount) = if quote_type.exact_in() {
let max_amount = if quote_type.input_is_token_a() {
b_to_a(*liquidity, U128::from(*price), true)?
} else {
a_to_b(*liquidity, U128::from(*price), true)?
};
let step_specified_amount = min(remaining_amount, max_amount);
let step_other_amount = if quote_type.input_is_token_a() {
a_to_b(step_specified_amount, U128::from(*price), false)?
} else {
b_to_a(step_specified_amount, U128::from(*price), false)?
};
(step_specified_amount, min(step_other_amount, *liquidity))
} else {
let step_specified_amount = min(remaining_amount, *liquidity);
let step_other_amount = if quote_type.output_is_token_a() {
a_to_b(step_specified_amount, U128::from(*price), true)?
} else {
b_to_a(step_specified_amount, U128::from(*price), true)?
};
(step_specified_amount, step_other_amount)
};
remaining_amount = remaining_amount
.checked_sub(step_specified_amount)
.ok_or(ARITHMETIC_OVERFLOW)?;
other_amount = other_amount
.checked_add(step_other_amount)
.ok_or(ARITHMETIC_OVERFLOW)?;
if remaining_amount == 0 {
break;
}
}
let consumed_amount = amount - remaining_amount;
let (amount_in, amount_out) = if quote_type.exact_in() {
(consumed_amount, other_amount)
} else {
(other_amount, consumed_amount)
};
Ok(Quote {
amount_in,
amount_out,
quote_type,
})
}
pub(crate) fn new_oracle_data(
oracle: &OracleData,
quote: &Quote,
reserves_a: u64,
reserves_b: u64,
) -> Result<OracleData, CoreError> {
match oracle {
OracleData::Empty
| OracleData::FlatPrice { .. }
| OracleData::SimpleSpread { .. }
| OracleData::AutomatedMarketMaker { .. } => Ok(*oracle),
OracleData::OrderBook {
price_q64_64,
spacing,
bid_liquidity_per_m,
ask_liquidity_per_m,
} => new_book_liquidity(
quote,
*price_q64_64,
*spacing,
bid_liquidity_per_m,
ask_liquidity_per_m,
reserves_a,
reserves_b,
),
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn test_empty(
#[values(true, false)] amount_is_token_a: bool,
#[values(true, false)] amount_is_input: bool,
) {
let quote_type = QuoteType::new(amount_is_token_a, amount_is_input);
let payload = OraclePayload {
data: OracleData::Empty,
skew: SkewMode::None,
};
let liquidity = build_liquidity(&payload, quote_type, 1000, 1000).unwrap();
let quote = consume_liquidity(100, quote_type, &liquidity).unwrap();
assert_eq!(
quote,
Quote {
amount_in: 0,
amount_out: 0,
quote_type,
}
);
}
#[rstest]
#[case(OracleData::FlatPrice { price_q64_64: 100 }, QuoteType::TokenAExactIn, 100, 100, 0)]
#[case(OracleData::FlatPrice { price_q64_64: 100 }, QuoteType::TokenBExactIn, 100, 100, 0)]
#[case(OracleData::SimpleSpread { price_q64_64: 100, bid_spread_per_m: 10000, ask_spread_per_m: 20000 }, QuoteType::TokenAExactIn, 100, 99, 10000)]
#[case(OracleData::SimpleSpread { price_q64_64: 100, bid_spread_per_m: 10000, ask_spread_per_m: 20000 }, QuoteType::TokenBExactIn, 100, 102, 20000)]
#[case(OracleData::SimpleSpread { price_q64_64: 100, bid_spread_per_m: -10000, ask_spread_per_m: -20000 }, QuoteType::TokenAExactIn, 100, 101, -10000)]
#[case(OracleData::SimpleSpread { price_q64_64: 100, bid_spread_per_m: -10000, ask_spread_per_m: -20000 }, QuoteType::TokenBExactIn, 100, 98, -20000)]
fn test_build_price(
#[case] oracle: OracleData,
#[case] quote_type: QuoteType,
#[case] expected_oracle_price: u128,
#[case] expected_best_price: u128,
#[case] expected_spread_per_m: i32,
) {
let reserves_a = 1_000_000;
let reserves_b = 1_000_000;
let payload = OraclePayload {
data: oracle,
skew: SkewMode::None,
};
let liquidity = build_liquidity(&payload, quote_type, reserves_a, reserves_b).unwrap();
let price = build_price(&liquidity, &oracle, quote_type, reserves_a, reserves_b).unwrap();
let expected = Price {
oracle_price_q64_64: expected_oracle_price,
best_price_q64_64: expected_best_price,
spread_per_m: expected_spread_per_m,
};
assert_eq!(price, expected);
}
#[rstest]
#[case(vec![500_000, 500_000, 0], 100, 100, 0)]
#[case(vec![0, 500_000, 500_000], 100, 99, 10000)]
#[case(vec![0, 0, 1_000_000], 100, 98, 20000)]
#[case(vec![0, 0, 0], 100, 0, 0)]
fn test_build_price_book(
#[case] liquidity: Vec<u32>,
#[case] expected_oracle_price: u128,
#[case] expected_best_price: u128,
#[case] expected_spread_per_m: i32,
) {
let mut liquidity_per_m = [0u32; 32];
liquidity_per_m[..liquidity.len()].copy_from_slice(&liquidity);
let reserves_a = 1_000_000;
let reserves_b = 1_000_000;
let oracle = OracleData::OrderBook {
price_q64_64: 100,
spacing: BookSpacingType::Linear(10000),
bid_liquidity_per_m: liquidity_per_m,
ask_liquidity_per_m: [0; 32],
};
let payload = OraclePayload {
data: oracle,
skew: SkewMode::None,
};
let liquidity =
build_liquidity(&payload, QuoteType::TokenAExactIn, reserves_a, reserves_b).unwrap();
let price = build_price(
&liquidity,
&oracle,
QuoteType::TokenAExactIn,
reserves_a,
reserves_b,
)
.unwrap();
let expected = Price {
oracle_price_q64_64: expected_oracle_price,
best_price_q64_64: expected_best_price,
spread_per_m: expected_spread_per_m,
};
assert_eq!(price, expected);
}
#[rstest]
#[case(OracleData::Empty)]
#[case(OracleData::FlatPrice { price_q64_64: 1 << 64 })]
#[case(OracleData::SimpleSpread { price_q64_64: 1 << 64, bid_spread_per_m: 1000, ask_spread_per_m: 1000 })]
#[case(OracleData::AutomatedMarketMaker { liquidity_type: LiquidityType::ConstantProduct, bid_spread_per_m: 1000, ask_spread_per_m: 1000 })]
fn test_new_liquidity_unchanged(#[case] oracle: OracleData) {
let quote = Quote {
amount_in: 100,
amount_out: 100,
quote_type: QuoteType::TokenAExactIn,
};
let new_oracle = new_oracle_data(&oracle, "e, 0, 0).unwrap();
assert_eq!(new_oracle, oracle);
}
#[rstest]
#[case(100, Ok(Quote { amount_in: 100, amount_out: 100, quote_type: QuoteType::TokenAExactIn }))]
#[case(500, Ok(Quote { amount_in: 500, amount_out: 300, quote_type: QuoteType::TokenAExactIn }))]
#[case(1100, Ok(Quote { amount_in: 1100, amount_out: 600, quote_type: QuoteType::TokenAExactIn }))]
#[case(2500, Ok(Quote { amount_in: 2500, amount_out: 950, quote_type: QuoteType::TokenAExactIn }))]
#[case(10000, Ok(Quote { amount_in: 5100, amount_out: 1600, quote_type: QuoteType::TokenAExactIn }))]
fn test_consume_liquidity_input_token_a(
#[case] amount: u64,
#[case] expected: Result<Quote, CoreError>,
) {
let liquidity = SingleSideLiquidity::from_slice(&[
(1 << 64, 100),
((1 << 64) / 2, 500),
((1 << 64) / 4, 1000),
]);
let quote = consume_liquidity(amount, QuoteType::TokenAExactIn, &liquidity);
assert_eq!(quote, expected);
}
#[rstest]
#[case(100, Ok(Quote { amount_in: 100, amount_out: 100, quote_type: QuoteType::TokenBExactIn }))]
#[case(500, Ok(Quote { amount_in: 500, amount_out: 300, quote_type: QuoteType::TokenBExactIn }))]
#[case(1100, Ok(Quote { amount_in: 1100, amount_out: 600, quote_type: QuoteType::TokenBExactIn }))]
#[case(2500, Ok(Quote { amount_in: 2500, amount_out: 950, quote_type: QuoteType::TokenBExactIn }))]
#[case(10000, Ok(Quote { amount_in: 5100, amount_out: 1600, quote_type: QuoteType::TokenBExactIn }))]
fn test_consume_liquidity_input_token_b(
#[case] amount: u64,
#[case] expected: Result<Quote, CoreError>,
) {
let liquidity =
SingleSideLiquidity::from_slice(&[(1 << 64, 100), (2 << 64, 500), (4 << 64, 1000)]);
let quote = consume_liquidity(amount, QuoteType::TokenBExactIn, &liquidity);
assert_eq!(quote, expected);
}
#[rstest]
#[case(100, Ok(Quote { amount_in: 100, amount_out: 100, quote_type: QuoteType::TokenAExactOut }))]
#[case(300, Ok(Quote { amount_in: 500, amount_out: 300, quote_type: QuoteType::TokenAExactOut }))]
#[case(600, Ok(Quote { amount_in: 1100, amount_out: 600, quote_type: QuoteType::TokenAExactOut }))]
#[case(950, Ok(Quote { amount_in: 2500, amount_out: 950, quote_type: QuoteType::TokenAExactOut }))]
#[case(2000, Ok(Quote { amount_in: 5100, amount_out: 1600, quote_type: QuoteType::TokenAExactOut }))]
fn test_consume_liquidity_output_token_a(
#[case] amount: u64,
#[case] expected: Result<Quote, CoreError>,
) {
let liquidity =
SingleSideLiquidity::from_slice(&[(1 << 64, 100), (2 << 64, 500), (4 << 64, 1000)]);
let quote = consume_liquidity(amount, QuoteType::TokenAExactOut, &liquidity);
assert_eq!(quote, expected);
}
#[rstest]
#[case(100, Ok(Quote { amount_in: 100, amount_out: 100, quote_type: QuoteType::TokenBExactOut }))]
#[case(300, Ok(Quote { amount_in: 500, amount_out: 300, quote_type: QuoteType::TokenBExactOut }))]
#[case(600, Ok(Quote { amount_in: 1100, amount_out: 600, quote_type: QuoteType::TokenBExactOut }))]
#[case(950, Ok(Quote { amount_in: 2500, amount_out: 950, quote_type: QuoteType::TokenBExactOut }))]
#[case(2000, Ok(Quote { amount_in: 5100, amount_out: 1600, quote_type: QuoteType::TokenBExactOut }))]
fn test_consume_liquidity_output_token_b(
#[case] amount: u64,
#[case] expected: Result<Quote, CoreError>,
) {
let liquidity = SingleSideLiquidity::from_slice(&[
(1 << 64, 100),
((1 << 64) / 2, 500),
((1 << 64) / 4, 1000),
]);
let quote = consume_liquidity(amount, QuoteType::TokenBExactOut, &liquidity);
assert_eq!(quote, expected);
}
#[rstest]
#[case((1 << 64) / 8, true, true, Ok(Quote { amount_in: 100, amount_out: 12, quote_type: QuoteType::TokenAExactIn }))]
#[case((1 << 64) / 8, true, false, Ok(Quote { amount_in: 13, amount_out: 100, quote_type: QuoteType::TokenAExactOut }))]
#[case(8 << 64, false, true, Ok(Quote { amount_in: 100, amount_out: 12, quote_type: QuoteType::TokenBExactIn }))]
#[case(8 << 64, false, false, Ok(Quote { amount_in: 13, amount_out: 100, quote_type: QuoteType::TokenBExactOut }))]
fn test_consume_liquidity_rounding_direction(
#[case] price: u128,
#[case] amount_is_token_a: bool,
#[case] amount_is_input: bool,
#[case] expected: Result<Quote, CoreError>,
) {
let liquidity = SingleSideLiquidity::from_slice(&[(price, 1000)]);
let quote_type = QuoteType::new(amount_is_token_a, amount_is_input);
let result = consume_liquidity(100, quote_type, &liquidity);
assert_eq!(result, expected);
}
const PRICE_ONE: u128 = 1 << 64;
#[rstest]
#[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 500, 500, SkewMode::None, false)] #[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 500, 500, SkewMode::Polynomial { exponent: SkewExponent::Linear, positive_bid_per_m: 500_000, negative_bid_per_m: 500_000, positive_ask_per_m: 500_000, negative_ask_per_m: 500_000 }, false)] #[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 750, 250, SkewMode::Polynomial { exponent: SkewExponent::Linear, positive_bid_per_m: 500_000, negative_bid_per_m: 500_000, positive_ask_per_m: 500_000, negative_ask_per_m: 500_000 }, true)] #[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 250, 750, SkewMode::Polynomial { exponent: SkewExponent::Linear, positive_bid_per_m: 500_000, negative_bid_per_m: 500_000, positive_ask_per_m: 500_000, negative_ask_per_m: 500_000 }, false)] #[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 750, 250, SkewMode::Polynomial { exponent: SkewExponent::Linear, positive_bid_per_m: 100_000, negative_bid_per_m: 100_000, positive_ask_per_m: 100_000, negative_ask_per_m: 100_000 }, true)] #[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 750, 250, SkewMode::Polynomial { exponent: SkewExponent::Linear, positive_bid_per_m: 1_000_000, negative_bid_per_m: 1_000_000, positive_ask_per_m: 1_000_000, negative_ask_per_m: 1_000_000 }, true)] #[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 750, 250, SkewMode::Polynomial { exponent: SkewExponent::Quadratic, positive_bid_per_m: 500_000, negative_bid_per_m: 500_000, positive_ask_per_m: 500_000, negative_ask_per_m: 500_000 }, true)] #[case(OracleData::FlatPrice { price_q64_64: PRICE_ONE }, 750, 250, SkewMode::Polynomial { exponent: SkewExponent::Linear, positive_bid_per_m: 500_000, negative_bid_per_m: 500_000, positive_ask_per_m: 500_000, negative_ask_per_m: 500_000 }, true)] #[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 750, 250, SkewMode::Polynomial { exponent: SkewExponent::Linear, positive_bid_per_m: 100_000, negative_bid_per_m: 200_000, positive_ask_per_m: 300_000, negative_ask_per_m: 400_000 }, true)] #[case(OracleData::SimpleSpread { price_q64_64: PRICE_ONE, bid_spread_per_m: 10_000, ask_spread_per_m: 10_000 }, 250, 750, SkewMode::Polynomial { exponent: SkewExponent::Linear, positive_bid_per_m: 100_000, negative_bid_per_m: 200_000, positive_ask_per_m: 300_000, negative_ask_per_m: 400_000 }, false)] fn test_build_liquidity_skew(
#[case] oracle: OracleData,
#[case] reserves_a: u64,
#[case] reserves_b: u64,
#[case] skew_mode: SkewMode,
#[case] price_lower: bool,
) {
let payload_skew = OraclePayload {
data: oracle,
skew: skew_mode,
};
let payload_none = OraclePayload {
data: oracle,
skew: SkewMode::None,
};
let result_skew = build_liquidity(
&payload_skew,
QuoteType::TokenAExactIn,
reserves_a,
reserves_b,
)
.unwrap();
let result_none = build_liquidity(
&payload_none,
QuoteType::TokenAExactIn,
reserves_a,
reserves_b,
)
.unwrap();
let (skew_price, _) = result_skew.as_slice()[0];
let (none_price, _) = result_none.as_slice()[0];
assert_eq!(skew_price < none_price, price_lower);
}
#[test]
fn test_build_liquidity_skew_empty() {
let payload = OraclePayload {
data: OracleData::Empty,
skew: SkewMode::Polynomial {
exponent: SkewExponent::Linear,
positive_bid_per_m: 500_000,
negative_bid_per_m: 500_000,
positive_ask_per_m: 500_000,
negative_ask_per_m: 500_000,
},
};
let result = build_liquidity(&payload, QuoteType::TokenAExactIn, 750, 250).unwrap();
assert_eq!(result.len(), 0);
}
}