From 106bd58cc1cd8c58b0f48cf2a2421f5140680d56 Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:01:30 -0800 Subject: [PATCH] feat: v4 non-complex hooks routing (#916) we can support non-complex hooks routing, that we know have no effect during swaps. --- lib/cron/cache-pools.ts | 12 +- lib/util/v4HooksPoolsFiltering.ts | 63 ++ package-lock.json | 43 +- package.json | 5 +- .../util/v4HooksPoolsFiltering.test.ts | 597 ++++++++++++++++++ 5 files changed, 695 insertions(+), 25 deletions(-) create mode 100644 lib/util/v4HooksPoolsFiltering.ts create mode 100644 test/jest/unit/handlers/util/v4HooksPoolsFiltering.test.ts diff --git a/lib/cron/cache-pools.ts b/lib/cron/cache-pools.ts index 6229f10bef..888c8e9c8b 100644 --- a/lib/cron/cache-pools.ts +++ b/lib/cron/cache-pools.ts @@ -24,7 +24,7 @@ import { AWSMetricsLogger } from '../handlers/router-entities/aws-metrics-logger import { metricScope } from 'aws-embedded-metrics' import * as zlib from 'zlib' import dotenv from 'dotenv' -import { HOOKS_ADDRESSES_ALLOWLIST } from '../util/hooksAddressesAllowlist' +import { v4HooksPoolsFiltering } from '../util/v4HooksPoolsFiltering' // Needed for local stack dev, not needed for staging or prod // But it still doesn't work on the local cdk stack update, @@ -224,15 +224,7 @@ const handler: ScheduledHandler = metricScope((metrics) => async (event: EventBr } if (protocol === Protocol.V4) { - pools = (pools as Array).filter((pool: V4SubgraphPool) => { - const shouldFilterOut = !HOOKS_ADDRESSES_ALLOWLIST[chainId].includes(pool.hooks.toLowerCase()) - - if (shouldFilterOut) { - log.info(`Filtering out pool ${pool.id} from ${protocol} on ${chainId}`) - } - - return !shouldFilterOut - }) + pools = v4HooksPoolsFiltering(chainId, pools as Array) } metric.putMetric(`${metricPrefix}.getPools.latency`, Date.now() - beforeGetPool) diff --git a/lib/util/v4HooksPoolsFiltering.ts b/lib/util/v4HooksPoolsFiltering.ts new file mode 100644 index 0000000000..6012be1973 --- /dev/null +++ b/lib/util/v4HooksPoolsFiltering.ts @@ -0,0 +1,63 @@ +import { V4SubgraphPool } from '@uniswap/smart-order-router' +import { Hook } from '@uniswap/v4-sdk' +import { HOOKS_ADDRESSES_ALLOWLIST } from './hooksAddressesAllowlist' +import { ChainId } from '@uniswap/sdk-core' +import { PriorityQueue } from '@datastructures-js/priority-queue' + +type V4PoolGroupingKey = string +const TOP_GROUPED_V4_POOLS = 10 + +function convertV4PoolToGroupingKey(pool: V4SubgraphPool): V4PoolGroupingKey { + return pool.token0.id.concat(pool.token1.id).concat(pool.feeTier) +} + +function isHooksPoolRoutable(pool: V4SubgraphPool): boolean { + return ( + !Hook.hasSwapPermissions(pool.hooks) && + // If the fee tier is smaller than or equal to 100%, it means the pool is not dynamic fee pool. + // Swap fee in total can be 100% (https://github.com/Uniswap/v4-core/blob/b619b6718e31aa5b4fa0286520c455ceb950276d/src/libraries/SwapMath.sol#L12) + // Dynamic fee is at 0x800000 or 838.8608% fee tier. + // Since pool manager doesn;t check the fee at 100% max during pool initialization (https://github.com/Uniswap/v4-core/blob/main/src/PoolManager.sol#L128) + // it's more defensive programming to ensure the fee tier is less than or equal to 100% + Number(pool.feeTier) <= 1000000 + ) +} + +// it has to be a min heap in order to preserve the top eth tvl v4 pools +const V4SubgraphPoolComparator = (a: V4SubgraphPool, b: V4SubgraphPool) => { + return a.tvlETH > b.tvlETH ? 1 : -1 +} + +export function v4HooksPoolsFiltering(chainId: ChainId, pools: Array): Array { + const v4PoolsByTokenPairsAndFees: Record> = {} + + pools.forEach((pool: V4SubgraphPool) => { + if (isHooksPoolRoutable(pool)) { + const v4Pools = + v4PoolsByTokenPairsAndFees[convertV4PoolToGroupingKey(pool)] ?? + new PriorityQueue(V4SubgraphPoolComparator) + + v4Pools.push(pool) + + if (v4Pools.size() > TOP_GROUPED_V4_POOLS) { + v4Pools.dequeue() + } + + v4PoolsByTokenPairsAndFees[pool.token0.id.concat(pool.token1.id).concat(pool.feeTier)] = v4Pools + } + }) + + const topTvlPools: Array = [] + Object.values(v4PoolsByTokenPairsAndFees).forEach((pq: PriorityQueue) => { + topTvlPools.push(...pq.toArray()) + }) + + const allowlistedHooksPools = pools.filter((pool: V4SubgraphPool) => { + return ( + HOOKS_ADDRESSES_ALLOWLIST[chainId].includes(pool.hooks.toLowerCase()) && + !topTvlPools.find((topPool: V4SubgraphPool) => topPool.id.toLowerCase() === pool.id.toLowerCase()) + ) + }) + + return topTvlPools.concat(allowlistedHooksPools) +} diff --git a/package-lock.json b/package-lock.json index 0988490dcc..afd35082ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "GPL", "dependencies": { + "@datastructures-js/priority-queue": "^6.3.1", "@hapi/joi": "^17.1.1", "@middy/core": "^2.4.1", "@middy/http-error-handler": "^2.4.1", @@ -34,7 +35,7 @@ "@uniswap/v2-sdk": "^4.6.1", "@uniswap/v3-periphery": "^1.4.4", "@uniswap/v3-sdk": "^3.17.1", - "@uniswap/v4-sdk": "^1.10.0", + "@uniswap/v4-sdk": "^1.11.2", "async-retry": "^1.3.1", "aws-cdk-lib": "^2.137.0", "aws-embedded-metrics": "^2.0.6", @@ -835,9 +836,17 @@ } }, "node_modules/@datastructures-js/heap": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.1.2.tgz", - "integrity": "sha512-Dl6MPPVXxzWsSQxIaV0sOpAx/B8r7RYUO5/GWe7GhG9v9P4QfZ1cgPSq+SoF0QJFhu9G9TmtPfRLHPWzL73GpQ==" + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.3.3.tgz", + "integrity": "sha512-UcUu/DLh/aM4W3C8zZfwxxm6/6FIZUlm3mcAXuNOCa6Aj4iizNvNXQyb8DjZQH2jKSQbMRyNlngP6TPimuGjpQ==" + }, + "node_modules/@datastructures-js/priority-queue": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@datastructures-js/priority-queue/-/priority-queue-6.3.1.tgz", + "integrity": "sha512-eoxkWql/j0VJ0UFMFTpnyJz4KbEEVQ6aZ/JuJUgenu0Im4tYKylAycNGsYCHGXiVNEd7OKGVwfx1Ac3oYkuu7A==", + "dependencies": { + "@datastructures-js/heap": "^4.3.3" + } }, "node_modules/@ensdomains/ens": { "version": "0.4.5", @@ -4729,9 +4738,9 @@ } }, "node_modules/@uniswap/v4-sdk": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.10.0.tgz", - "integrity": "sha512-aqA40tHgjZ8VwUAXehjR+d7v/8qnQ5CrbhHmMOLo/98jwGM63iHYLyq1kEyaQJl+JhCMXdTGczsEpZSj05iQgw==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.11.2.tgz", + "integrity": "sha512-vrAHv13J7LdAsqBkYEkaChc9D3tY9vsEd0YbUwLraC6AUCYGxb2sCEyg+tse5RDicDd0aqiEwyq8OHnCI1R3aQ==", "dependencies": { "@ethersproject/solidity": "^5.0.9", "@uniswap/sdk-core": "^5.3.1", @@ -25260,9 +25269,17 @@ } }, "@datastructures-js/heap": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.1.2.tgz", - "integrity": "sha512-Dl6MPPVXxzWsSQxIaV0sOpAx/B8r7RYUO5/GWe7GhG9v9P4QfZ1cgPSq+SoF0QJFhu9G9TmtPfRLHPWzL73GpQ==" + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.3.3.tgz", + "integrity": "sha512-UcUu/DLh/aM4W3C8zZfwxxm6/6FIZUlm3mcAXuNOCa6Aj4iizNvNXQyb8DjZQH2jKSQbMRyNlngP6TPimuGjpQ==" + }, + "@datastructures-js/priority-queue": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@datastructures-js/priority-queue/-/priority-queue-6.3.1.tgz", + "integrity": "sha512-eoxkWql/j0VJ0UFMFTpnyJz4KbEEVQ6aZ/JuJUgenu0Im4tYKylAycNGsYCHGXiVNEd7OKGVwfx1Ac3oYkuu7A==", + "requires": { + "@datastructures-js/heap": "^4.3.3" + } }, "@ensdomains/ens": { "version": "0.4.5", @@ -28138,9 +28155,9 @@ } }, "@uniswap/v4-sdk": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.10.0.tgz", - "integrity": "sha512-aqA40tHgjZ8VwUAXehjR+d7v/8qnQ5CrbhHmMOLo/98jwGM63iHYLyq1kEyaQJl+JhCMXdTGczsEpZSj05iQgw==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.11.2.tgz", + "integrity": "sha512-vrAHv13J7LdAsqBkYEkaChc9D3tY9vsEd0YbUwLraC6AUCYGxb2sCEyg+tse5RDicDd0aqiEwyq8OHnCI1R3aQ==", "requires": { "@ethersproject/solidity": "^5.0.9", "@uniswap/sdk-core": "^5.3.1", diff --git a/package.json b/package.json index 62683f8bd8..f1854a6816 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "typescript": "^4.2.3" }, "dependencies": { + "@datastructures-js/priority-queue": "^6.3.1", "@hapi/joi": "^17.1.1", "@middy/core": "^2.4.1", "@middy/http-error-handler": "^2.4.1", @@ -80,20 +81,20 @@ "@types/async-retry": "^1.4.2", "@types/chai-subset": "^1.3.3", "@types/qs": "^6.9.7", + "@types/semver": "^7.5.8", "@types/sinon": "^10.0.6", "@types/stats-lite": "^2.2.0", "@uniswap/default-token-list": "^11.13.0", "@uniswap/permit2-sdk": "^1.3.0", "@uniswap/router-sdk": "^1.14.0", "@uniswap/sdk-core": "^5.9.0", - "@types/semver": "^7.5.8", "@uniswap/smart-order-router": "4.8.0", "@uniswap/token-lists": "^1.0.0-beta.33", "@uniswap/universal-router-sdk": "^4.6.1", "@uniswap/v2-sdk": "^4.6.1", "@uniswap/v3-periphery": "^1.4.4", "@uniswap/v3-sdk": "^3.17.1", - "@uniswap/v4-sdk": "^1.10.0", + "@uniswap/v4-sdk": "^1.11.2", "async-retry": "^1.3.1", "aws-cdk-lib": "^2.137.0", "aws-embedded-metrics": "^2.0.6", diff --git a/test/jest/unit/handlers/util/v4HooksPoolsFiltering.test.ts b/test/jest/unit/handlers/util/v4HooksPoolsFiltering.test.ts new file mode 100644 index 0000000000..cc8b16b9fc --- /dev/null +++ b/test/jest/unit/handlers/util/v4HooksPoolsFiltering.test.ts @@ -0,0 +1,597 @@ +import { describe, expect } from '@jest/globals' +import { v4HooksPoolsFiltering } from '../../../../../lib/util/v4HooksPoolsFiltering' +import { V4SubgraphPool } from '@uniswap/smart-order-router' + +describe('v4HooksPoolsFiltering', () => { + it('before swap hooks pool is filtered out', () => { + const beforeSwapHookAddress = '0x0000000000000000000000000000000000000080' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeSwapHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[0]]) + }) + + it('after swap hooks pool is filtered out', () => { + const afterSwapHookAddress = '0x0000000000000000000000000000000000000040' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterSwapHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[0]]) + }) + + it('dynamic fee hooks pool is filtered out', () => { + const dynamicFee = `8388608` + + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: dynamicFee, + tickSpacing: '1', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[0]]) + }) + + it('before initialize hooks pool is not filtered out', () => { + const beforeInitializeHookAddress = '0x0000000000000000000000000000000000002000' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeInitializeHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after initialize hooks pool is not filtered out', () => { + const afterInitializeHookAddress = '0x0000000000000000000000000000000000001000' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterInitializeHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('before add liquidity hooks pool is not filtered out', () => { + const beforeAddLiquidityHookAddress = '0x0000000000000000000000000000000000000800' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeAddLiquidityHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after add liquidity hooks pool is not filtered out', () => { + const afterAddLiquidityHookAddress = '0x0000000000000000000000000000000000000400' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterAddLiquidityHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('before remove liquidity hooks pool is not filtered out', () => { + const beforeRemoveLiquidityHookAddress = '0x0000000000000000000000000000000000000200' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeRemoveLiquidityHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after remove liquidity hooks pool is not filtered out', () => { + const afterRemoveLiquidityHookAddress = '0x0000000000000000000000000000000000000100' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterRemoveLiquidityHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('before donate hooks pool is not filtered out', () => { + const beforeDonateHookAddress = '0x0000000000000000000000000000000000000020' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeDonateHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after donate hooks pool is not filtered out', () => { + const afterDonateHookAddress = '0x0000000000000000000000000000000000000010' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterDonateHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('before swap returns delta hooks pool is not filtered out', () => { + const beforeSwapReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000008' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeSwapReturnsDeltaHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after swap returns delta hooks pool is not filtered out', () => { + const afterSwapReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000004' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterSwapReturnsDeltaHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after add liquidity returns delta hooks pool is not filtered out', () => { + const afterAddLiquidityReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000002' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterAddLiquidityReturnsDeltaHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after remove liquidity returns delta hooks pool is not filtered out', () => { + const afterRemoveLiquidityReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000001' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterRemoveLiquidityReturnsDeltaHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('11 hooks pool to retain 10 pools', () => { + const afterRemoveLiquidityReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000001' + const v4Pools: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'].map((i) => ({ + id: i, + feeTier: '500', + tickSpacing: i, + hooks: afterRemoveLiquidityReturnsDeltaHookAddress, + liquidity: i, + token0: { + id: 'USDC', + }, + token1: { + id: 'ETH', + }, + tvlETH: Number(i), + tvlUSD: Number(i), + })) + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual(v4Pools.slice(1, 11)) + }) +})