From 65e217d5e92bf07295f36abeb6330d71c1ce9fd0 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Thu, 12 Sep 2024 05:05:23 -0400 Subject: [PATCH] feat: return tick spacing in tick lens (#9) Introduce `tickSpacing` to the function `getPopulatedTicksInRange`. This change updates function calls, return types, and test cases to handle the new `tickSpacing` parameter. Major version bump and dependency updates included. --- Cargo.toml | 8 ++--- .../EphemeralGetPopulatedTicksInRange.sol | 12 ++++--- src/caller.rs | 2 +- src/pool_lens.rs | 36 ++++++++++++------- test/foundry/TickLens.t.sol | 6 +++- test/hardhat/univ3_test.ts | 2 +- 6 files changed, 42 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b5b64a3..ecbadbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniswap-lens" -version = "0.2.1" +version = "0.3.0" edition = "2021" authors = ["Shuhui Luo "] description = "A library for querying Uniswap V3 using ephemeral lens contracts." @@ -11,7 +11,7 @@ keywords = ["alloy", "ethereum", "solidity", "uniswap"] include = ["src/**/*.rs"] [dependencies] -alloy = { version = "0.3.0", features = ["contract", "rpc-types"] } +alloy = { version = "0.3", features = ["contract", "rpc-types"] } anyhow = "1" [features] @@ -19,8 +19,8 @@ default = [] std = ["alloy/std"] [dev-dependencies] -alloy = { version = "0.3.0", features = ["transport-http"] } -dotenv = "0.15.0" +alloy = { version = "0.3", features = ["transport-http"] } +dotenv = "0.15" futures = "0.3" once_cell = "1.19" ruint = "1.12" diff --git a/contracts/EphemeralGetPopulatedTicksInRange.sol b/contracts/EphemeralGetPopulatedTicksInRange.sol index d7b8bda..8469a0c 100644 --- a/contracts/EphemeralGetPopulatedTicksInRange.sol +++ b/contracts/EphemeralGetPopulatedTicksInRange.sol @@ -9,8 +9,12 @@ import "./PoolUtils.sol"; /// revert data, and decoded by `abi.decode(data, (PopulatedTick[]))` contract EphemeralGetPopulatedTicksInRange is PoolUtils { constructor(V3PoolCallee pool, int24 tickLower, int24 tickUpper) payable { - PopulatedTick[] memory populatedTicks = getPopulatedTicksInRange(pool, tickLower, tickUpper); - bytes memory returnData = abi.encode(populatedTicks); + (PopulatedTick[] memory populatedTicks, int24 tickSpacing) = getPopulatedTicksInRange( + pool, + tickLower, + tickUpper + ); + bytes memory returnData = abi.encode(populatedTicks, tickSpacing); assembly ("memory-safe") { revert(add(returnData, 0x20), mload(returnData)) } @@ -25,10 +29,10 @@ contract EphemeralGetPopulatedTicksInRange is PoolUtils { V3PoolCallee pool, int24 tickLower, int24 tickUpper - ) public payable returns (PopulatedTick[] memory populatedTicks) { + ) public payable returns (PopulatedTick[] memory populatedTicks, int24 tickSpacing) { require(tickLower <= tickUpper); // checks that the pool exists - int24 tickSpacing = IUniswapV3Pool(V3PoolCallee.unwrap(pool)).tickSpacing(); + tickSpacing = IUniswapV3Pool(V3PoolCallee.unwrap(pool)).tickSpacing(); (int16 wordPosLower, int16 wordPosUpper) = getWordPositions(tickLower, tickUpper, tickSpacing); unchecked { (uint256[] memory tickBitmap, uint256 count) = getTickBitmapAndCount(pool, wordPosLower, wordPosUpper); diff --git a/src/caller.rs b/src/caller.rs index a2b573b..c2996b4 100644 --- a/src/caller.rs +++ b/src/caller.rs @@ -9,7 +9,7 @@ macro_rules! call_ephemeral_contract { match deploy_builder.call_raw().await { Err(Error::TransportError(err)) => match err { TransportError::ErrorResp(payload) => { - let data: Bytes = payload.try_data_as().unwrap().unwrap(); + let data: Bytes = payload.as_revert_data().unwrap(); Ok(<$call_type>::abi_decode_returns(data.as_ref(), true).unwrap()) } _ => panic!("should be an error response: {:?}", err), diff --git a/src/pool_lens.rs b/src/pool_lens.rs index 4f05c67..cfb4797 100644 --- a/src/pool_lens.rs +++ b/src/pool_lens.rs @@ -4,15 +4,19 @@ use crate::{ bindings::{ - ephemeralgetpopulatedticksinrange::EphemeralGetPopulatedTicksInRange::{ - getPopulatedTicksInRangeCall, getPopulatedTicksInRangeReturn, - EphemeralGetPopulatedTicksInRangeInstance, PopulatedTick, + ephemeralgetpopulatedticksinrange::{ + EphemeralGetPopulatedTicksInRange::{ + getPopulatedTicksInRangeCall, getPopulatedTicksInRangeReturn, + EphemeralGetPopulatedTicksInRangeInstance, + }, + PoolUtils::PopulatedTick, }, - ephemeralpoolpositions::EphemeralPoolPositions::{ - EphemeralPoolPositionsInstance, PositionKey, + ephemeralpoolpositions::{ + EphemeralPoolPositions::EphemeralPoolPositionsInstance, PoolUtils::PositionKey, }, - ephemeralpoolslots::EphemeralPoolSlots::{ - getSlotsCall, getSlotsReturn, EphemeralPoolSlotsInstance, Slot, + ephemeralpoolslots::{ + EphemeralPoolSlots::{getSlotsCall, getSlotsReturn, EphemeralPoolSlotsInstance}, + PoolUtils::Slot, }, ephemeralpooltickbitmap::EphemeralPoolTickBitmap::EphemeralPoolTickBitmapInstance, ephemeralpoolticks::EphemeralPoolTicks::EphemeralPoolTicksInstance, @@ -49,7 +53,7 @@ pub async fn get_populated_ticks_in_range( tick_upper: I24, provider: P, block_id: Option, -) -> Result> +) -> Result<(Vec, I24)> where T: Transport + Clone, P: Provider, @@ -58,10 +62,16 @@ where provider, pool, tick_lower, tick_upper, ); match call_ephemeral_contract!(deploy_builder, getPopulatedTicksInRangeCall, block_id) { - Ok(getPopulatedTicksInRangeReturn { populatedTicks }) => Ok(populatedTicks - .into_iter() - .filter(|PopulatedTick { tick, .. }| *tick >= tick_lower && *tick <= tick_upper) - .collect()), + Ok(getPopulatedTicksInRangeReturn { + populatedTicks, + tickSpacing, + }) => Ok(( + populatedTicks + .into_iter() + .filter(|PopulatedTick { tick, .. }| *tick >= tick_lower && *tick <= tick_upper) + .collect(), + tickSpacing, + )), Err(err) => Err(err.into()), } } @@ -202,7 +212,7 @@ mod tests { 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( + let (ticks, _) = get_populated_ticks_in_range( POOL_ADDRESS, tick_current, tick_current + (tick_spacing << 8), diff --git a/test/foundry/TickLens.t.sol b/test/foundry/TickLens.t.sol index e87c3ed..ae2b505 100644 --- a/test/foundry/TickLens.t.sol +++ b/test/foundry/TickLens.t.sol @@ -35,7 +35,11 @@ contract TickLensTest is BaseTest, PoolUtils { tick + 128 * tickSpacing ) {} catch (bytes memory returnData) { - PopulatedTick[] memory populatedTicks = abi.decode(returnData, (PopulatedTick[])); + (PopulatedTick[] memory populatedTicks, int24 _tickSpacing) = abi.decode( + returnData, + (PopulatedTick[], int24) + ); + assertEq(_tickSpacing, IUniswapV3Pool(pool).tickSpacing(), "tickSpacing"); console2.log("length", populatedTicks.length); verifyTicks(populatedTicks); } diff --git a/test/hardhat/univ3_test.ts b/test/hardhat/univ3_test.ts index c17c0bd..4114f71 100644 --- a/test/hardhat/univ3_test.ts +++ b/test/hardhat/univ3_test.ts @@ -76,7 +76,7 @@ describe("Pool lens test with UniV3 on mainnet", () => { it("Test getting populated ticks", async () => { const [, tickCurrent] = await poolContract.read.slot0({ blockNumber }); - const ticks = await getPopulatedTicksInRange(pool, tickCurrent, tickCurrent, publicClient, 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 });