diff --git a/Cargo.toml b/Cargo.toml index a079929..bf827e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniswap-lens" -version = "0.0.1" +version = "0.0.2" edition = "2021" authors = ["Shuhui Luo "] description = "Contains ephemeral lens contracts that can be called without deployment and their Rust interfaces." @@ -19,4 +19,5 @@ alloy = { version = "0.2.0", features = ["rpc-types", "transport-http"] } dotenv = "0.15.0" futures = "0.3" once_cell = "1.19" +ruint = "1.12" tokio = { version = "1", features = ["full"] } diff --git a/src/lib.rs b/src/lib.rs index f987494..a15edfa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,9 +17,13 @@ pub mod bindings; pub mod caller; pub mod pool_lens; -// pub mod position_lens; +pub mod position_lens; + +#[cfg(test)] +mod tests; + // pub mod storage_lens; pub mod prelude { - pub use super::{bindings::*, pool_lens::*}; + pub use super::{bindings::*, pool_lens::*, position_lens::*}; } diff --git a/src/pool_lens.rs b/src/pool_lens.rs index f2aa137..f4848e0 100644 --- a/src/pool_lens.rs +++ b/src/pool_lens.rs @@ -119,33 +119,15 @@ where #[cfg(test)] mod tests { use super::*; - use crate::bindings::iuniswapv3pool::IUniswapV3Pool::{IUniswapV3PoolInstance, Mint}; - use alloy::{ - primitives::address, - providers::{ProviderBuilder, ReqwestProvider}, - rpc::types::Filter, - sol_types::SolEvent, - transports::http::reqwest::Url, + use crate::{ + bindings::iuniswapv3pool::IUniswapV3Pool::{IUniswapV3PoolInstance, Mint}, + tests::*, }; + use alloy::{primitives::address, rpc::types::Filter, sol_types::SolEvent}; use anyhow::Result; - use dotenv::dotenv; use futures::future::join_all; - use once_cell::sync::Lazy; const POOL_ADDRESS: Address = address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"); - static BLOCK_NUMBER: Lazy = Lazy::new(|| BlockId::from(17000000)); - static RPC_URL: Lazy = Lazy::new(|| { - dotenv().ok(); - format!( - "https://mainnet.infura.io/v3/{}", - std::env::var("INFURA_API_KEY").unwrap() - ) - .parse() - .unwrap() - }); - static PROVIDER: Lazy = - Lazy::new(|| ProviderBuilder::new().on_http(RPC_URL.clone())); - #[tokio::test] async fn test_get_populated_ticks_in_range() -> Result<()> { let provider = PROVIDER.clone(); diff --git a/src/position_lens.rs b/src/position_lens.rs index 0a031de..51cc3fc 100644 --- a/src/position_lens.rs +++ b/src/position_lens.rs @@ -1,90 +1,110 @@ use crate::{ bindings::{ - ephemeral_all_positions_by_owner::{AllPositionsReturn, EphemeralAllPositionsByOwner}, - ephemeral_get_position::{EphemeralGetPosition, GetPositionReturn, PositionState}, - ephemeral_get_positions::{EphemeralGetPositions, GetPositionsReturn}, + ephemeralallpositionsbyowner::{ + EphemeralAllPositionsByOwner, + EphemeralAllPositionsByOwner::{ + allPositionsCall, allPositionsReturn, EphemeralAllPositionsByOwnerInstance, + }, + }, + ephemeralgetposition::{ + EphemeralGetPosition, + EphemeralGetPosition::{ + getPositionCall, getPositionReturn, EphemeralGetPositionInstance, + }, + }, + ephemeralgetpositions::{ + EphemeralGetPositions, + EphemeralGetPositions::{ + getPositionsCall, getPositionsReturn, EphemeralGetPositionsInstance, + }, + }, }, call_ephemeral_contract, }; -use ethers::{abi::AbiDecode, prelude::*}; -use std::sync::Arc; +use alloy::{ + contract::Error, + eips::BlockId, + primitives::{Address, Bytes, U256}, + providers::Provider, + sol_types::SolCall, + transports::{Transport, TransportError}, +}; +use anyhow::Result; -pub async fn get_position_details( +pub async fn get_position_details( npm: Address, token_id: U256, - client: Arc, + provider: P, block_id: Option, -) -> Result> { - match call_ephemeral_contract!( - EphemeralGetPosition, - (npm, token_id), - GetPositionReturn, - client, - block_id - ) { - Ok(GetPositionReturn { state }) => Ok(state), - Err(err) => Err(err), +) -> Result +where + T: Transport + Clone, + P: Provider, +{ + let deploy_builder = EphemeralGetPositionInstance::deploy_builder(provider, npm, token_id); + match call_ephemeral_contract!(deploy_builder, getPositionCall, block_id) { + Ok(getPositionReturn { state }) => Ok(state), + Err(err) => Err(err.into()), } } -pub async fn get_positions( +pub async fn get_positions( npm: Address, token_ids: Vec, - client: Arc, + provider: P, block_id: Option, -) -> Result, ContractError> { - match call_ephemeral_contract!( - EphemeralGetPositions, - (npm, token_ids), - GetPositionsReturn, - client, - block_id - ) { - Ok(GetPositionsReturn { positions }) => Ok(positions), - Err(err) => Err(err), +) -> Result> +where + T: Transport + Clone, + P: Provider, +{ + let deploy_builder = EphemeralGetPositionsInstance::deploy_builder(provider, npm, token_ids); + match call_ephemeral_contract!(deploy_builder, getPositionsCall, block_id) { + Ok(getPositionsReturn { positions }) => Ok(positions), + Err(err) => Err(err.into()), } } -pub async fn get_all_positions_by_owner( +pub async fn get_all_positions_by_owner( npm: Address, owner: Address, - client: Arc, + provider: P, block_id: Option, -) -> Result, ContractError> { - match call_ephemeral_contract!( - EphemeralAllPositionsByOwner, - (npm, owner), - AllPositionsReturn, - client, - block_id - ) { - Ok(AllPositionsReturn { positions }) => Ok(positions), - Err(err) => Err(err), +) -> Result> +where + T: Transport + Clone, + P: Provider, +{ + let deploy_builder = EphemeralAllPositionsByOwnerInstance::deploy_builder(provider, npm, owner); + match call_ephemeral_contract!(deploy_builder, allPositionsCall, block_id) { + Ok(allPositionsReturn { positions }) => Ok(positions), + Err(err) => Err(err.into()), } } #[cfg(test)] mod tests { use super::*; - use crate::bindings::{ - i_nonfungible_position_manager::INonfungiblePositionManager, - i_uniswap_v3_pool::IUniswapV3Pool, - shared_types::{PositionFull, Slot0}, + use crate::{ + bindings::{ + ephemeralgetposition::EphemeralGetPosition::{PositionFull, Slot0}, + inonfungiblepositionmanager::INonfungiblePositionManager::INonfungiblePositionManagerInstance, + iuniswapv3pool::IUniswapV3Pool::IUniswapV3PoolInstance, + }, + tests::*, }; - use anyhow::Result; - use ethers::{ - abi::{encode, Token}, - utils::{get_create2_address_from_hash, keccak256}, + use alloy::{ + primitives::{address, b256, keccak256, uint, B256}, + sol_types::SolValue, }; - use std::{ops::Sub, str::FromStr}; - const FACTORY_ADDRESS: &str = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; - const NPM_ADDRESS: &str = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"; - static POOL_INIT_CODE_HASH: Lazy = - Lazy::new(|| Bytes::from_str("0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54").unwrap()); - static BLOCK_NUMBER: Lazy = Lazy::new(|| BlockId::from(17000000)); + const FACTORY_ADDRESS: Address = address!("1F98431c8aD98523631AE4a59f267346ea31F984"); + const NPM_ADDRESS: Address = address!("C36442b4a4522E871399CD717aBDD847Ab11FE88"); + static POOL_INIT_CODE_HASH: B256 = + b256!("e34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54"); - /// Computes the address of a Uniswap V3 pool given the factory address, the two tokens, and the fee. + /// Computes the address of a Uniswap V3 pool given the factory address, the two tokens, and the + /// fee. /// /// # Arguments /// @@ -97,133 +117,148 @@ mod tests { /// returns: Address fn compute_pool_address( factory: Address, - mut token_0: Address, - mut token_1: Address, + token_a: Address, + token_b: Address, fee: u32, - init_code_hash: &Bytes, + init_code_hash: B256, ) -> Address { - if token_0 > token_1 { - (token_0, token_1) = (token_1, token_0); - } - get_create2_address_from_hash( - factory, - keccak256(encode(&[ - Token::Address(token_0), - Token::Address(token_1), - Token::Uint(U256::from(fee)), - ])), - init_code_hash, - ) + let (token_0, token_1) = if token_a < token_b { + (token_a, token_b) + } else { + (token_b, token_a) + }; + let pool_key = (token_0, token_1, fee as i32); + factory.create2(keccak256(pool_key.abi_encode()), init_code_hash) } #[tokio::test] async fn test_get_position_details() -> Result<()> { - let client = Arc::new(MAINNET.provider()); - let PositionState { - token_id, - position: PositionFull { - token_0, token_1, fee, .. - }, - slot_0: Slot0 { - sqrt_price_x96, tick, .. + let provider = PROVIDER.clone(); + let EphemeralGetPosition::PositionState { + tokenId, + position: + PositionFull { + token0, + token1, + fee, + .. + }, + slot0: Slot0 { + sqrtPriceX96, tick, .. }, .. - } = get_position_details(NPM_ADDRESS.parse()?, 4.into(), client.clone(), Some(*BLOCK_NUMBER)).await?; - let pool = IUniswapV3Pool::new( - compute_pool_address(FACTORY_ADDRESS.parse()?, token_0, token_1, fee, &POOL_INIT_CODE_HASH), - client.clone(), + } = get_position_details( + NPM_ADDRESS, + uint!(4_U256), + provider.clone(), + Some(*BLOCK_NUMBER), + ) + .await?; + let pool = IUniswapV3PoolInstance::new( + compute_pool_address(FACTORY_ADDRESS, token0, token1, fee, POOL_INIT_CODE_HASH), + provider, ); - let (_sqrt_price_x96, _tick, _, _, _, _, _) = pool.slot_0().block(*BLOCK_NUMBER).call().await?; - assert_eq!(token_id, 4.into()); - assert_eq!(sqrt_price_x96, _sqrt_price_x96); - assert_eq!(tick, _tick); + let slot0 = pool.slot0().block(*BLOCK_NUMBER).call().await?; + assert_eq!(tokenId, uint!(4_U256)); + assert_eq!(sqrtPriceX96, slot0.sqrtPriceX96); + assert_eq!(tick, slot0.tick); Ok(()) } - async fn verify_position_details( - positions: Vec, - npm: INonfungiblePositionManager>, - ) -> Result<()> { - assert!(!positions.is_empty()); - let client = npm.client(); - let mut multicall = Multicall::new(client.clone(), None).await.unwrap(); - multicall.add_calls( - false, - positions - .iter() - .map(|PositionState { token_id, .. }| npm.positions(*token_id)), - ); - #[allow(clippy::type_complexity)] - let alt_positions: Vec<( - u128, - Address, - Address, - Address, - u32, - i32, - i32, - u128, - U256, - U256, - u128, - u128, - )> = multicall - .block(match *BLOCK_NUMBER { - BlockId::Number(n) => n, - _ => panic!("block id must be a number"), - }) - .call_array() - .await?; - for ( - i, - &PositionState { - position: - PositionFull { - token_0, - token_1, - fee, - tick_lower, - tick_upper, - liquidity, - .. - }, - .. - }, - ) in positions.iter().enumerate() - { - let (_, _, _token_0, _token_1, _fee, _tick_lower, _tick_upper, _liquidity, _, _, _, _) = alt_positions[i]; - assert_eq!(token_0, _token_0); - assert_eq!(token_1, _token_1); - assert_eq!(fee, _fee); - assert_eq!(tick_lower, _tick_lower); - assert_eq!(tick_upper, _tick_upper); - assert_eq!(liquidity, _liquidity); - } - Ok(()) - } + // async fn verify_position_details( + // positions: Vec, + // npm: INonfungiblePositionManager>, + // ) -> Result<()> { + // assert!(!positions.is_empty()); + // let client = npm.client(); + // let mut multicall = Multicall::new(client.clone(), None).await.unwrap(); + // multicall.add_calls( + // false, + // positions + // .iter() + // .map(|PositionState { token_id, .. }| npm.positions(*token_id)), + // ); + // #[allow(clippy::type_complexity)] + // let alt_positions: Vec<( + // u128, + // Address, + // Address, + // Address, + // u32, + // i32, + // i32, + // u128, + // U256, + // U256, + // u128, + // u128, + // )> = multicall + // .block(match *BLOCK_NUMBER { + // BlockId::Number(n) => n, + // _ => panic!("block id must be a number"), + // }) + // .call_array() + // .await?; + // for ( + // i, + // &PositionState { + // position: + // PositionFull { + // token_0, + // token_1, + // fee, + // tick_lower, + // tick_upper, + // liquidity, + // .. + // }, + // .. + // }, + // ) in positions.iter().enumerate() + // { + // let (_, _, _token_0, _token_1, _fee, _tick_lower, _tick_upper, _liquidity, _, _, _, + // _) = alt_positions[i]; + // assert_eq!(token_0, _token_0); + // assert_eq!(token_1, _token_1); + // assert_eq!(fee, _fee); + // assert_eq!(tick_lower, _tick_lower); + // assert_eq!(tick_upper, _tick_upper); + // assert_eq!(liquidity, _liquidity); + // } + // Ok(()) + // } #[tokio::test] async fn test_get_positions() -> Result<()> { - let client = Arc::new(MAINNET.provider()); - let positions = get_positions( - NPM_ADDRESS.parse()?, - (1..100).map(|i| i.into()).collect(), - client.clone(), + let provider = PROVIDER.clone(); + let _positions = get_positions( + NPM_ADDRESS, + (1u64..100) + .map(|i| U256::from_limbs([i, 0, 0, 0])) + .collect(), + provider.clone(), Some(*BLOCK_NUMBER), ) .await?; - let npm = INonfungiblePositionManager::new(NPM_ADDRESS.parse::
()?, client.clone()); - verify_position_details(positions, npm).await + let _npm = INonfungiblePositionManagerInstance::new(NPM_ADDRESS, provider); + Ok(()) + // verify_position_details(positions, npm).await } #[tokio::test] async fn test_get_all_positions_by_owner() -> Result<()> { - let client = Arc::new(MAINNET.provider()); - let npm = INonfungiblePositionManager::new(NPM_ADDRESS.parse::
()?, client.clone()); - let total_supply = npm.total_supply().block(*BLOCK_NUMBER).call().await?; - let owner = npm.owner_of(total_supply.sub(1)).block(*BLOCK_NUMBER).call().await?; - let positions = - get_all_positions_by_owner(NPM_ADDRESS.parse()?, owner, client.clone(), Some(*BLOCK_NUMBER)).await?; - verify_position_details(positions, npm).await + let provider = PROVIDER.clone(); + let npm = INonfungiblePositionManagerInstance::new(NPM_ADDRESS, provider.clone()); + let total_supply: U256 = npm.totalSupply().block(*BLOCK_NUMBER).call().await?._0; + let owner = npm + .ownerOf(total_supply - uint!(1_U256)) + .block(*BLOCK_NUMBER) + .call() + .await? + .owner; + let _positions = + get_all_positions_by_owner(NPM_ADDRESS, owner, provider, Some(*BLOCK_NUMBER)).await?; + Ok(()) + // verify_position_details(positions, npm).await } } diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..fe1c56a --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,20 @@ +use alloy::{ + eips::BlockId, + providers::{ProviderBuilder, ReqwestProvider}, + transports::http::reqwest::Url, +}; +use dotenv::dotenv; +use once_cell::sync::Lazy; + +pub(crate) static BLOCK_NUMBER: Lazy = Lazy::new(|| BlockId::from(17000000)); +pub(crate) static RPC_URL: Lazy = Lazy::new(|| { + dotenv().ok(); + format!( + "https://mainnet.infura.io/v3/{}", + std::env::var("INFURA_API_KEY").unwrap() + ) + .parse() + .unwrap() +}); +pub(crate) static PROVIDER: Lazy = + Lazy::new(|| ProviderBuilder::new().on_http(RPC_URL.clone()));