Skip to content

Commit

Permalink
Test all lens callers
Browse files Browse the repository at this point in the history
  • Loading branch information
shuhuiluo committed Dec 17, 2023
1 parent 315bc47 commit e3467d0
Show file tree
Hide file tree
Showing 6 changed files with 3,417 additions and 3,231 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aperture-lens"
version = "0.1.4"
version = "0.2.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/Aperture-Finance/Aperture-Lens"
Expand All @@ -11,4 +11,4 @@ dotenv = "0.15.0"
ethers = { version = "2.0.10", features = ["default"] }
futures = "0.3.29"
serde = { version = "1.0.192", features = ["derive"] }
tokio = { version = "1.34.0", features = ["full"] }
tokio = { version = "1.35.0", features = ["full"] }
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aperture-lens",
"version": "0.1.5",
"version": "0.2.0",
"description": "Contains ephemeral lens contracts that can be called without deployment and their interfaces in various Web3 libraries.",
"author": "Aperture Finance <[email protected]>",
"license": "Apache-2.0",
Expand Down Expand Up @@ -39,7 +39,7 @@
"clean": "tsc --build --clean && forge clean",
"compile": "forge build",
"test": "forge test",
"test:hardhat": "npx hardhat test",
"test:hardhat": "hardhat test",
"snapshot": "forge snapshot",
"snapshot:diff": "forge snapshot --diff",
"prettier": "prettier -c {contracts,test}/**/*.sol ./**/*.ts",
Expand All @@ -50,10 +50,10 @@
"@aperture_finance/uni-v3-lib": "^1.2.0",
"@openzeppelin/contracts": "^5.0.1",
"ethers": "5.7.2",
"viem": "^1.20.0"
"viem": "^1.20.1"
},
"devDependencies": {
"@aperture_finance/uniswap-v3-automation-sdk": "^1.8.5",
"@aperture_finance/uniswap-v3-automation-sdk": "^1.9.1",
"@ethersproject/abi": "5.7.0",
"@ethersproject/providers": "5.7.2",
"@nomicfoundation/hardhat-foundry": "^1.1.1",
Expand All @@ -62,13 +62,13 @@
"@typechain/hardhat": "^7.0.0",
"@types/chai": "^4.3.11",
"@types/mocha": "^10.0.6",
"@types/node": "^20.10.0",
"@types/node": "^20.10.4",
"chai": "^4.3.10",
"hardhat": "^2.19.1",
"hardhat": "^2.19.2",
"mocha": "^10.2.0",
"prettier": "^3.1.0",
"prettier": "^3.1.1",
"prettier-plugin-solidity": "^1.2.0",
"ts-node": "^10.9.1",
"ts-node": "^10.9.2",
"typechain": "^8.3.2",
"typescript": "^5.3.3"
},
Expand Down
205 changes: 202 additions & 3 deletions src/pool_lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub async fn get_position_details<M: Middleware>(

pub async fn get_positions<M: Middleware>(
npm: Address,
token_ids: U256,
token_ids: Vec<U256>,
client: Arc<M>,
block_id: Option<BlockId>,
) -> Result<Vec<PositionState>, ContractError<M>> {
Expand Down Expand Up @@ -135,13 +135,24 @@ pub async fn get_positions_slots<M: Middleware>(
#[cfg(test)]
mod tests {
use super::*;
use crate::bindings::{
i_nonfungible_position_manager::INonfungiblePositionManager,
i_uniswap_v3_pool::IUniswapV3Pool,
shared_types::{PositionFull, Slot0},
};
use anyhow::Result;
use dotenv::dotenv;
use ethers::contract::Lazy;
use ethers::{
abi::{encode, Token},
utils::{get_create2_address_from_hash, keccak256},
};
use futures::future::join_all;
use std::sync::Arc;
use std::{ops::Sub, str::FromStr};

const POOL_ADDRESS: &str = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640";
const NPM_ADDRESS: &str = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88";
static POOL_INIT_CODE_HASH: Lazy<Bytes> =
Lazy::new(|| Bytes::from_str("0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54").unwrap());
static BLOCK_NUMBER: Lazy<BlockId> = Lazy::new(|| BlockId::from(17000000));
static RPC_URL: Lazy<String> = Lazy::new(|| {
dotenv().ok();
Expand All @@ -155,7 +166,195 @@ mod tests {
Arc::new(Provider::<Http>::connect(&*RPC_URL).await)
}

#[tokio::test]
async fn test_get_populated_ticks_in_range() -> Result<()> {
let client = make_provider().await;
let pool = IUniswapV3Pool::new(POOL_ADDRESS.parse::<Address>()?, client.clone());
let (_, tick_current, _, _, _, _, _) = pool.slot_0().block(BlockId::from(*BLOCK_NUMBER)).call().await?;
let ticks = get_populated_ticks_in_range(
POOL_ADDRESS.parse()?,
tick_current,
tick_current,
client.clone(),
Some(*BLOCK_NUMBER),
)
.await?;
assert!(!ticks.is_empty());
let mut multicall = Multicall::new(client.clone(), None).await?;
multicall.add_calls(false, ticks.iter().map(|&PopulatedTick { tick, .. }| pool.ticks(tick)));
let alt_ticks: Vec<(u128, i128, U256, U256, i64, U256, u32, bool)> = multicall
.block(match *BLOCK_NUMBER {
BlockId::Number(n) => n,
_ => panic!("block id must be a number"),
})
.call_array()
.await?;
for (
i,
&PopulatedTick {
liquidity_gross,
liquidity_net,
..
},
) in ticks.iter().enumerate()
{
let (_liquidity_gross, _liquidity_net, _, _, _, _, _, _) = alt_ticks[i];
assert_eq!(liquidity_gross, _liquidity_gross);
assert_eq!(liquidity_net, _liquidity_net);
}
Ok(())
}

/// Computes the address of a Uniswap V3 pool given the factory address, the two tokens, and the fee.
///
/// # Arguments
///
/// * `factory`: The Uniswap V3 factory address
/// * `token_0`: The first token address
/// * `token_1`: The second token address
/// * `fee`: The fee tier
/// * `init_code_hash`: The init code hash of the pool
///
/// returns: Address
fn compute_pool_address(
factory: Address,
mut token_0: Address,
mut token_1: Address,
fee: u32,
init_code_hash: &Bytes,
) -> Address {
if token_0 > token_1 {
(token_0, token_1) = (token_1, token_0);
}
get_create2_address_from_hash(
factory,
keccak256(encode(&vec![
Token::Address(token_0),
Token::Address(token_1),
Token::Uint(U256::from(fee)),
])),
init_code_hash,
)
}

#[tokio::test]
async fn test_get_position_details() -> Result<()> {
let client = make_provider().await;
let PositionState {
token_id,
position: PositionFull {
token_0, token_1, fee, ..
},
slot_0: Slot0 {
sqrt_price_x96, tick, ..
},
..
} = get_position_details(NPM_ADDRESS.parse()?, 4.into(), client.clone(), Some(*BLOCK_NUMBER)).await?;
let pool = IUniswapV3Pool::new(
compute_pool_address(
"0x1F98431c8aD98523631AE4a59f267346ea31F984".parse()?,
token_0,
token_1,
fee,
&POOL_INIT_CODE_HASH,
),
client.clone(),
);
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);
Ok(())
}

async fn verify_position_details(
positions: Vec<PositionState>,
npm: INonfungiblePositionManager<Provider<Http>>,
) -> 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)),
);
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 = make_provider().await;
let positions = get_positions(
NPM_ADDRESS.parse()?,
(1..100).map(|i| i.into()).collect(),
client.clone(),
Some(*BLOCK_NUMBER),
)
.await?;
let npm = INonfungiblePositionManager::new(NPM_ADDRESS.parse::<Address>()?, client.clone());
verify_position_details(positions, npm).await
}

#[tokio::test]
async fn test_get_all_positions_by_owner() -> Result<()> {
let client = make_provider().await;
let npm = INonfungiblePositionManager::new(NPM_ADDRESS.parse::<Address>()?, 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
}

async fn verify_slots(slots: Vec<Slot>, client: Arc<Provider<Http>>) {
assert!(!slots.is_empty());
let client = client.as_ref();
let futures = slots[0..4].iter().map(|slot| async move {
let data = client
Expand Down
27 changes: 14 additions & 13 deletions test/hardhat/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getPositions,
getAllPositionsByOwner,
} from "../../src/viem";
import { EphemeralGetPositions__factory, IUniswapV3Pool__factory } from "../../typechain";
import { EphemeralGetPositions__factory, EphemeralPoolSlots__factory, IUniswapV3Pool__factory } from "../../typechain";

dotenvConfig();

Expand Down Expand Up @@ -105,29 +105,30 @@ describe("Pool lens test", () => {
await verifyPositionDetails(posArr);
});

it("Test getting static storage slots", async () => {
const slots = await getStaticSlots(pool, publicClient, blockNumber);
async function verifySlots(slots: ContractFunctionResult<typeof EphemeralPoolSlots__factory.abi>) {
expect(slots.some(({ data }) => data > 0)).to.be.true;
const address = pool;
const altSlots = await Promise.all([
publicClient.getStorageAt({ address, slot: toHex(0), blockNumber }),
publicClient.getStorageAt({ address, slot: toHex(1), blockNumber }),
publicClient.getStorageAt({ address, slot: toHex(2), blockNumber }),
publicClient.getStorageAt({ address, slot: toHex(3), blockNumber }),
]);
for (let i = 0; i < 4; i++) {
const altSlots = await Promise.all(
slots.slice(0, 4).map(({ slot }) => publicClient.getStorageAt({ address, slot: toHex(slot), blockNumber })),
);
for (let i = 0; i < altSlots.length; i++) {
expect(slots[i].data).to.be.eq(BigInt(altSlots[i]!));
}
}

it("Test getting static storage slots", async () => {
const slots = await getStaticSlots(pool, publicClient, blockNumber);
await verifySlots(slots);
});

it("Test getting populated ticks slots", async () => {
const slots = await getTicksSlots(pool, TickMath.MIN_TICK, TickMath.MAX_TICK, publicClient, blockNumber);
expect(slots.some(({ data }) => data > 0)).to.be.true;
await verifySlots(slots);
});

it("Test getting tick bitmap slots", async () => {
const slots = await getTickBitmapSlots(pool, publicClient, blockNumber);
expect(slots.some(({ data }) => data > 0)).to.be.true;
await verifySlots(slots);
});

it("Test getting positions mapping slots", async () => {
Expand All @@ -144,6 +145,6 @@ describe("Pool lens test", () => {
tickUpper: tickUpper!,
}));
const slots = await getPositionsSlots(pool, positions, publicClient, blockNumber);
expect(slots.some(({ data }) => data > 0)).to.be.true;
await verifySlots(slots);
});
});
Loading

0 comments on commit e3467d0

Please sign in to comment.