use alloy::{
eips::BlockId,
network::Ethereum,
primitives::{Address, U256},
providers::DynProvider,
};
use anyhow::Result;
use tracing::{instrument, trace};
use waterpump_evm_shadow_client::interfaces::{
INonfungiblePositionManager::INonfungiblePositionManagerInstance,
IRamsesV3PoolState::IRamsesV3PoolStateInstance,
};
use crate::{
common::{calculate_position_token_amounts, calculate_tokens_owed_unrealized},
impl_multi_position_predictor, impl_multi_position_viewer, impl_pool_base,
impl_position_viewer_helpers, impl_shadow_pool_state, impl_shadow_pool_viewer,
pool_viewers::v3::utils::calculate_price_from_sqrt_price_x96,
types::{
position_metadata::PositionMetadata, position_values::PositionValues,
v3_pool_key::V3PoolKey,
},
};
#[derive(Clone, Debug)]
pub struct ShadowMultiPositionViewer {
pub position_manager_address: Address,
pub pool_address: Address,
pub pool_key: V3PoolKey,
pub position_metadatas: Vec<PositionMetadata>,
pub provider: DynProvider<Ethereum>,
}
impl ShadowMultiPositionViewer {
pub fn new(
position_manager_address: Address,
pool_address: Address,
pool_key: V3PoolKey,
position_metadatas: Vec<PositionMetadata>,
provider: DynProvider<Ethereum>,
) -> Self {
Self { position_manager_address, pool_address, pool_key, position_metadatas, provider }
}
pub async fn with_position_ids(
provider: DynProvider<Ethereum>,
position_manager_address: Address,
pool_address: Address,
pool_key: V3PoolKey,
token_ids: Vec<U256>,
block_id: Option<BlockId>,
) -> Result<Self> {
println!("position_manager_address: {:?}", position_manager_address);
let position_manager =
INonfungiblePositionManagerInstance::new(position_manager_address, provider.clone());
let mut multicall = alloy::providers::Provider::multicall(&provider).dynamic();
for token_id in &token_ids {
multicall = multicall.add_dynamic(position_manager.positions(*token_id));
}
let block_id = block_id.unwrap_or(BlockId::latest());
let position_data = multicall.block(block_id).aggregate().await?;
println!("position_data: {:?}", position_data.len());
for position in &position_data {
println!("ShadowMultiPositionViewer position liquidity: {:?}", position.liquidity);
println!("ShadowMultiPositionViewer position tick_lower: {:?}", position.tickLower);
println!("ShadowMultiPositionViewer position tick_upper: {:?}", position.tickUpper);
}
let position_metadatas = position_data
.iter()
.zip(token_ids)
.map(|(position, token_id)| PositionMetadata {
token_id,
liquidity: position.liquidity,
tick_lower: position.tickLower,
tick_upper: position.tickUpper,
})
.collect::<Vec<PositionMetadata>>();
Ok(Self { position_manager_address, pool_address, pool_key, position_metadatas, provider })
}
pub fn pool_key(&self) -> &V3PoolKey { &self.pool_key }
pub fn pool_address(&self) -> Address { self.pool_address }
pub fn position_manager_address(&self) -> Address { self.position_manager_address }
pub fn len(&self) -> usize { self.position_metadatas.len() }
pub fn is_empty(&self) -> bool { self.position_metadatas.is_empty() }
pub fn position_metadatas_internal(&self) -> Vec<PositionMetadata> {
self.position_metadatas.clone()
}
}
impl ShadowMultiPositionViewer {
#[instrument(skip(self))]
pub async fn get_positions(
&self,
block_id: Option<BlockId>,
) -> Result<
Vec<waterpump_evm_shadow_client::interfaces::INonfungiblePositionManager::positionsReturn>,
> {
let position_manager = INonfungiblePositionManagerInstance::new(
self.position_manager_address,
self.provider.clone(),
);
let block_id = block_id.unwrap_or(BlockId::latest());
let mut multicall = alloy::providers::Provider::multicall(&self.provider).dynamic();
for metadata in &self.position_metadatas {
multicall = multicall.add_dynamic(position_manager.positions(metadata.token_id));
}
let position_data = multicall.block(block_id).aggregate().await?;
trace!("Calling positions() on position manager for fees");
Ok(position_data)
}
#[instrument(skip(self))]
pub async fn position_values_internal(
&self,
block_id: Option<BlockId>,
) -> Result<Vec<PositionValues>> {
let block_id = block_id.unwrap_or(BlockId::latest());
let pool_contract =
IRamsesV3PoolStateInstance::new(self.pool_address, self.provider.clone());
let npm_contract = INonfungiblePositionManagerInstance::new(
self.position_manager_address,
self.provider.clone(),
);
let multicall_global = alloy::providers::Provider::multicall(&self.provider)
.add(pool_contract.slot0())
.add(pool_contract.feeGrowthGlobal0X128())
.add(pool_contract.feeGrowthGlobal1X128())
.block(block_id);
let mut multicall_for_tick_upper =
alloy::providers::Provider::multicall(&self.provider).dynamic();
let mut multicall_for_tick_lower =
alloy::providers::Provider::multicall(&self.provider).dynamic();
let mut multicall_for_position =
alloy::providers::Provider::multicall(&self.provider).dynamic();
for metadata in &self.position_metadatas {
multicall_for_position =
multicall_for_position.add_dynamic(npm_contract.positions(metadata.token_id));
multicall_for_tick_lower =
multicall_for_tick_lower.add_dynamic(pool_contract.ticks(metadata.tick_lower));
multicall_for_tick_upper =
multicall_for_tick_upper.add_dynamic(pool_contract.ticks(metadata.tick_upper));
}
let position_data = multicall_for_position.block(block_id).aggregate().await?;
let (slot0, fee_growth_global_0_x128, fee_growth_global_1_x128) =
multicall_global.block(block_id).aggregate().await?;
let ticks_lower = multicall_for_tick_lower.block(block_id).aggregate().await?;
let ticks_upper = multicall_for_tick_upper.block(block_id).aggregate().await?;
let ticks: Vec<(
waterpump_evm_shadow_client::interfaces::IRamsesV3PoolState::ticksReturn,
waterpump_evm_shadow_client::interfaces::IRamsesV3PoolState::ticksReturn,
)> = ticks_lower
.iter()
.zip(ticks_upper.iter())
.map(|(tick_lower, tick_upper)| (tick_lower.clone(), tick_upper.clone()))
.collect();
let mut position_values = Vec::with_capacity(position_data.len());
for (position, (tick_lower, tick_upper)) in position_data.iter().zip(ticks.iter()) {
let (tokens_owed0_unrealized, tokens_owed1_unrealized) =
calculate_tokens_owed_unrealized(
slot0.tick,
position.tickLower,
position.tickUpper,
fee_growth_global_0_x128,
fee_growth_global_1_x128,
tick_lower.feeGrowthOutside0X128,
tick_lower.feeGrowthOutside1X128,
tick_upper.feeGrowthOutside0X128,
tick_upper.feeGrowthOutside1X128,
position.feeGrowthInside0LastX128,
position.feeGrowthInside1LastX128,
position.liquidity,
)?;
let amount_owed0 = tokens_owed0_unrealized + U256::from(position.tokensOwed0);
let amount_owed1 = tokens_owed1_unrealized + U256::from(position.tokensOwed1);
let (amount_owed0, amount_owed1) =
self.to_currency_amounts(amount_owed0, amount_owed1)?;
let (amount0, amount1) = calculate_position_token_amounts(
position.liquidity,
position.tickLower,
position.tickUpper,
slot0.tick,
slot0.sqrtPriceX96,
)?;
let (amount0, amount1) = self.to_currency_amounts(amount0, amount1)?;
let price = calculate_price_from_sqrt_price_x96(
slot0.sqrtPriceX96,
self.pool_key().token_a.clone(),
self.pool_key().token_b.clone(),
);
position_values.push(PositionValues {
amount0,
amount1,
amount_owed0,
amount_owed1,
price,
});
}
Ok(position_values)
}
}
impl_pool_base!(ShadowMultiPositionViewer);
impl_multi_position_viewer!(ShadowMultiPositionViewer);
impl_position_viewer_helpers!(ShadowMultiPositionViewer);
impl_multi_position_predictor!(ShadowMultiPositionViewer);
impl_shadow_pool_state!(ShadowMultiPositionViewer);
impl_shadow_pool_viewer!(ShadowMultiPositionViewer);