use ethnum::U256;
use super::{
super::{
error::{CoreError, AMOUNT_EXCEEDS_MAX_U128, ARITHMETIC_OVERFLOW, INVALID_ORACLE_DATA},
oracle::PER_M_DENOMINATOR,
},
QuoteType, SingleSideLiquidity,
};
pub(crate) fn spread_liquidity(
price: u128,
bid_spread_per_m: i32,
ask_spread_per_m: i32,
quote_type: QuoteType,
reserves_a: u64,
reserves_b: u64,
) -> Result<SingleSideLiquidity, CoreError> {
let mut liquidity = SingleSideLiquidity::new();
if bid_spread_per_m <= -PER_M_DENOMINATOR
|| bid_spread_per_m >= PER_M_DENOMINATOR
|| ask_spread_per_m <= -PER_M_DENOMINATOR
{
return Err(INVALID_ORACLE_DATA);
}
let spread = if quote_type.a_to_b() {
PER_M_DENOMINATOR - bid_spread_per_m
} else {
PER_M_DENOMINATOR + ask_spread_per_m
};
let product = U256::from(price)
.checked_mul(U256::from(spread as u128))
.ok_or(ARITHMETIC_OVERFLOW)?;
let quotient = product
.checked_div(U256::from(PER_M_DENOMINATOR as u128))
.ok_or(ARITHMETIC_OVERFLOW)?;
let remainder = product
.checked_rem(U256::from(PER_M_DENOMINATOR as u128))
.ok_or(ARITHMETIC_OVERFLOW)?;
let price = if !quote_type.a_to_b() && remainder > 0 {
(quotient + 1)
.try_into()
.map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
} else {
quotient.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
};
if quote_type.a_to_b() {
liquidity.push((price, reserves_b));
} else {
liquidity.push((price, reserves_a));
}
Ok(liquidity)
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case(QuoteType::TokenAExactIn, 1000, 2000, Ok((988, 2000)))]
#[case(QuoteType::TokenAExactOut, 1000, 2000, Ok((1023, 1000)))]
#[case(QuoteType::TokenBExactIn, 1000, 2000, Ok((1023, 1000)))]
#[case(QuoteType::TokenBExactOut, 1000, 2000, Ok((988, 2000)))]
fn test_spread_liquidity(
#[case] quote_type: QuoteType,
#[case] reserves_a: u64,
#[case] reserves_b: u64,
#[case] expected: Result<(u128, u64), CoreError>,
) {
let liquidity = spread_liquidity(1000, 11111, 22222, quote_type, reserves_a, reserves_b);
let expected = expected.map(|x| SingleSideLiquidity::from_slice(&[x]));
assert_eq!(liquidity, expected);
}
#[rstest]
#[case(QuoteType::TokenAExactIn, 1000, 2000, Ok((1011, 2000)))]
#[case(QuoteType::TokenAExactOut, 1000, 2000, Ok((978, 1000)))]
#[case(QuoteType::TokenBExactIn, 1000, 2000, Ok((978, 1000)))]
#[case(QuoteType::TokenBExactOut, 1000, 2000, Ok((1011, 2000)))]
fn test_spread_liquidity_negative_spread(
#[case] quote_type: QuoteType,
#[case] reserves_a: u64,
#[case] reserves_b: u64,
#[case] expected: Result<(u128, u64), CoreError>,
) {
let liquidity = spread_liquidity(1000, -11111, -22222, quote_type, reserves_a, reserves_b);
let expected = expected.map(|x| SingleSideLiquidity::from_slice(&[x]));
assert_eq!(liquidity, expected);
}
}