use alloy::primitives::{
aliases::{I24, U160},
U256,
};
use anyhow::{Context, Result};
use tracing::debug;
use uniswap_sdk_core::{
entities::BaseCurrency,
prelude::{Currency, CurrencyAmount, Price},
};
pub trait PoolKey {
fn token_a(&self) -> &Currency;
fn token_b(&self) -> &Currency;
}
impl PoolKey for crate::types::v3_pool_key::V3PoolKey {
fn token_a(&self) -> &Currency { &self.token_a }
fn token_b(&self) -> &Currency { &self.token_b }
}
impl PoolKey for waterpump_evm_core::pool_key::SlipstreamPoolKey {
fn token_a(&self) -> &Currency { &self.token_a }
fn token_b(&self) -> &Currency { &self.token_b }
}
impl PoolKey for waterpump_evm_core::pool_key::V4PoolKey {
fn token_a(&self) -> &Currency { &self.token_a }
fn token_b(&self) -> &Currency { &self.token_b }
}
pub fn merge_two_pool_prices(
pool_a_key: &impl PoolKey,
pool_b_key: &impl PoolKey,
price_a: &Price<Currency, Currency>,
price_b: &Price<Currency, Currency>,
) -> Result<Price<Currency, Currency>> {
let merged_price = if pool_a_key.token_b().address() == pool_b_key.token_a().address() {
price_a.multiply(price_b).context("Failed to multiply prices")?
} else if pool_a_key.token_b().address() == pool_b_key.token_b().address() {
let price_b_inv = price_b.invert();
price_a.multiply(&price_b_inv).context("Failed to multiply prices")?
} else if pool_a_key.token_a().address() == pool_b_key.token_a().address() {
let price_a_inv = price_a.invert();
price_a_inv.multiply(price_b).context("Failed to multiply prices")?
} else if pool_a_key.token_a().address() == pool_b_key.token_b().address() {
let price_a_inv = price_a.invert();
let price_b_inv = price_b.invert();
price_a_inv.multiply(&price_b_inv).context("Failed to multiply prices")?
} else {
anyhow::bail!(
"Pools are not connected: pool_a ({:?}/{:?}) and pool_b ({:?}/{:?})",
pool_a_key.token_a().address(),
pool_a_key.token_b().address(),
pool_b_key.token_a().address(),
pool_b_key.token_b().address()
);
};
let merged_price_str =
merged_price.to_significant(8, None).unwrap_or_else(|_| "N/A".to_string());
debug!(
merged_price = %merged_price_str,
"Prices merged successfully"
);
Ok(merged_price)
}
pub fn calculate_position_value(
amount0: CurrencyAmount<Currency>,
amount1: CurrencyAmount<Currency>,
fee0: Option<CurrencyAmount<Currency>>,
fee1: Option<CurrencyAmount<Currency>>,
price: Price<Currency, Currency>,
target_currency0: bool,
) -> Result<CurrencyAmount<Currency>> {
let total_amount0 = if let Some(fee) = fee0 {
amount0.add(&fee).map_err(|e| anyhow::anyhow!("Failed to add amount0 and fee0: {:?}", e))?
} else {
amount0
};
let total_amount1 = if let Some(fee) = fee1 {
amount1.add(&fee).map_err(|e| anyhow::anyhow!("Failed to add amount1 and fee1: {:?}", e))?
} else {
amount1
};
let total_value =
calculate_value_in_target_currency(total_amount0, total_amount1, price, target_currency0)
.map_err(|e| anyhow::anyhow!("Failed to calculate position value: {:?}", e))?;
Ok(total_value)
}
pub fn calculate_value_in_target_currency(
amount0: CurrencyAmount<Currency>,
amount1: CurrencyAmount<Currency>,
price: Price<Currency, Currency>,
target_currency0: bool,
) -> Result<CurrencyAmount<Currency>> {
if target_currency0 {
let inverted_price = price.invert();
let amount1_in_currency0 = inverted_price
.quote(&amount1)
.map_err(|e| anyhow::anyhow!("Failed to quote amount1 to currency0: {:?}", e))?;
amount0
.add(&amount1_in_currency0)
.map_err(|e| anyhow::anyhow!("Failed to add amounts in currency0: {:?}", e))
} else {
let amount0_in_currency1 = price
.quote(&amount0)
.map_err(|e| anyhow::anyhow!("Failed to quote amount0 to currency1: {:?}", e))?;
amount1
.add(&amount0_in_currency1)
.map_err(|e| anyhow::anyhow!("Failed to add amounts in currency1: {:?}", e))
}
}
#[allow(clippy::too_many_arguments)]
pub fn calculate_tokens_owed_unrealized(
tick: I24,
tick_lower: I24,
tick_upper: I24,
fee_growth_global_0x128: U256,
fee_growth_global_1x128: U256,
fee_growth_outside_0x128_lower: U256,
fee_growth_outside_1x128_lower: U256,
fee_growth_outside_0x128_upper: U256,
fee_growth_outside_1x128_upper: U256,
fee_growth_inside_0_last_x128: U256,
fee_growth_inside_1_last_x128: U256,
liquidity: u128,
) -> Result<(U256, U256)> {
use uniswap_v3_sdk::prelude::get_tokens_owed;
let tick_bytes = tick.to_le_bytes::<3>();
let tick_lower_bytes = tick_lower.to_le_bytes::<3>();
let tick_upper_bytes = tick_upper.to_le_bytes::<3>();
let sign_extend = |bytes: [u8; 3]| -> [u8; 4] {
let sign_byte = if bytes[2] & 0x80 != 0 { 0xFF } else { 0x00 };
[bytes[0], bytes[1], bytes[2], sign_byte]
};
let tick_i32 = i32::from_le_bytes(sign_extend(tick_bytes));
let tick_lower_i32 = i32::from_le_bytes(sign_extend(tick_lower_bytes));
let tick_upper_i32 = i32::from_le_bytes(sign_extend(tick_upper_bytes));
let (fee_growth_inside_0x128, fee_growth_inside_1x128) = if tick_i32 < tick_lower_i32 {
(
fee_growth_outside_0x128_lower.wrapping_sub(fee_growth_outside_0x128_upper),
fee_growth_outside_1x128_lower.wrapping_sub(fee_growth_outside_1x128_upper),
)
} else if tick_i32 >= tick_upper_i32 {
(
fee_growth_outside_0x128_upper.wrapping_sub(fee_growth_outside_0x128_lower),
fee_growth_outside_1x128_upper.wrapping_sub(fee_growth_outside_1x128_lower),
)
} else {
(
fee_growth_global_0x128
.wrapping_sub(fee_growth_outside_0x128_lower)
.wrapping_sub(fee_growth_outside_0x128_upper),
fee_growth_global_1x128
.wrapping_sub(fee_growth_outside_1x128_lower)
.wrapping_sub(fee_growth_outside_1x128_upper),
)
};
let (tokens_owed_0, tokens_owed_1) = get_tokens_owed(
fee_growth_inside_0_last_x128,
fee_growth_inside_1_last_x128,
liquidity,
fee_growth_inside_0x128,
fee_growth_inside_1x128,
);
Ok((tokens_owed_0, tokens_owed_1))
}
pub fn calculate_position_token_amounts(
liquidity: u128,
tick_lower: I24,
tick_upper: I24,
current_tick: I24,
sqrt_price_x96: U160,
) -> Result<(U256, U256)> {
use waterpump_evm_amm_math::{sqrt_price_math, tick_math::get_sqrt_ratio_at_tick};
let i24_to_i32 = |tick: I24| -> i32 {
let bytes = tick.to_le_bytes::<3>();
let sign_byte = if bytes[2] & 0x80 != 0 { 0xFF } else { 0x00 };
i32::from_le_bytes([bytes[0], bytes[1], bytes[2], sign_byte])
};
let tick_lower_i32 = i24_to_i32(tick_lower);
let tick_upper_i32 = i24_to_i32(tick_upper);
let current_tick_i32 = i24_to_i32(current_tick);
let sqrt_price_lower = get_sqrt_ratio_at_tick(tick_lower_i32)?;
let sqrt_price_upper = get_sqrt_ratio_at_tick(tick_upper_i32)?;
let sqrt_price_current = U256::from(sqrt_price_x96);
let (amount0_raw, amount1_raw) = if current_tick_i32 < tick_lower_i32 {
let amount0 = sqrt_price_math::get_amount_0_delta(
sqrt_price_lower,
sqrt_price_upper,
liquidity,
false, )?;
(amount0, U256::from(0u128))
} else if current_tick_i32 >= tick_upper_i32 {
let amount1 = sqrt_price_math::get_amount_1_delta(
sqrt_price_lower,
sqrt_price_upper,
liquidity,
false, )?;
(U256::from(0u128), amount1)
} else {
let amount0 = sqrt_price_math::get_amount_0_delta(
sqrt_price_current,
sqrt_price_upper,
liquidity,
false,
)?;
let amount1 = sqrt_price_math::get_amount_1_delta(
sqrt_price_lower,
sqrt_price_current,
liquidity,
false,
)?;
(amount0, amount1)
};
Ok((amount0_raw, amount1_raw))
}
#[cfg(test)]
mod tests {
use alloy::primitives::{aliases::I24, U256};
use super::*;
fn i24_from_i32(val: i32) -> I24 {
let bytes = val.to_le_bytes();
I24::from_le_bytes::<3>([bytes[0], bytes[1], bytes[2]])
}
#[test]
fn test_fee_growth_wrapping_tick_below_lower() {
let result = calculate_tokens_owed_unrealized(
i24_from_i32(-100), i24_from_i32(-50), i24_from_i32(50), U256::from(1000u64), U256::from(1000u64), U256::from(10u64), U256::from(10u64), U256::from(20u64), U256::from(20u64), U256::ZERO, U256::ZERO, 0u128, );
assert!(result.is_ok());
}
#[test]
fn test_fee_growth_wrapping_tick_above_upper() {
let result = calculate_tokens_owed_unrealized(
i24_from_i32(100), i24_from_i32(-50), i24_from_i32(50), U256::from(1000u64), U256::from(1000u64), U256::from(30u64), U256::from(30u64), U256::from(5u64), U256::from(5u64), U256::ZERO, U256::ZERO, 0u128,
);
assert!(result.is_ok());
}
#[test]
fn test_fee_growth_wrapping_tick_in_range() {
let result = calculate_tokens_owed_unrealized(
i24_from_i32(0), i24_from_i32(-50), i24_from_i32(50), U256::from(10u64), U256::from(10u64), U256::from(100u64), U256::from(100u64), U256::from(200u64), U256::from(200u64), U256::ZERO, U256::ZERO, 0u128,
);
assert!(result.is_ok());
}
#[test]
fn test_fee_growth_normal_no_wrap() {
let result = calculate_tokens_owed_unrealized(
i24_from_i32(0),
i24_from_i32(-50),
i24_from_i32(50),
U256::from(1000u64),
U256::from(2000u64),
U256::from(100u64),
U256::from(200u64),
U256::from(200u64),
U256::from(300u64),
U256::ZERO,
U256::ZERO,
1u128,
);
assert!(result.is_ok());
let (owed0, owed1) = result.unwrap();
assert_eq!(owed0, U256::ZERO);
assert_eq!(owed1, U256::ZERO);
}
}