#[cfg(feature = "wasm")]
use fusionamm_macros::wasm_expose;
use crate::{tick_index_to_sqrt_price, CoreError, AMOUNT_EXCEEDS_MAX_U64, ARITHMETIC_OVERFLOW, INVALID_RANGE_BOUNDS};
use ethnum::U256;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "wasm", wasm_expose)]
pub struct TokenPair {
pub a: u64,
pub b: u64,
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn try_get_liquidity_from_amount_a(amount_a: u64, sqrt_price_lower: u128, sqrt_price_upper: u128) -> Result<u128, CoreError> {
let sqrt_price_diff = sqrt_price_upper - sqrt_price_lower;
let mul: U256 = <U256>::from(amount_a)
.checked_mul(sqrt_price_lower.into())
.ok_or(ARITHMETIC_OVERFLOW)?
.checked_mul(sqrt_price_upper.into())
.ok_or(ARITHMETIC_OVERFLOW)?;
let result: U256 = (mul / sqrt_price_diff) >> 64;
result.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn try_get_amount_a_from_liquidity(liquidity: u128, sqrt_price_lower: u128, sqrt_price_upper: u128, round_up: bool) -> Result<u64, CoreError> {
let sqrt_price_diff = sqrt_price_upper - sqrt_price_lower;
let numerator: U256 = <U256>::from(liquidity)
.checked_mul(sqrt_price_diff.into())
.ok_or(ARITHMETIC_OVERFLOW)?
.checked_shl(64)
.ok_or(ARITHMETIC_OVERFLOW)?;
let denominator = <U256>::from(sqrt_price_upper)
.checked_mul(<U256>::from(sqrt_price_lower))
.ok_or(ARITHMETIC_OVERFLOW)?;
let quotient = numerator / denominator;
let remainder = numerator % denominator;
let result = if round_up && remainder != 0 { quotient + 1 } else { quotient };
result.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn try_get_liquidity_from_amount_b(amount_b: u64, sqrt_price_lower: u128, sqrt_price_upper: u128) -> Result<u128, CoreError> {
let numerator: U256 = <U256>::from(amount_b).checked_shl(64).ok_or(ARITHMETIC_OVERFLOW)?;
let sqrt_price_diff = sqrt_price_upper - sqrt_price_lower;
let result = numerator / <U256>::from(sqrt_price_diff);
result.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn try_get_amount_b_from_liquidity(liquidity: u128, sqrt_price_lower: u128, sqrt_price_upper: u128, round_up: bool) -> Result<u64, CoreError> {
let sqrt_price_diff = sqrt_price_upper - sqrt_price_lower;
let mul: U256 = <U256>::from(liquidity).checked_mul(sqrt_price_diff.into()).ok_or(ARITHMETIC_OVERFLOW)?;
let result: U256 = mul >> 64;
if round_up && mul & <U256>::from(u64::MAX) > 0 {
(result + 1).try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
} else {
result.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
}
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn try_get_amounts_from_liquidity(
liquidity: u128,
sqrt_price: u128,
tick_lower_index: i32,
tick_upper_index: i32,
round_up: bool,
) -> Result<TokenPair, CoreError> {
if liquidity == 0 {
return Ok(TokenPair { a: 0, b: 0 });
}
if tick_lower_index > tick_upper_index {
return Err(INVALID_RANGE_BOUNDS);
}
let sqrt_price_lower = tick_index_to_sqrt_price(tick_lower_index).into();
let sqrt_price_upper = tick_index_to_sqrt_price(tick_upper_index).into();
if sqrt_price_lower == sqrt_price_upper {
Ok(TokenPair { a: 0, b: 0 })
} else if sqrt_price <= sqrt_price_lower {
let amount_a = try_get_amount_a_from_liquidity(liquidity, sqrt_price_lower, sqrt_price_upper, round_up)?;
Ok(TokenPair { a: amount_a, b: 0 })
} else if sqrt_price >= sqrt_price_upper {
let amount_b = try_get_amount_b_from_liquidity(liquidity, sqrt_price_lower, sqrt_price_upper, round_up)?;
Ok(TokenPair { a: 0, b: amount_b })
} else {
let amount_a = try_get_amount_a_from_liquidity(liquidity, sqrt_price, sqrt_price_upper, round_up)?;
let amount_b = try_get_amount_b_from_liquidity(liquidity, sqrt_price_lower, sqrt_price, round_up)?;
Ok(TokenPair { a: amount_a, b: amount_b })
}
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn try_get_liquidity_from_amounts(
sqrt_price: u128,
sqrt_price_lower: u128,
sqrt_price_upper: u128,
amount_a: u64,
amount_b: u64,
) -> Result<u128, CoreError> {
if sqrt_price_lower > sqrt_price_upper {
return Err(INVALID_RANGE_BOUNDS);
}
if sqrt_price <= sqrt_price_lower {
try_get_liquidity_from_amount_a(amount_a, sqrt_price_lower, sqrt_price_upper)
} else if sqrt_price < sqrt_price_upper {
let liquidity_a = try_get_liquidity_from_amount_a(amount_a, sqrt_price, sqrt_price_upper)?;
let liquidity_b = try_get_liquidity_from_amount_b(amount_b, sqrt_price_lower, sqrt_price)?;
Ok(u128::min(liquidity_a, liquidity_b))
} else {
try_get_liquidity_from_amount_b(amount_b, sqrt_price_lower, sqrt_price_upper)
}
}
#[cfg(all(test, not(feature = "wasm")))]
mod tests {
use crate::{try_get_amount_a_from_liquidity, try_get_amount_b_from_liquidity};
#[test]
fn test_get_amount_delta_a() {
assert_eq!(try_get_amount_a_from_liquidity(4, 2 << 64, 4 << 64, true), Ok(1));
assert_eq!(try_get_amount_a_from_liquidity(4, 2 << 64, 4 << 64, false), Ok(1));
assert_eq!(try_get_amount_a_from_liquidity(4, 4 << 64, 4 << 64, true), Ok(0));
assert_eq!(try_get_amount_a_from_liquidity(4, 4 << 64, 4 << 64, false), Ok(0));
}
#[test]
fn test_get_amount_delta_b() {
assert_eq!(try_get_amount_b_from_liquidity(4, 2 << 64, 4 << 64, true), Ok(8));
assert_eq!(try_get_amount_b_from_liquidity(4, 2 << 64, 4 << 64, false), Ok(8));
assert_eq!(try_get_amount_b_from_liquidity(4, 4 << 64, 4 << 64, true), Ok(0));
assert_eq!(try_get_amount_b_from_liquidity(4, 4 << 64, 4 << 64, false), Ok(0));
}
}