riptide-amm-math 2.0.1

The Riptide program math library
Documentation
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);
    }
}