diff --git a/src/Constants.ts b/src/Constants.ts index d058c29..d5e1e54 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -178,12 +178,7 @@ const LISK_CONSTANTS: chainConstants = { startBlock: 15591759, updateDelta: 60 * 60, // 1 hour }, - rewardToken: { - address: "0x7f9AdFbd38b669F03d1d11000Bc76b9AaEA28A81", - symbol: "XVELO", - decimals: 18, - createdBlock: 15405191, - }, + rewardToken: findToken(LISK_WHITELISTED_TOKENS, "XVELO"), rpcURL: process.env.MODE_RPC_URL || "wss://lisk.drpc.org", stablecoinPools: [], stablecoinPoolAddresses: [], @@ -205,12 +200,7 @@ const MODE_CONSTANTS: chainConstants = { startBlock: 15591759, updateDelta: 60 * 60, // 1 hour }, - rewardToken: { - address: "0x7f9AdFbd38b669F03d1d11000Bc76b9AaEA28A81", - symbol: "XVELO", - decimals: 18, - createdBlock: 15405191, - }, + rewardToken: findToken(MODE_WHITELISTED_TOKENS, "XVELO"), rpcURL: process.env.MODE_RPC_URL || "https://mainnet.mode.network", stablecoinPools: MODE_STABLECOIN_POOLS, stablecoinPoolAddresses: MODE_STABLECOIN_POOLS.map((pool) => pool.address), diff --git a/src/EventHandlers/CLPool.ts b/src/EventHandlers/CLPool.ts index 756718a..209fcb4 100644 --- a/src/EventHandlers/CLPool.ts +++ b/src/EventHandlers/CLPool.ts @@ -18,23 +18,22 @@ import { multiplyBase1e18, abs } from "../Maths"; import { updateLiquidityPoolAggregator } from "../Aggregators/LiquidityPoolAggregator"; /** - * Updates the fee amounts for a CLPoolAggregator based on event data. - * - * This function calculates the new total fees for both tokens in a liquidity pool - * and their equivalent value in USD. It normalizes the token amounts to a base of 1e18 - * for consistent calculations and updates the total fees in the aggregator. - * - * @param clPoolAggregator - The current state of the CLPoolAggregator, containing existing fee data. - * @param event - The event data containing the fee amounts for token0 and token1. - * @param token0Instance - The instance of token0, containing its decimals and price per USD. - * @param token1Instance - The instance of token1, containing its decimals and price per USD. - * - * @returns An object containing the updated total fees for token0, token1, and their equivalent in USD. - * - * The returned object has the following structure: - * - `totalFees0`: The updated total fees for token0, normalized to 1e18. - * - `totalFees1`: The updated total fees for token1, normalized to 1e18. - * - `totalFeesUSD`: The updated total fees in USD, calculated using the normalized token fees and their prices. + * Updates the fee-related metrics for a Concentrated Liquidity Pool. + * + * This function calculates the total fees collected in both tokens and USD value. + * The USD values are computed by: + * 1. Normalizing token amounts to 18 decimals + * 2. Multiplying by the token's USD price + * + * @param liquidityPoolAggregator - The current state of the liquidity pool + * @param event - The event containing fee collection data (amount0, amount1) + * @param token0Instance - Token instance for token0, containing decimals and price data + * @param token1Instance - Token instance for token1, containing decimals and price data + * + * @returns {Object} Updated fee metrics + * @returns {bigint} .totalFees0 - Cumulative fees collected in token0 + * @returns {bigint} .totalFees1 - Cumulative fees collected in token1 + * @returns {bigint} .totalFeesUSD - Cumulative fees collected in USD */ function updateCLPoolFees( liquidityPoolAggregator: LiquidityPoolAggregator, @@ -77,6 +76,32 @@ function updateCLPoolFees( return tokenUpdateData; } +/** + * Updates the liquidity-related metrics for a Concentrated Liquidity Pool. + * + * This function calculates both addition and subtraction of liquidity to handle + * various pool operations (mint, burn, collect). For each token: + * 1. Normalizes reserve amounts to 18 decimals + * 2. Calculates USD value using token prices + * 3. Computes both addition and subtraction scenarios + * + * @param liquidityPoolAggregator - The current state of the liquidity pool + * @param event - The event containing liquidity change data (amount0, amount1) + * @param token0Instance - Token instance for token0, containing decimals and price data + * @param token1Instance - Token instance for token1, containing decimals and price data + * + * @returns {Object} Updated liquidity metrics + * @returns {bigint} .reserve0 - New token0 reserve amount + * @returns {bigint} .reserve1 - New token1 reserve amount + * @returns {bigint} .addTotalLiquidity0USD - USD value if adding token0 liquidity + * @returns {bigint} .subTotalLiquidity0USD - USD value if removing token0 liquidity + * @returns {bigint} .addTotalLiquidity1USD - USD value if adding token1 liquidity + * @returns {bigint} .subTotalLiquidity1USD - USD value if removing token1 liquidity + * @returns {bigint} .addTotalLiquidityUSD - Total USD value for liquidity addition + * @returns {bigint} .subTotalLiquidityUSD - Total USD value for liquidity removal + * @returns {bigint} .normalizedReserve0 - Reserve0 normalized to 18 decimals + * @returns {bigint} .normalizedReserve1 - Reserve1 normalized to 18 decimals + */ function updateCLPoolLiquidity( liquidityPoolAggregator: LiquidityPoolAggregator, event: any, @@ -85,63 +110,76 @@ function updateCLPoolLiquidity( ) { let tokenUpdateData = { - totalLiquidityUSD: 0n, + addTotalLiquidity0USD: 0n, + subTotalLiquidity0USD: 0n, + addTotalLiquidity1USD: 0n, + subTotalLiquidity1USD: 0n, + addTotalLiquidityUSD: 0n, + subTotalLiquidityUSD: 0n, reserve0: 0n, reserve1: 0n, normalizedReserve0: 0n, normalizedReserve1: 0n, }; - // Update reserves regardles of whether the token is priced - tokenUpdateData.reserve0 += event.params.amount0; - - tokenUpdateData.reserve1 += event.params.amount1; + // Return new token reserve amounts + tokenUpdateData.reserve0 = event.params.amount0; + tokenUpdateData.reserve1 = event.params.amount1; + // Update liquidity amounts in USD. Computes both the addition and subtraction of liquidity + // from event params. if (token0Instance) { - tokenUpdateData.normalizedReserve0 += normalizeTokenAmountTo1e18( - event.params.amount0, + const normalizedReserveAdd0 = normalizeTokenAmountTo1e18( + liquidityPoolAggregator.reserve0 + tokenUpdateData.reserve0, Number(token0Instance.decimals || 18) ); + const normalizedReserveSub0 = normalizeTokenAmountTo1e18( + liquidityPoolAggregator.reserve0 - tokenUpdateData.reserve0, + Number(token0Instance.decimals || 18) + ); + + tokenUpdateData.addTotalLiquidity0USD = multiplyBase1e18( + normalizedReserveAdd0, + liquidityPoolAggregator.token0Price + ); - tokenUpdateData.totalLiquidityUSD += multiplyBase1e18( - tokenUpdateData.normalizedReserve0, + tokenUpdateData.subTotalLiquidity0USD = multiplyBase1e18( + normalizedReserveSub0, liquidityPoolAggregator.token0Price ); } if (token1Instance) { - tokenUpdateData.normalizedReserve1 += normalizeTokenAmountTo1e18( - event.params.amount1, + const normalizedReserveAdd1 = normalizeTokenAmountTo1e18( + liquidityPoolAggregator.reserve1 + tokenUpdateData.reserve1, + Number(token1Instance.decimals || 18) + ); + const normalizedReserveSub1 = normalizeTokenAmountTo1e18( + liquidityPoolAggregator.reserve1 - tokenUpdateData.reserve1, Number(token1Instance.decimals || 18) ); - tokenUpdateData.totalLiquidityUSD += multiplyBase1e18( - tokenUpdateData.normalizedReserve1, + + tokenUpdateData.addTotalLiquidity1USD = multiplyBase1e18( + normalizedReserveAdd1, + liquidityPoolAggregator.token1Price + ); + + tokenUpdateData.subTotalLiquidity1USD = multiplyBase1e18( + normalizedReserveSub1, liquidityPoolAggregator.token1Price ); } + tokenUpdateData.addTotalLiquidityUSD = tokenUpdateData.addTotalLiquidity0USD + tokenUpdateData.addTotalLiquidity1USD; + tokenUpdateData.subTotalLiquidityUSD = tokenUpdateData.subTotalLiquidity0USD + tokenUpdateData.subTotalLiquidity1USD; + return tokenUpdateData; } CLPool.Burn.handlerWithLoader({ loader: async ({ event, context }) => { - const pool_id = event.srcAddress; - - const liquidityPoolAggregator = await context.LiquidityPoolAggregator.get(pool_id); - - if (!liquidityPoolAggregator) { - context.log.error(`CLPoolAggregator ${pool_id} not found during mint`); - return null; - } - - const [token0Instance, token1Instance] = await Promise.all([ - context.Token.get(liquidityPoolAggregator.token0_id), - context.Token.get(liquidityPoolAggregator.token1_id), - ]); - - return { liquidityPoolAggregator, token0Instance, token1Instance }; + return null; }, - handler: async ({ event, context, loaderReturn }) => { const entity: CLPool_Burn = { id: `${event.chainId}_${event.block.number}_${event.logIndex}`, @@ -157,35 +195,6 @@ CLPool.Burn.handlerWithLoader({ }; context.CLPool_Burn.set(entity); - - if (loaderReturn) { - const { liquidityPoolAggregator, token0Instance, token1Instance } = loaderReturn; - - const tokenUpdateData = updateCLPoolLiquidity( - liquidityPoolAggregator, - event, - token0Instance, - token1Instance - ); - - const liquidityPoolDiff = { - reserve0: - liquidityPoolAggregator.reserve0 - tokenUpdateData.reserve0, - reserve1: - liquidityPoolAggregator.reserve1 - tokenUpdateData.reserve1, - totalLiquidityUSD: - liquidityPoolAggregator.totalLiquidityUSD - - tokenUpdateData.totalLiquidityUSD, - lastUpdatedTimestamp: new Date(event.block.timestamp * 1000), - }; - - updateLiquidityPoolAggregator( - liquidityPoolDiff, - liquidityPoolAggregator, - liquidityPoolDiff.lastUpdatedTimestamp, - context - ); - } }, }); @@ -224,23 +233,31 @@ CLPool.Collect.handlerWithLoader({ context.CLPool_Collect.set(entity); - if (loaderReturn && loaderReturn.liquidityPoolAggregator) { + if (loaderReturn) { const { liquidityPoolAggregator, token0Instance, token1Instance } = loaderReturn; - const tokenUpdateData = updateCLPoolFees( + const tokenUpdateData = updateCLPoolLiquidity( liquidityPoolAggregator, event, token0Instance, token1Instance ); + const liquidityPoolDiff = { + reserve0: liquidityPoolAggregator.reserve0 - tokenUpdateData.reserve0, + reserve1: liquidityPoolAggregator.reserve1 - tokenUpdateData.reserve1, + totalLiquidityUSD: tokenUpdateData.subTotalLiquidityUSD, + lastUpdatedTimestamp: new Date(event.block.timestamp * 1000), + }; + updateLiquidityPoolAggregator( - tokenUpdateData, + liquidityPoolDiff, liquidityPoolAggregator, - new Date(event.block.timestamp * 1000), + liquidityPoolDiff.lastUpdatedTimestamp, context ); } + }, }); @@ -387,13 +404,9 @@ CLPool.Mint.handlerWithLoader({ ); const liquidityPoolDiff = { - reserve0: - liquidityPoolAggregator.reserve0 + tokenUpdateData.reserve0, - reserve1: - liquidityPoolAggregator.reserve1 + tokenUpdateData.reserve1, - totalLiquidityUSD: - liquidityPoolAggregator.totalLiquidityUSD + - tokenUpdateData.totalLiquidityUSD, + reserve0: liquidityPoolAggregator.reserve0 + tokenUpdateData.reserve0, + reserve1: liquidityPoolAggregator.reserve1 + tokenUpdateData.reserve1, + totalLiquidityUSD: tokenUpdateData.addTotalLiquidityUSD, lastUpdatedTimestamp: new Date(event.block.timestamp * 1000), }; diff --git a/test/EventHandlers/CLPool/CLPool.test.ts b/test/EventHandlers/CLPool/CLPool.test.ts index 1b3d519..8f18984 100644 --- a/test/EventHandlers/CLPool/CLPool.test.ts +++ b/test/EventHandlers/CLPool/CLPool.test.ts @@ -38,13 +38,18 @@ describe("CLPool Event Handlers", () => { let mockEvent: any; let eventData: any; + + const { mockToken0Data, mockToken1Data, mockLiquidityPoolData } = setupCommon(); + let expectations: any = { amount0In: 100n * 10n ** 18n, amount1In: 100n * 10n ** 6n, - totalLiquidity: 400n * 10n ** 18n, + totalLiquidityUSD: 0n, }; - const { mockToken0Data, mockToken1Data, mockLiquidityPoolData } = setupCommon(); + expectations.totalLiquidityUSD = + (mockLiquidityPoolData.reserve0 + expectations.amount0In) * mockToken0Data.pricePerUSDNew / ( 10n ** (mockToken0Data.decimals) ) + + (mockLiquidityPoolData.reserve1 + expectations.amount1In) * mockToken1Data.pricePerUSDNew / ( 10n ** (mockToken1Data.decimals) ); let postEventDB: any; let collectedEntity: any; @@ -109,7 +114,7 @@ describe("CLPool Event Handlers", () => { }); it("should update the total liquidity in USD correctly", () => { expect(diff.totalLiquidityUSD).to.equal( - expectations.totalLiquidity, + expectations.totalLiquidityUSD, "Liquidity should be updated with appropriate prices"); }); }); @@ -172,28 +177,6 @@ describe("CLPool Event Handlers", () => { expect(collectedEntity?.amount1).to.equal(expectations.amount1In); }); - describe("CLPool Aggregator", () => { - let diff: any; - beforeEach(() => { - [diff] = updateLiquidityPoolAggregatorStub.firstCall.args; - }); - - it("should update the reserves", () => { - expect(diff.reserve0).to.equal( - mockLiquidityPoolData.reserve0 - - expectations.amount0In, - "Reserve 0 should be appropriately updated"); - expect(diff.reserve1).to.equal( - mockLiquidityPoolData.reserve1 - - expectations.amount1In, - "Reserve 1 should be appropriately updated"); - }); - it("should update the total liquidity in USD correctly", () => { - expect(diff.totalLiquidityUSD).to.equal( - expectations.totalLiquidity, - "Liquidity should be updated with appropriate prices"); - }); - }); }); describe("Collect Event", () => { @@ -202,18 +185,20 @@ describe("CLPool Event Handlers", () => { let setupDB: any; const { mockToken0Data, mockToken1Data, mockLiquidityPoolData } = setupCommon(); const poolId = mockLiquidityPoolData.id; - const token0Id = mockToken0Data.id; - const token1Id = mockToken1Data.id; - - const eventFees = { - amount0: 100n * 10n ** 18n, - amount1: 200n * 10n ** 6n, + let expectations: any = { + amount0In: 100n * 10n ** 18n, + amount1In: 100n * 10n ** 6n, + totalLiquidityUSD: 0n, }; + expectations.totalLiquidityUSD = + (mockLiquidityPoolData.reserve0 - expectations.amount0In) * mockToken0Data.pricePerUSDNew / ( 10n ** (mockToken0Data.decimals) ) + + (mockLiquidityPoolData.reserve1 - expectations.amount1In) * mockToken1Data.pricePerUSDNew / ( 10n ** (mockToken1Data.decimals) ); + beforeEach(() => { mockEventData = { - amount0: eventFees.amount0, - amount1: eventFees.amount1, + amount0: expectations.amount0In, + amount1: expectations.amount1In, mockEventData: { block: { number: 123456, @@ -246,20 +231,31 @@ describe("CLPool Event Handlers", () => { const expectedId = `${mockEvent.chainId}_${mockEvent.block.number}_${mockEvent.logIndex}`; const collectEntity = setupDB.entities.CLPool_Collect.get(expectedId); expect(collectEntity).to.not.be.undefined; - expect(collectEntity?.amount0).to.equal(100n * 10n ** 18n); - expect(collectEntity?.amount1).to.equal(200n * 10n ** 6n); + expect(collectEntity?.amount0).to.equal(expectations.amount0In); + expect(collectEntity?.amount1).to.equal(expectations.amount1In); }); - it("should update LiquidityPoolAggregator", async () => { - expect(updateLiquidityPoolAggregatorStub.calledOnce).to.be.true; - const [diff] = updateLiquidityPoolAggregatorStub.firstCall.args; + describe("CLPool Aggregator", () => { + let diff: any; + beforeEach(() => { + [diff] = updateLiquidityPoolAggregatorStub.firstCall.args; + }); - expect(diff.totalFees0).to.equal( - mockLiquidityPoolData.totalFees0 + eventFees.amount0 - ); - expect(diff.totalFees1).to.equal( - mockLiquidityPoolData.totalFees1 + eventFees.amount1 - ); + it("should update the reserves", () => { + expect(diff.reserve0).to.equal( + mockLiquidityPoolData.reserve0 - + expectations.amount0In, + "Reserve 0 should be appropriately updated"); + expect(diff.reserve1).to.equal( + mockLiquidityPoolData.reserve1 - + expectations.amount1In, + "Reserve 1 should be appropriately updated"); + }); + it("should update the total liquidity in USD correctly", () => { + expect(diff.totalLiquidityUSD).to.equal( + expectations.totalLiquidityUSD, + "Liquidity should be updated with appropriate prices"); + }); }); }); }); @@ -274,8 +270,6 @@ describe("CLPool Event Handlers", () => { }; const { mockToken0Data, mockToken1Data, mockLiquidityPoolData} = setupCommon(); const poolId = mockLiquidityPoolData.id; - const token0Id = mockToken0Data.id; - const token1Id = mockToken1Data.id; beforeEach(() => { mockEventData = { diff --git a/test/EventHandlers/Pool/common.ts b/test/EventHandlers/Pool/common.ts index 91be786..2d6f8da 100644 --- a/test/EventHandlers/Pool/common.ts +++ b/test/EventHandlers/Pool/common.ts @@ -31,9 +31,9 @@ export function setupCommon() { token0_address: mockToken0Data.address, token1_address: mockToken1Data.address, isStable: false, - reserve0: 100n * TEN_TO_THE_18_BI, - reserve1: 100n * TEN_TO_THE_6_BI, - totalLiquidityUSD: 200n * TEN_TO_THE_18_BI, + reserve0: 200n * TEN_TO_THE_18_BI, + reserve1: 200n * TEN_TO_THE_6_BI, + totalLiquidityUSD: 400n * TEN_TO_THE_18_BI, totalVolume0: 1n * TEN_TO_THE_18_BI, totalVolume1: 1n * TEN_TO_THE_6_BI, totalVolumeUSD: 10n * TEN_TO_THE_18_BI,