diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 3df6198..ba37e2f 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -8,7 +8,6 @@ on: env: FOUNDRY_PROFILE: ci - MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} BNB_RPC_URL: ${{ secrets.BNB_RPC_URL }} jobs: @@ -42,16 +41,7 @@ jobs: forge build id: build - - name: Run Forge tests on Ethereum mainnet + - name: Run Forge tests on Base run: | forge test -vvv - env: - CHAIN_ID: 1 - id: testMainnet - - - name: Run Forge tests on BNB mainnet - run: | - forge test -vvv - env: - CHAIN_ID: 56 - id: testBNB + id: testBase diff --git a/contracts/EphemeralAllPositionsByOwner.sol b/contracts/EphemeralAllPositionsByOwner.sol index eaab405..7321f81 100644 --- a/contracts/EphemeralAllPositionsByOwner.sol +++ b/contracts/EphemeralAllPositionsByOwner.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import {NPMCaller} from "@aperture_finance/uni-v3-lib/src/NPMCaller.sol"; import "./PositionUtils.sol"; /// @notice A lens for Uniswap v3 that peeks into the current state of all positions by an owner without deployment @@ -8,8 +9,8 @@ import "./PositionUtils.sol"; /// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the /// revert data, and decoded by `abi.decode(data, (PositionState[]))` contract EphemeralAllPositionsByOwner is PositionUtils { - constructor(INPM npm, address owner) payable { - PositionState[] memory positions = allPositions(npm, owner); + constructor(DEX dex, address npm, address owner) payable { + PositionState[] memory positions = allPositions(dex, npm, owner); bytes memory returnData = abi.encode(positions); assembly ("memory-safe") { // The return data in a constructor will be written to code, which may exceed the contract size limit. @@ -18,18 +19,19 @@ contract EphemeralAllPositionsByOwner is PositionUtils { } /// @dev Public function to expose the abi for easier decoding using TypeChain + /// @param dex DEX /// @param npm Nonfungible position manager /// @param owner The address that owns the NFTs - function allPositions(INPM npm, address owner) public payable returns (PositionState[] memory positions) { - uint256 balance = NPMCaller.balanceOf(npm, owner); + function allPositions(DEX dex, address npm, address owner) public payable returns (PositionState[] memory positions) { + uint256 balance = NPMCaller.balanceOf(INPM(npm), owner); positions = new PositionState[](balance); unchecked { for (uint256 i; i < balance; ++i) { - uint256 tokenId = NPMCaller.tokenOfOwnerByIndex(npm, owner, i); + uint256 tokenId = NPMCaller.tokenOfOwnerByIndex(INPM(npm), owner, i); PositionState memory state = positions[i]; state.owner = owner; positionInPlace(npm, tokenId, state.position); - peek(npm, tokenId, state); + peek(dex, npm, tokenId, state); } } } diff --git a/contracts/EphemeralGetPosition.sol b/contracts/EphemeralGetPosition.sol index e0f9548..49b3c98 100644 --- a/contracts/EphemeralGetPosition.sol +++ b/contracts/EphemeralGetPosition.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import {NPMCaller} from "@aperture_finance/uni-v3-lib/src/NPMCaller.sol"; import "./PositionUtils.sol"; /// @notice A lens for Uniswap v3 that peeks into the current state of position and pool info without deployment @@ -8,8 +9,8 @@ import "./PositionUtils.sol"; /// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the /// revert data, and decoded by `abi.decode(data, (PositionState))` contract EphemeralGetPosition is PositionUtils { - constructor(INPM npm, uint256 tokenId) payable { - PositionState memory pos = getPosition(npm, tokenId); + constructor(DEX dex, address npm, uint256 tokenId) payable { + PositionState memory pos = getPosition(dex, npm, tokenId); bytes memory returnData = abi.encode(pos); assembly ("memory-safe") { revert(add(returnData, 0x20), mload(returnData)) @@ -17,11 +18,12 @@ contract EphemeralGetPosition is PositionUtils { } /// @dev Public function to expose the abi for easier decoding using TypeChain + /// @param dex DEX /// @param npm Nonfungible position manager /// @param tokenId Token ID of the position - function getPosition(INPM npm, uint256 tokenId) public payable returns (PositionState memory state) { - state.owner = NPMCaller.ownerOf(npm, tokenId); + function getPosition(DEX dex, address npm, uint256 tokenId) public payable returns (PositionState memory state) { + state.owner = NPMCaller.ownerOf(INPM(npm), tokenId); positionInPlace(npm, tokenId, state.position); - peek(npm, tokenId, state); + peek(dex, npm, tokenId, state); } } diff --git a/contracts/EphemeralGetPositions.sol b/contracts/EphemeralGetPositions.sol index f358884..5840f4e 100644 --- a/contracts/EphemeralGetPositions.sol +++ b/contracts/EphemeralGetPositions.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import {NPMCaller} from "@aperture_finance/uni-v3-lib/src/NPMCaller.sol"; import "./PositionUtils.sol"; /// @notice A lens for Uniswap v3 that peeks into the current state of positions and pool info without deployment @@ -8,8 +9,8 @@ import "./PositionUtils.sol"; /// @dev The return data can be accessed externally by `eth_call` without a `to` address or internally by catching the /// revert data, and decoded by `abi.decode(data, (PositionState[]))` contract EphemeralGetPositions is PositionUtils { - constructor(INPM npm, uint256[] memory tokenIds) payable { - PositionState[] memory positions = getPositions(npm, tokenIds); + constructor(DEX dex, address npm, uint256[] memory tokenIds) payable { + PositionState[] memory positions = getPositions(dex, npm, tokenIds); bytes memory returnData = abi.encode(positions); assembly ("memory-safe") { revert(add(returnData, 0x20), mload(returnData)) @@ -17,10 +18,12 @@ contract EphemeralGetPositions is PositionUtils { } /// @dev Public function to expose the abi for easier decoding using TypeChain + /// @param dex DEX /// @param npm Nonfungible position manager /// @param tokenIds Token IDs of the positions function getPositions( - INPM npm, + DEX dex, + address npm, uint256[] memory tokenIds ) public payable returns (PositionState[] memory positions) { unchecked { @@ -32,8 +35,8 @@ contract EphemeralGetPositions is PositionUtils { PositionState memory state = positions[i]; if (positionInPlace(npm, tokenId, state.position)) { ++i; - state.owner = NPMCaller.ownerOf(npm, tokenId); - peek(npm, tokenId, state); + state.owner = NPMCaller.ownerOf(INPM(npm), tokenId); + peek(dex, npm, tokenId, state); } } assembly ("memory-safe") { diff --git a/contracts/PositionUtils.sol b/contracts/PositionUtils.sol index 416397d..7a51ccb 100644 --- a/contracts/PositionUtils.sol +++ b/contracts/PositionUtils.sol @@ -1,28 +1,58 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {INonfungiblePositionManager as INPM, IPCSV3NonfungiblePositionManager as IPCSV3NPM} from "@aperture_finance/uni-v3-lib/src/interfaces/INonfungiblePositionManager.sol"; -import {NPMCaller, PositionFull} from "@aperture_finance/uni-v3-lib/src/NPMCaller.sol"; +import {ICommonNonfungiblePositionManager as INPM, IUniswapV3NonfungiblePositionManager as IUniV3NPM} from "@aperture_finance/uni-v3-lib/src/interfaces/IUniswapV3NonfungiblePositionManager.sol"; +import {IPCSV3NonfungiblePositionManager as IPCSV3NPM} from "@aperture_finance/uni-v3-lib/src/interfaces/IPCSV3NonfungiblePositionManager.sol"; +import {ISlipStreamNonfungiblePositionManager as ISlipStreamNPM} from "@aperture_finance/uni-v3-lib/src/interfaces/ISlipStreamNonfungiblePositionManager.sol"; import {PoolAddress} from "@aperture_finance/uni-v3-lib/src/PoolAddress.sol"; import {PoolAddressPancakeSwapV3} from "@aperture_finance/uni-v3-lib/src/PoolAddressPancakeSwapV3.sol"; import {IUniswapV3PoolState, V3PoolCallee} from "@aperture_finance/uni-v3-lib/src/PoolCaller.sol"; import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; +import {IPancakeV3Factory} from "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Factory.sol"; import {ERC20Callee} from "./libraries/ERC20Caller.sol"; import {PoolUtils} from "./PoolUtils.sol"; +enum DEX { + UniswapV3, + PancakeSwapV3, + SlipStream +} + struct Slot0 { uint160 sqrtPriceX96; int24 tick; uint16 observationIndex; uint16 observationCardinality; uint16 observationCardinalityNext; - // `feeProtocol` is of type uint8 in Uniswap V3, and uint32 in PancakeSwap V3. - // We use uint32 here as this can hold both uint8 and uint32. + // `feeProtocol` is of type uint8 in Uniswap V3, and uint32 in PancakeSwap V3. We use uint32 here as this can hold both uint8 and uint32. + // `feeProtocol` doesn't exist on SlipStream so this is manually set to 0. uint32 feeProtocol; + // This should always be true because it is only temporarily set to false as re-entrancy guard during a transaction. bool unlocked; } -// The length of the struct is 25 words. +struct PositionFull { + // the nonce for permits + uint96 nonce; + // the address that is approved for spending this token + address operator; + address token0; + address token1; + // The pool's fee or tickSpacing. This depends on the DEX's NPM.positions() implementation. This is `fee` for Uniswap V3 and PancakeSwap V3, and `tickSpacing` for SlipStream. + uint24 feeOrTickSpacing; + // the tick range of the position + int24 tickLower; + int24 tickUpper; + // the liquidity of the position + uint128 liquidity; + // the fee growth of the aggregate position as of the last action on the individual position + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + // how many uncollected tokens are owed to the position, as of the last computation + uint128 tokensOwed0; + uint128 tokensOwed1; +} + struct PositionState { // token ID of the position uint256 tokenId; @@ -30,6 +60,12 @@ struct PositionState { address owner; // nonfungible position manager's position struct with real-time tokensOwed PositionFull position; + // The pool address. + address pool; + // The pool's fee in hundredths of a bip, i.e. 1e-6 + uint24 poolFee; + // The pool's tick spacing. + int24 poolTickSpacing; // pool's slot0 struct Slot0 slot0; // pool's active liquidity @@ -40,21 +76,56 @@ struct PositionState { uint8 decimals1; } +// Partial interface for the SlipStream factory. SlipStream factory is named "CLFactory" and "CL" presumably stands for concentrated liquidity. +// https://github.com/velodrome-finance/slipstream/blob/main/contracts/core/interfaces/ICLFactory.sol +interface ISlipStreamCLFactory { + /// @notice Returns the pool address for a given pair of tokens and a tick spacing, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param tickSpacing The tick spacing of the pool + /// @return pool The pool address + function getPool(address tokenA, address tokenB, int24 tickSpacing) external view returns (address pool); +} + /// @title Position utility contract /// @author Aperture Finance /// @notice Base contract for Uniswap v3 that peeks into the current state of position and pool info abstract contract PositionUtils is PoolUtils { /// @dev Peek a position and calculate the fee growth inside the position /// state.position must be populated before calling this function + /// @param dex DEX type /// @param npm Nonfungible position manager /// @param tokenId Token ID of the position /// @param state Position state pointer to be updated in place - function peek(INPM npm, uint256 tokenId, PositionState memory state) internal view { + function peek(DEX dex, address npm, uint256 tokenId, PositionState memory state) internal view { state.tokenId = tokenId; PositionFull memory position = state.position; - V3PoolCallee pool = V3PoolCallee.wrap( - IUniswapV3Factory(NPMCaller.factory(npm)).getPool(position.token0, position.token1, position.fee) - ); + if (dex == DEX.UniswapV3) { + state.poolFee = position.feeOrTickSpacing; + state.pool = IUniswapV3Factory(IUniV3NPM(npm).factory()).getPool( + position.token0, + position.token1, + state.poolFee + ); + } else if (dex == DEX.PancakeSwapV3) { + state.poolFee = position.feeOrTickSpacing; + state.pool = IPancakeV3Factory(IUniV3NPM(npm).factory()).getPool( + position.token0, + position.token1, + state.poolFee + ); + } else if (dex == DEX.SlipStream) { + state.poolTickSpacing = int24(position.feeOrTickSpacing); + state.pool = ISlipStreamCLFactory(ISlipStreamNPM(npm).factory()).getPool( + position.token0, + position.token1, + state.poolTickSpacing + ); + } + V3PoolCallee pool = V3PoolCallee.wrap(state.pool); + state.poolFee = pool.fee(); + state.poolTickSpacing = pool.tickSpacing(); state.activeLiquidity = pool.liquidity(); slot0InPlace(pool, state.slot0); if (position.liquidity != 0) { @@ -83,8 +154,8 @@ abstract contract PositionUtils is PoolUtils { /// @param tokenId The ID of the token that represents the position /// @param pos The position pointer to be updated in place /// @return exists Whether the position exists - function positionInPlace(INPM npm, uint256 tokenId, PositionFull memory pos) internal view returns (bool exists) { - bytes4 selector = INPM.positions.selector; + function positionInPlace(address npm, uint256 tokenId, PositionFull memory pos) internal view returns (bool exists) { + bytes4 selector = IUniV3NPM(npm).positions.selector; assembly ("memory-safe") { // Write the abi-encoded calldata into memory. mstore(0, selector) diff --git a/foundry.toml b/foundry.toml index 7a78747..11b263f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -25,7 +25,6 @@ optimizer_runs = 200 runs = 16 [rpc_endpoints] -mainnet = "${MAINNET_RPC_URL}" -bnb_smart_chain = "${BNB_RPC_URL}" +base = "${BASE_RPC_URL}" # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/package.json b/package.json index 10fa907..7d766c3 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,11 @@ "typechain": "hardhat typechain" }, "dependencies": { - "@aperture_finance/uni-v3-lib": "^2.0.1", + "@aperture_finance/uni-v3-lib": "^3.0.0-alpha", "@openzeppelin/contracts": "^5.0.2", "ethers": "5.7.2", "viem": "^2.8.4", - "zod":"^3.23.8" + "zod": "^3.23.8" }, "devDependencies": { "@ethersproject/abi": "5.7.0", @@ -66,6 +66,7 @@ "@types/node": "^20.11.30", "@uniswap/v3-sdk": "^3.11.0", "chai": "^4.4.1", + "dotenv": "^16.4.5", "hardhat": "^2.22.2", "mocha": "^10.4.0", "prettier": "^3.2.5", diff --git a/src/viem/positionLens.ts b/src/viem/positionLens.ts index 765a4ab..f77771c 100644 --- a/src/viem/positionLens.ts +++ b/src/viem/positionLens.ts @@ -5,6 +5,20 @@ import { EphemeralGetPosition__factory, EphemeralGetPositions__factory, } from "../../typechain"; +import { AutomatedMarketMakerEnum } from "./poolLens"; + +function ammToSolidityDexEnum(amm: AutomatedMarketMakerEnum): number { + if (amm === AutomatedMarketMakerEnum.enum.UNISWAP_V3) { + return 0; + } + if (amm === AutomatedMarketMakerEnum.enum.PANCAKESWAP_V3) { + return 1; + } + if (amm === AutomatedMarketMakerEnum.enum.SLIPSTREAM) { + return 2; + } + throw new Error(`Unexpected AMM: ${amm}`); +} /** * Get the position details in a single call by deploying an ephemeral contract via `eth_call` @@ -15,6 +29,7 @@ import { * @returns The position details. */ export async function getPositionDetails( + amm: AutomatedMarketMakerEnum, npm: Address, positionId: bigint, publicClient: PublicClient, @@ -24,7 +39,7 @@ export async function getPositionDetails( { abi: EphemeralGetPosition__factory.abi, bytecode: EphemeralGetPosition__factory.bytecode, - args: [npm, positionId], + args: [ammToSolidityDexEnum(amm), npm, positionId], }, publicClient, blockNumber, @@ -40,6 +55,7 @@ export async function getPositionDetails( * @returns The position details for all positions. */ export async function getPositions( + amm: AutomatedMarketMakerEnum, npm: Address, positionIds: bigint[], publicClient: PublicClient, @@ -49,7 +65,7 @@ export async function getPositions( { abi: EphemeralGetPositions__factory.abi, bytecode: EphemeralGetPositions__factory.bytecode, - args: [npm, positionIds], + args: [ammToSolidityDexEnum(amm), npm, positionIds], }, publicClient, blockNumber, @@ -67,6 +83,7 @@ export async function getPositions( * @returns The position details for all positions of the specified owner. */ export async function getAllPositionsByOwner( + amm: AutomatedMarketMakerEnum, npm: Address, owner: Address, publicClient: PublicClient, @@ -76,7 +93,7 @@ export async function getAllPositionsByOwner( { abi: EphemeralAllPositionsByOwner__factory.abi, bytecode: EphemeralAllPositionsByOwner__factory.bytecode, - args: [npm, owner], + args: [ammToSolidityDexEnum(amm), npm, owner], }, publicClient, blockNumber, diff --git a/test/foundry/Base.t.sol b/test/foundry/Base.t.sol index 12a7948..1ca4a4d 100644 --- a/test/foundry/Base.t.sol +++ b/test/foundry/Base.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; -import {INonfungiblePositionManager as INPM} from "@aperture_finance/uni-v3-lib/src/interfaces/INonfungiblePositionManager.sol"; +import "contracts/PositionUtils.sol"; import "@aperture_finance/uni-v3-lib/src/LiquidityAmounts.sol"; import "@aperture_finance/uni-v3-lib/src/PoolCaller.sol"; import "@aperture_finance/uni-v3-lib/src/TernaryLib.sol"; @@ -17,11 +17,6 @@ import "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.so import "forge-std/Test.sol"; import "solady/src/utils/SafeTransferLib.sol"; -enum DEX { - UniswapV3, - PancakeSwapV3 -} - abstract contract BaseTest is Test, IPancakeV3MintCallback, @@ -39,13 +34,10 @@ abstract contract BaseTest is uint160 internal constant MAX_SQRT_RATIO_MINUS_ONE = 1461446703485210103287273052203988822378723970342 - 1; DEX internal dex; + address internal npm; - // Uniswap v3 position manager - INPM internal npm; - - uint256 internal chainId; address internal WETH; - address internal USDC; + address internal constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; address internal token0; address internal token1; uint24 internal constant fee = 500; @@ -58,8 +50,8 @@ abstract contract BaseTest is // Configure state variables for each chain after creating a fork function initAfterFork() internal { - factory = npm.factory(); - WETH = npm.WETH9(); + factory = INPM(npm).factory(); + WETH = INPM(npm).WETH9(); (token0, token1) = (WETH < USDC).switchIf(USDC, WETH); pool = IUniswapV3Factory(factory).getPool(token0, token1, fee); tickSpacing = V3PoolCallee.wrap(pool).tickSpacing(); @@ -68,31 +60,19 @@ abstract contract BaseTest is } function setUp() public virtual { - if (chainId == 0) { - chainId = vm.envOr("CHAIN_ID", uint256(1)); - } - string memory chainAlias = getChain(chainId).chainAlias; - // Configuration for each chain - if (chainId == 1) { - vm.createSelectFork(chainAlias, 17000000); - USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - npm = dex == DEX.PancakeSwapV3 - ? INPM(0x46A15B0b27311cedF172AB29E4f4766fbE7F4364) - : INPM(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); - } else if (chainId == 56) { - vm.createSelectFork(chainAlias); - USDC = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; - npm = dex == DEX.PancakeSwapV3 - ? INPM(0x46A15B0b27311cedF172AB29E4f4766fbE7F4364) - : INPM(0x7b8A01B39D58278b5DE7e48c8449c9f4F5170613); + vm.createSelectFork("base", 17577000); + if (dex == DEX.UniswapV3) { + npm = 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1; + } else if (dex == DEX.PancakeSwapV3) { + npm = 0x46A15B0b27311cedF172AB29E4f4766fbE7F4364; } else { - revert("Unsupported chain"); + npm = 0x827922686190790b37229fd06084350E74485b72; } initAfterFork(); vm.label(WETH, "WETH"); vm.label(USDC, "USDC"); vm.label(address(npm), "NPM"); - vm.label(pool, "UniswapV3Pool"); + vm.label(pool, "Pool"); vm.label(address(this), "Test"); } diff --git a/test/foundry/PoolLens.t.sol b/test/foundry/PoolLens.t.sol index bb5c06a..d36c7f4 100644 --- a/test/foundry/PoolLens.t.sol +++ b/test/foundry/PoolLens.t.sol @@ -51,8 +51,8 @@ contract PoolLensTest is BaseTest, PoolUtils { (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); + deal(token0, address(this), type(uint128).max); + deal(token1, address(this), type(uint128).max); PositionKey[] memory keys = new PositionKey[](3); for (uint256 i; i < 3; ++i) { mint(address(this), amount0Desired, amount1Desired, tickLower, tickUpper); @@ -118,8 +118,8 @@ contract PCSV3PoolLensTest is BaseTest, PoolUtils { (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); + deal(token0, address(this), type(uint128).max); + deal(token1, address(this), type(uint128).max); PositionKey[] memory keys = new PositionKey[](3); for (uint256 i; i < 3; ++i) { mint(address(this), amount0Desired, amount1Desired, tickLower, tickUpper); diff --git a/test/foundry/PositionLens.t.sol b/test/foundry/PositionLens.t.sol index b70ca3f..a489c42 100644 --- a/test/foundry/PositionLens.t.sol +++ b/test/foundry/PositionLens.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IPCSV3NonfungiblePositionManager} from "@aperture_finance/uni-v3-lib/src/interfaces/INonfungiblePositionManager.sol"; +import {IPCSV3NonfungiblePositionManager} from "@aperture_finance/uni-v3-lib/src/interfaces/IPCSV3NonfungiblePositionManager.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"; @@ -19,7 +19,7 @@ contract PositionLensTest is BaseTest { function setUp() public virtual override { super.setUp(); - lastTokenId = npm.totalSupply(); + lastTokenId = INPM(npm).totalSupply(); positionLens = new PositionLens(); int24 tick = currentTick(); _tickLower = tick - tickSpacing; @@ -35,8 +35,8 @@ contract PositionLensTest is BaseTest { function generateFees() internal { uint160 initialPrice = sqrtPriceX96(); uint256 amountIn = 1000 * token0Unit; - deal(token0, address(this), 2 * amountIn); - deal(token1, address(this), 1000 * token1Unit); + deal(token0, address(this), 100000 * amountIn); + deal(token1, address(this), 100000 * token1Unit); for (uint256 i; i < 10; ++i) { swapBackAndForth(amountIn, true, initialPrice); } @@ -90,12 +90,13 @@ contract PositionLensTest is BaseTest { function verifyPosition(PositionState memory pos) internal view { { - assertEq(pos.owner, npm.ownerOf(pos.tokenId), "owner"); - (, , address token0, , uint24 fee, int24 tickLower, , uint128 liquidity, , , , ) = npm.positions( + assertEq(pos.owner, INPM(npm).ownerOf(pos.tokenId), "owner"); + (, , address token0, , uint24 fee, int24 tickLower, , uint128 liquidity, , , , ) = IUniV3NPM(npm).positions( pos.tokenId ); assertEq(token0, pos.position.token0, "token0"); - assertEq(fee, pos.position.fee, "fee"); + assertEq(fee, pos.position.feeOrTickSpacing, "fee"); + assertEq(fee, pos.poolFee, "poolFee"); assertEq(tickLower, pos.position.tickLower, "tickLower"); assertEq(liquidity, pos.position.liquidity, "liquidity"); } @@ -103,7 +104,7 @@ contract PositionLensTest is BaseTest { address pool = IUniswapV3Factory(factory).getPool( pos.position.token0, pos.position.token1, - pos.position.fee + pos.poolFee ); ( uint160 sqrtPriceX96, @@ -131,7 +132,7 @@ contract PositionLensTest is BaseTest { /// forge-config: ci.fuzz.runs = 16 function testFuzz_GetPosition(uint256 tokenId) public virtual { tokenId = bound(tokenId, 1, 200); - try new EphemeralGetPosition(npm, tokenId) {} catch (bytes memory returnData) { + try new EphemeralGetPosition(dex, npm, tokenId) {} catch (bytes memory returnData) { PositionState memory pos = abi.decode(returnData, (PositionState)); verifyPosition(pos); } @@ -143,7 +144,7 @@ contract PositionLensTest is BaseTest { for (uint256 i; i < 10; ++i) { tokenIds[i] = startTokenId + i; } - try new EphemeralGetPositions(npm, tokenIds) {} catch (bytes memory returnData) { + try new EphemeralGetPositions(dex, npm, tokenIds) {} catch (bytes memory returnData) { PositionState[] memory positions = abi.decode(returnData, (PositionState[])); uint256 length = positions.length; console2.log("length", length); @@ -154,8 +155,8 @@ contract PositionLensTest is BaseTest { } function test_AllPositions() public { - address owner = npm.ownerOf(lastTokenId); - try new EphemeralAllPositionsByOwner(npm, owner) {} catch (bytes memory returnData) { + address owner = INPM(npm).ownerOf(lastTokenId); + try new EphemeralAllPositionsByOwner(dex, npm, owner) {} catch (bytes memory returnData) { PositionState[] memory positions = abi.decode(returnData, (PositionState[])); uint256 length = positions.length; console2.log("balance", length); diff --git a/test/hardhat/SlipStreamPool_abi.json b/test/hardhat/SlipStreamPool_abi.json new file mode 100644 index 0000000..731bdaf --- /dev/null +++ b/test/hardhat/SlipStreamPool_abi.json @@ -0,0 +1,1309 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "CollectFees", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid1", + "type": "uint256" + } + ], + "name": "Flash", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextOld", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextNew", + "type": "uint16" + } + ], + "name": "IncreaseObservationCardinalityNext", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0New", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1New", + "type": "uint8" + } + ], + "name": "SetFeeProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "collectFees", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factoryRegistry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal0X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal1X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "gauge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gaugeFees", + "outputs": [ + { + "internalType": "uint128", + "name": "token0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "token1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint256", + "name": "_rewardGrowthGlobalX128", + "type": "uint256" + } + ], + "name": "getRewardGrowthInside", + "outputs": [ + { + "internalType": "uint256", + "name": "rewardGrowthInside", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + } + ], + "name": "increaseObservationCardinalityNext", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_token0", + "type": "address" + }, + { + "internalType": "address", + "name": "_token1", + "type": "address" + }, + { + "internalType": "int24", + "name": "_tickSpacing", + "type": "int24" + }, + { + "internalType": "address", + "name": "_factoryRegistry", + "type": "address" + }, + { + "internalType": "uint160", + "name": "_sqrtPriceX96", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lastUpdated", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidityPerTick", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "nft", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "observations", + "outputs": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32[]", + "name": "secondsAgos", + "type": "uint32[]" + } + ], + "name": "observe", + "outputs": [ + { + "internalType": "int56[]", + "name": "tickCumulatives", + "type": "int56[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerLiquidityCumulativeX128s", + "type": "uint160[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "periodFinish", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "tokensOwed0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokensOwed1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardGrowthGlobalX128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardReserve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rollover", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_gauge", + "type": "address" + }, + { + "internalType": "address", + "name": "_nft", + "type": "address" + } + ], + "name": "setGaugeAndPositionManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "snapshotCumulativesInside", + "outputs": [ + { + "internalType": "int56", + "name": "tickCumulativeInside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityInsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsInside", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int128", + "name": "stakedLiquidityDelta", + "type": "int128" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "bool", + "name": "positionUpdate", + "type": "bool" + } + ], + "name": "stake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakedLiquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_rewardRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_rewardReserve", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_periodFinish", + "type": "uint256" + } + ], + "name": "syncReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int16", + "name": "", + "type": "int16" + } + ], + "name": "tickBitmap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "int128", + "name": "stakedLiquidityNet", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardGrowthOutsideX128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unstakedFee", + "outputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "updateRewardsGrowthGlobal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/test/hardhat/pcsv3_test.ts b/test/hardhat/pcsv3_test.ts index 67f2e26..4cf586e 100644 --- a/test/hardhat/pcsv3_test.ts +++ b/test/hardhat/pcsv3_test.ts @@ -17,7 +17,7 @@ import { import { EphemeralGetPositions__factory, EphemeralPoolSlots__factory, - INonfungiblePositionManager__factory, + IPCSV3NonfungiblePositionManager__factory, IPancakeV3Pool__factory, } from "../../typechain"; import { computePoolAddress } from "@pancakeswap/v3-sdk"; @@ -51,7 +51,7 @@ describe("Pool lens test with PCSV3 on BSC", () => { }); const npm = getContract({ address: PCSV3_NPM, - abi: INonfungiblePositionManager__factory.abi, + abi: IPCSV3NonfungiblePositionManager__factory.abi, client: publicClient, }); @@ -90,9 +90,9 @@ describe("Pool lens test with PCSV3 on BSC", () => { it("Test getting position details", async () => { const { tokenId, - position: { token0, token1, fee }, + position: { token0, token1, feeOrTickSpacing: fee }, slot0: { sqrtPriceX96, tick }, - } = await getPositionDetails(PCSV3_NPM, 4n, publicClient, blockNumber); + } = await getPositionDetails(AMM, PCSV3_NPM, 4n, publicClient, blockNumber); expect(tokenId).to.be.eq(4n); const poolAddress = computePoolAddress({ deployerAddress: PCSV3_POOL_DEPLOYER, @@ -111,22 +111,24 @@ describe("Pool lens test with PCSV3 on BSC", () => { async function verifyPositionDetails(posArr: ContractFunctionReturnType) { await Promise.all( - posArr.map(async ({ tokenId, position }) => { + posArr.map(async ({ tokenId, position, poolFee }) => { 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.feeOrTickSpacing).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); + expect(poolFee).to.be.eq(fee); }), ); } it("Test getting position array", async () => { const posArr = await getPositions( + AMM, PCSV3_NPM, Array.from({ length: 100 }, (_, i) => BigInt(i + 1)), publicClient, @@ -139,7 +141,7 @@ describe("Pool lens test with PCSV3 on BSC", () => { 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); + const posArr = await getAllPositionsByOwner(AMM, PCSV3_NPM, owner, publicClient, blockNumber); await verifyPositionDetails(posArr); }); diff --git a/test/hardhat/slipstream_test.ts b/test/hardhat/slipstream_test.ts new file mode 100644 index 0000000..648f6ba --- /dev/null +++ b/test/hardhat/slipstream_test.ts @@ -0,0 +1,186 @@ +import { TickMath } from "@uniswap/v3-sdk"; +import { expect } from "chai"; +import { Address, ContractFunctionReturnType, createPublicClient, getContract, http, toHex } from "viem"; +import { + AutomatedMarketMakerEnum, + getAllPositionsByOwner, + getPopulatedTicksInRange, + getPositionDetails, + getPositions, + getPositionsSlots, + getStaticSlots, + getStorageAt, + getTickBitmapSlots, + getTicksSlots, +} from "../../src/viem"; +import { + EphemeralGetPositions__factory, + EphemeralPoolSlots__factory, + ISlipStreamCLFactory__factory, + ISlipStreamNonfungiblePositionManager__factory, +} from "../../typechain"; +import { base } from "viem/chains"; +import 'dotenv/config'; +import SlipStreamPoolAbi from './SlipStreamPool_abi.json'; + +const AMM = AutomatedMarketMakerEnum.enum.SLIPSTREAM; +const SLIPSTREAM_NPM = "0x827922686190790b37229fd06084350E74485b72"; +const SLIPSTREAM_FACTORY = "0x5e7BB104d84c7CB9B682AaC2F3d509f5F406809A"; +const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; +const WETH_ADDRESS = "0x4200000000000000000000000000000000000006"; + +describe("Pool lens test with SlipStream on Base", () => { + const publicClient = createPublicClient({ + chain: base, + transport: http(`${process.env.BASE_RPC_URL}`), + batch: { + multicall: true, + }, + }); + const factoryContract = getContract({ + address: SLIPSTREAM_FACTORY, + abi: ISlipStreamCLFactory__factory.abi, + client: publicClient, + }); + const npm = getContract({ + address: SLIPSTREAM_NPM, + abi: ISlipStreamNonfungiblePositionManager__factory.abi, + client: publicClient, + }); + let blockNumber: bigint; + let pool: Address; + let poolContract; + + before(async () => { + blockNumber = (await publicClient.getBlockNumber()) - 64n; + console.log(`Running SlipStream tests on Base mainnet at block number ${blockNumber}...`); + pool = await factoryContract.read.getPool([WETH_ADDRESS, USDC_ADDRESS, /*tickSpacing=*/100], { blockNumber }); + poolContract = getContract({ + abi: SlipStreamPoolAbi, + client: publicClient, + address: pool, + }); + }); + + 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, feeOrTickSpacing: tickSpacing }, + slot0: { sqrtPriceX96, tick }, + } = await getPositionDetails(AMM, SLIPSTREAM_NPM, 43484n, publicClient, blockNumber); + expect(tokenId).to.be.eq(43484n); + const [_sqrtPriceX96, _tick] = await getContract({ + address: await factoryContract.read.getPool([token0, token1, tickSpacing], { blockNumber }), + abi: SlipStreamPoolAbi, + 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, poolTickSpacing }) => { + const [, , token0, token1, tickSpacing, 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.feeOrTickSpacing).to.be.eq(tickSpacing); + expect(position.tickLower).to.be.eq(tickLower); + expect(position.tickUpper).to.be.eq(tickUpper); + expect(position.liquidity).to.be.eq(liquidity); + expect(poolTickSpacing).to.be.eq(tickSpacing); + }), + ); + } + + it("Test getting position array", async () => { + const posArr = await getPositions( + AMM, + SLIPSTREAM_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(AMM, SLIPSTREAM_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 - 1000n, + 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); + }); +}); diff --git a/test/hardhat/univ3_test.ts b/test/hardhat/univ3_test.ts index 2d75346..d66b358 100644 --- a/test/hardhat/univ3_test.ts +++ b/test/hardhat/univ3_test.ts @@ -16,7 +16,7 @@ import { import { EphemeralGetPositions__factory, EphemeralPoolSlots__factory, - INonfungiblePositionManager__factory, + IUniswapV3NonfungiblePositionManager__factory, IUniswapV3Pool__factory, } from "../../typechain"; import { mainnet } from "viem/chains"; @@ -50,7 +50,7 @@ describe("Pool lens test with UniV3 on mainnet", () => { }); const npm = getContract({ address: UNIV3_NPM, - abi: INonfungiblePositionManager__factory.abi, + abi: IUniswapV3NonfungiblePositionManager__factory.abi, client: publicClient, }); @@ -89,9 +89,9 @@ describe("Pool lens test with UniV3 on mainnet", () => { it("Test getting position details", async () => { const { tokenId, - position: { token0, token1, fee }, + position: { token0, token1, feeOrTickSpacing: fee }, slot0: { sqrtPriceX96, tick }, - } = await getPositionDetails(UNIV3_NPM, 4n, publicClient, blockNumber); + } = await getPositionDetails(AMM, UNIV3_NPM, 4n, publicClient, blockNumber); expect(tokenId).to.be.eq(4n); const [_sqrtPriceX96, _tick] = await getContract({ address: computePoolAddress({ @@ -109,22 +109,24 @@ describe("Pool lens test with UniV3 on mainnet", () => { async function verifyPositionDetails(posArr: ContractFunctionReturnType) { await Promise.all( - posArr.map(async ({ tokenId, position }) => { + posArr.map(async ({ tokenId, position, poolFee }) => { 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.feeOrTickSpacing).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); + expect(poolFee).to.be.eq(fee); }), ); } it("Test getting position array", async () => { const posArr = await getPositions( + AMM, UNIV3_NPM, Array.from({ length: 100 }, (_, i) => BigInt(i + 1)), publicClient, @@ -137,7 +139,7 @@ describe("Pool lens test with UniV3 on mainnet", () => { 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(UNIV3_NPM, owner, publicClient, blockNumber); + const posArr = await getAllPositionsByOwner(AMM, UNIV3_NPM, owner, publicClient, blockNumber); await verifyPositionDetails(posArr); }); diff --git a/yarn.lock b/yarn.lock index 380c763..c7d9991 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== -"@aperture_finance/uni-v3-lib@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@aperture_finance/uni-v3-lib/-/uni-v3-lib-2.0.1.tgz#60a6f32a523a08491f884a39610fb224b1636b5f" - integrity sha512-m7R34aVy7WBGAOnA3Jw1uGVLUc1UoicN4nFoecFaTQcRz/INho9FF7KhvL7TbPqi3CUzSTkgntK+NSjo0tiVqA== +"@aperture_finance/uni-v3-lib@^3.0.0-alpha": + version "3.0.0-alpha" + resolved "https://registry.yarnpkg.com/@aperture_finance/uni-v3-lib/-/uni-v3-lib-3.0.0-alpha.tgz#b542d730641cbd477eceddc4b633dae98d73f4aa" + integrity sha512-GJe5wEPKgP25z3eoMqrfRuCFOnebn5dk6cfLj3hqnPHAjcAYgT5IUt4yPI+ba0lpEgQiybQIkK67IAoeq/lhRQ== dependencies: "@openzeppelin/contracts" "^5.0.2" "@pancakeswap/v3-core" "^1.0.2" @@ -1472,6 +1472,11 @@ dotenv@^14.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.3.2.tgz#7c30b3a5f777c79a3429cb2db358eef6751e8369" integrity sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ== +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"