diff --git a/.gas-snapshot b/.gas-snapshot index f34e310..b07e9d3 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,26 +1,13 @@ -PCSV3PoolLensTest:testFuzz_GetPositions(int24,int24) (runs: 17, μ: 1392200, ~: 1437447) -PCSV3PoolLensTest:test_GetPopulatedTicksInRange() (gas: 524480) -PCSV3PoolLensTest:test_GetPositions() (gas: 1348279) -PCSV3PoolLensTest:test_GetSlots() (gas: 151189) -PCSV3PoolLensTest:test_GetTickBitmap() (gas: 3370484) -PCSV3PositionLensTest:testFuzz_GetPosition(uint256) (runs: 17, μ: 274996, ~: 275218) -PCSV3PositionLensTest:test_AllPositions() (gas: 19719373) -PCSV3PositionLensTest:test_GetFeesOwed() (gas: 2791103) -PCSV3PositionLensTest:test_GetPositions() (gas: 1176049) -PCSV3PositionLensTest:test_GetTotalAmounts() (gas: 2794460) -PCSV3StorageLensTest:testFuzz_extsload(bytes32) (runs: 257, μ: 33810, ~: 33944) -PCSV3StorageLensTest:test_extsload() (gas: 66675) -PCSV3TickLensTest:test_GetPopulatedTicksInRange() (gas: 793601) -PoolLensTest:testFuzz_GetPositions(int24,int24) (runs: 17, μ: 1389477, ~: 1419421) -PoolLensTest:test_GetPopulatedTicksInRange() (gas: 4524074) -PoolLensTest:test_GetPositions() (gas: 1132031) -PoolLensTest:test_GetSlots() (gas: 3695636) -PoolLensTest:test_GetTickBitmap() (gas: 3385051) -PositionLensTest:testFuzz_GetPosition(uint256) (runs: 17, μ: 271318, ~: 272509) -PositionLensTest:test_AllPositions() (gas: 1137940) -PositionLensTest:test_GetFeesOwed() (gas: 2444915) -PositionLensTest:test_GetPositions() (gas: 940131) -PositionLensTest:test_GetTotalAmounts() (gas: 2456667) -StorageLensTest:testFuzz_extsload(bytes32) (runs: 17, μ: 33820, ~: 33944) -StorageLensTest:test_extsload() (gas: 66675) -TickLensTest:test_GetPopulatedTicksInRange() (gas: 11472791) \ No newline at end of file +PoolLensTest:testFuzz_GetPositions(int24,int24) (runs: 22, μ: 1364083, ~: 1404255) +PoolLensTest:test_GetPopulatedTicksInRange() (gas: 4544800) +PoolLensTest:test_GetPositions() (gas: 1132120) +PoolLensTest:test_GetSlots() (gas: 3710586) +PoolLensTest:test_GetTickBitmap() (gas: 3399608) +PositionLensTest:testFuzz_GetPosition(uint256) (runs: 22, μ: 270296, ~: 272574) +PositionLensTest:test_AllPositions() (gas: 1140527) +PositionLensTest:test_GetFeesOwed() (gas: 2444865) +PositionLensTest:test_GetPositions() (gas: 940419) +PositionLensTest:test_GetTotalAmounts() (gas: 2456617) +StorageLensTest:testFuzz_extsload(bytes32) (runs: 22, μ: 33735, ~: 33755) +StorageLensTest:test_extsload() (gas: 66705) +TickLensTest:test_GetPopulatedTicksInRange() (gas: 11486985) \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 1043929..ac813eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,21 @@ [package] name = "uniswap-lens" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["Shuhui Luo "] -description = "Contains ephemeral lens contracts that can be called without deployment and their Rust interfaces." +description = "A library for querying Uniswap V3 using ephemeral lens contracts." license = "Apache-2.0" readme = "README.md" repository = "https://github.com/shuhuiluo/uniswap-lens-rs" -keywords = ["alloy", "ethereum", "solidity", "rust", "uniswap"] +keywords = ["alloy", "ethereum", "solidity", "uniswap"] include = ["src/**/*.rs"] [dependencies] -alloy = { version = "0.2.0", features = ["contract", "rpc-types", "transports"] } +alloy = { version = "0.3.0", features = ["contract", "rpc-types", "transports"] } anyhow = "1" [dev-dependencies] -alloy = { version = "0.2.0", features = ["transport-http"] } +alloy = { version = "0.3.0", features = ["transport-http"] } dotenv = "0.15.0" futures = "0.3" once_cell = "1.19" diff --git a/README.md b/README.md index 196c967..58f2adf 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,10 @@ [![npm version](https://img.shields.io/npm/v/aperture-lens/latest.svg)](https://www.npmjs.com/package/aperture-lens/v/latest) [![crates.io](https://img.shields.io/crates/v/uniswap-lens.svg)](https://crates.io/crates/uniswap-lens) -Contains ephemeral lens contracts that can be called without deployment and their interfaces in various Web3 libraries. +A library for querying Uniswap V3 using ephemeral lens contracts. + +## Features + +- Lens contracts in Solidity using the revert-in-constructor pattern +- Rust SDK for querying lens contracts using [alloy-rs](https://github.com/alloy-rs) +- TypeScript SDK for querying lens contracts using viem diff --git a/package.json b/package.json index 2e35bd3..53b31ca 100644 --- a/package.json +++ b/package.json @@ -80,5 +80,6 @@ ], "endOfLine": "lf", "printWidth": 120 - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/lib.rs b/src/lib.rs index 16e87a5..b6efbcb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,12 @@ //! # uniswap-lens +//! +//! A library for querying Uniswap V3 using ephemeral lens contracts. #![warn( missing_copy_implementations, missing_debug_implementations, unreachable_pub, clippy::missing_const_for_fn, + clippy::redundant_clone, rustdoc::all )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] diff --git a/src/pool_lens.rs b/src/pool_lens.rs index f4848e0..021148d 100644 --- a/src/pool_lens.rs +++ b/src/pool_lens.rs @@ -1,3 +1,7 @@ +//! ## Pool Lens +//! +//! The pool lens module provides functions to fetch pool details using ephemeral contracts. + use crate::{ bindings::{ ephemeralgetpopulatedticksinrange::EphemeralGetPopulatedTicksInRange::{ @@ -18,17 +22,30 @@ use crate::{ use alloy::{ contract::Error, eips::BlockId, - primitives::{Address, Bytes}, + primitives::{aliases::I24, Address, Bytes}, providers::Provider, sol_types::SolCall, transports::{Transport, TransportError}, }; use anyhow::Result; +/// Get the populated ticks in a tick range. +/// +/// ## Arguments +/// +/// * `pool`: The address of a V3 pool +/// * `tick_lower`: The lower tick boundary +/// * `tick_upper`: The upper tick boundary +/// * `provider`: The alloy provider +/// * `block_id`: Optional block number to query +/// +/// ## Returns +/// +/// A vector of populated ticks within the range pub async fn get_populated_ticks_in_range( pool: Address, - tick_lower: i32, - tick_upper: i32, + tick_lower: I24, + tick_upper: I24, provider: P, block_id: Option, ) -> Result> @@ -40,7 +57,10 @@ where provider, pool, tick_lower, tick_upper, ); match call_ephemeral_contract!(deploy_builder, getPopulatedTicksInRangeCall, block_id) { - Ok(getPopulatedTicksInRangeReturn { populatedTicks }) => Ok(populatedTicks), + Ok(getPopulatedTicksInRangeReturn { populatedTicks }) => Ok(populatedTicks + .into_iter() + .filter(|PopulatedTick { tick, .. }| *tick >= tick_lower && *tick <= tick_upper) + .collect()), Err(err) => Err(err.into()), } } @@ -55,6 +75,17 @@ macro_rules! get_pool_storage { }; } +/// Get the static storage slots of a pool. +/// +/// ## Arguments +/// +/// * `pool`: The address of a V3 pool +/// * `provider`: The alloy provider +/// * `block_id`: Optional block number to query +/// +/// ## Returns +/// +/// A vector of slots containing the storage data pub async fn get_static_slots( pool: Address, provider: P, @@ -70,10 +101,23 @@ where ) } +/// Get the storage slots in the `ticks` mapping between `tick_lower` and `tick_upper`. +/// +/// ## Arguments +/// +/// * `pool`: The address of a V3 pool +/// * `tick_lower`: The lower tick boundary +/// * `tick_upper`: The upper tick boundary +/// * `provider`: The alloy provider +/// * `block_id`: Optional block number to query +/// +/// ## Returns +/// +/// A vector of slots containing the storage data pub async fn get_ticks_slots( pool: Address, - tick_lower: i32, - tick_upper: i32, + tick_lower: I24, + tick_upper: I24, provider: P, block_id: Option, ) -> Result> @@ -86,6 +130,17 @@ where block_id ) } +/// Get the storage slots in the `tickBitmap` mapping. +/// +/// ## Arguments +/// +/// * `pool`: The address of a V3 pool +/// * `provider`: The alloy provider +/// * `block_id`: Optional block number to query +/// +/// ## Returns +/// +/// A vector of slots containing the storage data pub async fn get_tick_bitmap_slots( pool: Address, provider: P, @@ -100,6 +155,18 @@ where block_id ) } +/// Get the storage slots in the `positions` mapping. +/// +/// ## Arguments +/// +/// * `pool`: The address of a V3 pool +/// * `positions`: A vector of position keys +/// * `provider`: The alloy provider +/// * `block_id`: Optional block number to query +/// +/// ## Returns +/// +/// A vector of slots containing the storage data pub async fn get_positions_slots( pool: Address, positions: Vec, @@ -133,10 +200,11 @@ mod tests { let provider = PROVIDER.clone(); let pool = IUniswapV3PoolInstance::new(POOL_ADDRESS, provider.clone()); let tick_current = pool.slot0().block(*BLOCK_NUMBER).call().await?.tick; + let tick_spacing = pool.tickSpacing().block(*BLOCK_NUMBER).call().await?._0; let ticks = get_populated_ticks_in_range( POOL_ADDRESS, tick_current, - tick_current, + tick_current + (tick_spacing << 8), provider, Some(*BLOCK_NUMBER), ) diff --git a/src/position_lens.rs b/src/position_lens.rs index 51cc3fc..c5c610b 100644 --- a/src/position_lens.rs +++ b/src/position_lens.rs @@ -1,3 +1,7 @@ +//! ## Position Lens +//! +//! The position lens module provides functions to fetch position details using ephemeral contracts. + use crate::{ bindings::{ ephemeralallpositionsbyowner::{ @@ -31,6 +35,18 @@ use alloy::{ }; use anyhow::Result; +/// Get the details of a position given the token ID. +/// +/// ## Arguments +/// +/// * `npm`: The address of the non-fungible position manager +/// * `token_id`: The token ID of the position +/// * `provider`: The alloy provider +/// * `block_id`: Optional block number to query +/// +/// ## Returns +/// +/// The position details pub async fn get_position_details( npm: Address, token_id: U256, @@ -48,6 +64,18 @@ where } } +/// Get the details of multiple positions given the token IDs. +/// +/// ## Arguments +/// +/// * `npm`: The address of the non-fungible position manager +/// * `token_ids`: The token IDs of the positions +/// * `provider`: The alloy provider +/// * `block_id`: Optional block number to query +/// +/// ## Returns +/// +/// The array of position details pub async fn get_positions( npm: Address, token_ids: Vec, @@ -65,6 +93,18 @@ where } } +/// Get all positions owned by an address. +/// +/// ## Arguments +/// +/// * `npm`: The address of the non-fungible position manager +/// * `owner`: The address of the owner +/// * `provider`: The alloy provider +/// * `block_id`: Optional block number to query +/// +/// ## Returns +/// +/// The array of position details pub async fn get_all_positions_by_owner( npm: Address, owner: Address, @@ -94,7 +134,7 @@ mod tests { tests::*, }; use alloy::{ - primitives::{address, b256, keccak256, uint, B256}, + primitives::{address, aliases::U24, b256, keccak256, uint, B256}, sol_types::SolValue, }; @@ -119,7 +159,7 @@ mod tests { factory: Address, token_a: Address, token_b: Address, - fee: u32, + fee: U24, init_code_hash: B256, ) -> Address { let (token_0, token_1) = if token_a < token_b { @@ -127,7 +167,7 @@ mod tests { } else { (token_b, token_a) }; - let pool_key = (token_0, token_1, fee as i32); + let pool_key = (token_0, token_1, fee); factory.create2(keccak256(pool_key.abi_encode()), init_code_hash) } diff --git a/src/storage_lens.rs b/src/storage_lens.rs index 3316167..69f08e9 100644 --- a/src/storage_lens.rs +++ b/src/storage_lens.rs @@ -1,3 +1,8 @@ +//! ## Storage Lens +//! +//! The storage lens module provides a function to batch `eth_getStorageAt` RPC calls in a single +//! `eth_call` by overriding the target contract's deployed bytecode with `EphemeralStorageLens`. + use crate::bindings::ephemeralstoragelens::{ EphemeralStorageLens, EphemeralStorageLens::EphemeralStorageLensInstance, }; diff --git a/test/foundry/PoolLens.t.sol b/test/foundry/PoolLens.t.sol index a526e80..be49df7 100644 --- a/test/foundry/PoolLens.t.sol +++ b/test/foundry/PoolLens.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Factory.sol"; import "contracts/EphemeralPoolSlots.sol"; import "contracts/EphemeralPoolTicks.sol"; import "contracts/EphemeralPoolTickBitmap.sol"; @@ -69,71 +68,3 @@ contract PoolLensTest is BaseTest, PoolUtils { } } } - -contract PCSV3PoolLensTest is BaseTest, PoolUtils { - function setUp() public override { - chainId = 56; - dex = DEX.PancakeSwapV3; - super.setUp(); - } - - function test_GetSlots() public { - try new EphemeralPCSV3PoolSlots(V3PoolCallee.wrap(pool)) {} catch (bytes memory returnData) { - Slot[] memory slots = abi.decode(returnData, (Slot[])); - uint256 length = slots.length; - for (uint256 i; i < length; ++i) { - assertEq(bytes32(slots[i].data), vm.load(pool, bytes32(slots[i].slot))); - } - } - } - - function test_GetPopulatedTicksInRange() public { - int24 tick = currentTick(); - try new EphemeralPCSV3PoolTicks(V3PoolCallee.wrap(pool), tick, tick) {} catch (bytes memory returnData) { - Slot[] memory slots = abi.decode(returnData, (Slot[])); - uint256 length = slots.length; - for (uint256 i; i < length; ++i) { - assertEq(bytes32(slots[i].data), vm.load(pool, bytes32(slots[i].slot))); - } - } - } - - function test_GetTickBitmap() public { - try new EphemeralPCSV3PoolTickBitmap(V3PoolCallee.wrap(pool)) {} catch (bytes memory returnData) { - Slot[] memory slots = abi.decode(returnData, (Slot[])); - uint256 length = slots.length; - for (uint256 i; i < length; ++i) { - assertEq(bytes32(slots[i].data), vm.load(pool, bytes32(slots[i].slot))); - } - } - } - - function test_GetPositions() public { - int24 tick = currentTick(); - testFuzz_GetPositions(tick - tickSpacing, tick + tickSpacing); - } - - /// forge-config: default.fuzz.runs = 16 - /// forge-config: ci.fuzz.runs = 16 - function testFuzz_GetPositions(int24 tickLower, int24 tickUpper) public { - (tickLower, tickUpper) = prepTicks(tickLower, tickUpper); - uint256 amount0Desired = token0Unit; - uint256 amount1Desired = token1Unit; - deal(token0, address(this), type(uint256).max); - deal(token1, address(this), type(uint256).max); - PositionKey[] memory keys = new PositionKey[](3); - for (uint256 i; i < 3; ++i) { - mint(address(this), amount0Desired, amount1Desired, tickLower, tickUpper); - keys[i] = PositionKey(address(this), tickLower, tickUpper); - tickLower -= tickSpacing; - tickUpper += tickSpacing; - } - try new EphemeralPCSV3PoolPositions(V3PoolCallee.wrap(pool), keys) {} catch (bytes memory returnData) { - Slot[] memory slots = abi.decode(returnData, (Slot[])); - uint256 length = slots.length; - for (uint256 i; i < length; ++i) { - assertEq(bytes32(slots[i].data), vm.load(pool, bytes32(slots[i].slot))); - } - } - } -} diff --git a/test/foundry/PositionLens.t.sol b/test/foundry/PositionLens.t.sol index c7d9966..7de1342 100644 --- a/test/foundry/PositionLens.t.sol +++ b/test/foundry/PositionLens.t.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IPCSV3NonfungiblePositionManager} from "@aperture_finance/uni-v3-lib/src/interfaces/INonfungiblePositionManager.sol"; import {PoolAddress} from "@aperture_finance/uni-v3-lib/src/PoolAddress.sol"; -import {PoolAddressPancakeSwapV3} from "@aperture_finance/uni-v3-lib/src/PoolAddressPancakeSwapV3.sol"; -import {IPancakeV3Pool} from "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Pool.sol"; import "contracts/EphemeralAllPositionsByOwner.sol"; import "contracts/EphemeralGetPosition.sol"; import "contracts/EphemeralGetPositions.sol"; @@ -111,9 +108,9 @@ contract PositionLensTest is BaseTest { uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, - uint32 feeProtocol, + uint8 feeProtocol, bool unlocked - ) = IPancakeV3Pool(pool).slot0(); + ) = IUniswapV3Pool(pool).slot0(); assertEq(sqrtPriceX96, pos.slot0.sqrtPriceX96, "sqrtPriceX96"); assertEq(tick, pos.slot0.tick, "tick"); assertEq(observationIndex, pos.slot0.observationIndex, "observationIndex"); @@ -165,18 +162,3 @@ contract PositionLensTest is BaseTest { } } } - -contract PCSV3PositionLensTest is PositionLensTest { - function setUp() public override { - chainId = 56; - dex = DEX.PancakeSwapV3; - super.setUp(); - } - - // Trivially override so that the "forge-config" settings are applied. - /// forge-config: default.fuzz.runs = 16 - /// forge-config: ci.fuzz.runs = 16 - function testFuzz_GetPosition(uint256 tokenId) public override { - super.testFuzz_GetPosition(tokenId); - } -} diff --git a/test/foundry/StorageLens.t.sol b/test/foundry/StorageLens.t.sol index 1ca1628..db9f71d 100644 --- a/test/foundry/StorageLens.t.sol +++ b/test/foundry/StorageLens.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Factory.sol"; import "contracts/EphemeralStorageLens.sol"; import "./Base.t.sol"; @@ -32,11 +31,3 @@ contract StorageLensTest is BaseTest { assertEq(values[0], vm.load(pool, slot)); } } - -contract PCSV3StorageLensTest is StorageLensTest { - function setUp() public override { - chainId = 56; - dex = DEX.PancakeSwapV3; - super.setUp(); - } -} diff --git a/test/foundry/TickLens.t.sol b/test/foundry/TickLens.t.sol index 88923d7..e87c3ed 100644 --- a/test/foundry/TickLens.t.sol +++ b/test/foundry/TickLens.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Factory.sol"; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "contracts/EphemeralGetPopulatedTicksInRange.sol"; import "./Base.t.sol"; @@ -42,11 +41,3 @@ contract TickLensTest is BaseTest, PoolUtils { } } } - -contract PCSV3TickLensTest is TickLensTest { - function setUp() public override { - chainId = 56; - dex = DEX.PancakeSwapV3; - super.setUp(); - } -} diff --git a/test/hardhat/pcsv3_test.ts b/test/hardhat/pcsv3_test.ts deleted file mode 100644 index 67f2e26..0000000 --- a/test/hardhat/pcsv3_test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { TickMath } from "@uniswap/v3-sdk"; -import { expect } from "chai"; -import { ContractFunctionReturnType, createPublicClient, getContract, http, toHex } from "viem"; -import { bsc } from "viem/chains"; -import { - AutomatedMarketMakerEnum, - getAllPositionsByOwner, - getPopulatedTicksInRange, - getPositionDetails, - getPositions, - getPositionsSlots, - getStaticSlots, - getStorageAt, - getTickBitmapSlots, - getTicksSlots, -} from "../../src/viem"; -import { - EphemeralGetPositions__factory, - EphemeralPoolSlots__factory, - INonfungiblePositionManager__factory, - IPancakeV3Pool__factory, -} from "../../typechain"; -import { computePoolAddress } from "@pancakeswap/v3-sdk"; -import { Token } from "@pancakeswap/sdk"; - -const AMM = AutomatedMarketMakerEnum.enum.PANCAKESWAP_V3; -const PCSV3_NPM = "0x46A15B0b27311cedF172AB29E4f4766fbE7F4364"; -const PCSV3_POOL_DEPLOYER = "0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9"; -const USDT_ADDRESS = "0x55d398326f99059fF775485246999027B3197955"; -const WBNB_ADDRESS = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"; - -describe("Pool lens test with PCSV3 on BSC", () => { - const publicClient = createPublicClient({ - chain: bsc, - transport: http(`https://bsc-rpc.publicnode.com`), - batch: { - multicall: true, - }, - }); - var blockNumber: bigint; - const pool = computePoolAddress({ - deployerAddress: PCSV3_POOL_DEPLOYER, - tokenA: new Token(bsc.id, USDT_ADDRESS, 6, "USDT"), - tokenB: new Token(bsc.id, WBNB_ADDRESS, 18, "WBNB"), - fee: 500, - }); - const poolContract = getContract({ - address: pool, - abi: IPancakeV3Pool__factory.abi, - client: publicClient, - }); - const npm = getContract({ - address: PCSV3_NPM, - abi: INonfungiblePositionManager__factory.abi, - client: publicClient, - }); - - before(async () => { - blockNumber = (await publicClient.getBlockNumber()) - 64n; - console.log(`Running PCSV3 tests on the BNB chain at block number ${blockNumber}...`); - }); - - it("Test extsload", async () => { - const slots = await getStorageAt( - pool, - Array.from({ length: 4 }, (_, i) => toHex(i, { size: 32 })), - publicClient, - blockNumber, - ); - await Promise.all( - slots.map(async (slot, i) => { - const _slot = await publicClient.getStorageAt({ address: pool, slot: toHex(i), blockNumber }); - expect(slot).to.be.eq(_slot); - }), - ); - }); - - it("Test getting populated ticks", async () => { - const [, tickCurrent] = await poolContract.read.slot0({ blockNumber }); - const ticks = await getPopulatedTicksInRange(pool, tickCurrent, tickCurrent, publicClient, blockNumber); - await Promise.all( - ticks.map(async ({ tick, liquidityGross, liquidityNet }) => { - const [_liquidityGross, _liquidityNet] = await poolContract.read.ticks([tick], { blockNumber }); - expect(liquidityGross).to.be.eq(_liquidityGross); - expect(liquidityNet).to.be.eq(_liquidityNet); - }), - ); - }); - - it("Test getting position details", async () => { - const { - tokenId, - position: { token0, token1, fee }, - slot0: { sqrtPriceX96, tick }, - } = await getPositionDetails(PCSV3_NPM, 4n, publicClient, blockNumber); - expect(tokenId).to.be.eq(4n); - const poolAddress = computePoolAddress({ - deployerAddress: PCSV3_POOL_DEPLOYER, - tokenA: new Token(bsc.id, token0, 0, "NOT_USED"), - tokenB: new Token(bsc.id, token1, 0, "NOT_USED"), - fee, - }); - const [_sqrtPriceX96, _tick] = await getContract({ - address: poolAddress, - abi: IPancakeV3Pool__factory.abi, - client: publicClient, - }).read.slot0({ blockNumber }); - expect(sqrtPriceX96).to.be.eq(_sqrtPriceX96); - expect(tick).to.be.eq(_tick); - }); - - async function verifyPositionDetails(posArr: ContractFunctionReturnType) { - await Promise.all( - posArr.map(async ({ tokenId, position }) => { - const [, , token0, token1, fee, tickLower, tickUpper, liquidity] = await npm.read.positions([tokenId], { - blockNumber, - }); - expect(position.token0).to.be.eq(token0); - expect(position.token1).to.be.eq(token1); - expect(position.fee).to.be.eq(fee); - expect(position.tickLower).to.be.eq(tickLower); - expect(position.tickUpper).to.be.eq(tickUpper); - expect(position.liquidity).to.be.eq(liquidity); - }), - ); - } - - it("Test getting position array", async () => { - const posArr = await getPositions( - PCSV3_NPM, - Array.from({ length: 100 }, (_, i) => BigInt(i + 1)), - publicClient, - blockNumber, - ); - await verifyPositionDetails(posArr); - }); - - it("Test getting all positions by owner", async () => { - const totalSupply = await npm.read.totalSupply({ blockNumber }); - const tokenId = await npm.read.tokenByIndex([totalSupply - 1n], { blockNumber }); - const owner = await npm.read.ownerOf([tokenId], { blockNumber }); - const posArr = await getAllPositionsByOwner(PCSV3_NPM, owner, publicClient, blockNumber); - await verifyPositionDetails(posArr); - }); - - async function verifySlots(slots: ContractFunctionReturnType) { - expect(slots.some(({ data }) => data > 0)).to.be.true; - const address = pool; - 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(AMM, pool, publicClient, blockNumber); - await verifySlots(slots); - }); - - it("Test getting populated ticks slots", async () => { - const slots = await getTicksSlots(AMM, pool, TickMath.MIN_TICK, TickMath.MAX_TICK, publicClient, blockNumber); - await verifySlots(slots); - }); - - it("Test getting tick bitmap slots", async () => { - const slots = await getTickBitmapSlots(AMM, pool, publicClient, blockNumber); - await verifySlots(slots); - }); - - it("Test getting positions mapping slots", async () => { - const logs = await poolContract.getEvents.Mint( - {}, - { - fromBlock: blockNumber - 10000n, - toBlock: blockNumber, - }, - ); - const positions = logs.map(({ args: { owner, tickLower, tickUpper } }) => ({ - owner: owner!, - tickLower: tickLower!, - tickUpper: tickUpper!, - })); - const slots = await getPositionsSlots(AMM, pool, positions, publicClient, blockNumber); - await verifySlots(slots); - }); -});