From 2d9a745e249566926c0156877702c3537a1a5909 Mon Sep 17 00:00:00 2001 From: "jrab227@gmail.com" Date: Wed, 26 Feb 2025 20:02:52 -0500 Subject: [PATCH 1/2] Refactor Pool loader sussess typing. --- src/EventHandlers/CLPool.ts | 153 +++--------- src/EventHandlers/Pool.ts | 459 ++++++++++++++++-------------------- src/Pools/common.ts | 85 +++++++ 3 files changed, 322 insertions(+), 375 deletions(-) create mode 100644 src/Pools/common.ts diff --git a/src/EventHandlers/CLPool.ts b/src/EventHandlers/CLPool.ts index c6e6749..c84472e 100644 --- a/src/EventHandlers/CLPool.ts +++ b/src/EventHandlers/CLPool.ts @@ -10,14 +10,14 @@ import { CLPool_SetFeeProtocol, CLPool_Swap, LiquidityPoolAggregator, - Dynamic_Fee_Swap_Module, Token, } from "generated"; import { refreshTokenPrice } from "../PriceOracle"; import { normalizeTokenAmountTo1e18 } from "../Helpers"; import { multiplyBase1e18, abs } from "../Maths"; import { updateLiquidityPoolAggregator } from "../Aggregators/LiquidityPoolAggregator"; -import { loaderContext, handlerContext, CLPool_Swap_event } from "generated/src/Types.gen"; +import { handlerContext, CLPool_Swap_event } from "generated/src/Types.gen"; +import { fetchPoolLoaderData } from "../Pools/common"; /** * Updates the fee-related metrics for a Concentrated Liquidity Pool. @@ -174,86 +174,6 @@ function updateCLPoolLiquidity( return tokenUpdateData; } -type CLPoolSuccessType = "success"; -type CLPoolLoaderErrorType = "LiquidityPoolAggregatorNotFoundError" | "TokenNotFoundError"; - -type CLPoolLoaderStatus = CLPoolSuccessType | CLPoolLoaderErrorType; - -type CLPoolLoaderData = { - liquidityPoolAggregator: LiquidityPoolAggregator; - token0Instance: Token; - token1Instance: Token; -} - -type CLPoolLoaderSuccess = { - _type: "success"; -} & CLPoolLoaderData; - -type CLPoolLoaderTokenError = { - _type: "TokenNotFoundError"; - message: string; - available: { - liquidityPoolAggregator: LiquidityPoolAggregator; - token0Instance?: Token; - token1Instance?: Token; - } -} - -type CLPoolLoaderLiquidityPoolError = { - _type: "LiquidityPoolAggregatorNotFoundError"; - message: string; - available: Partial -} - -type CLPoolLoaderError = T extends "TokenNotFoundError" ? - CLPoolLoaderTokenError : CLPoolLoaderLiquidityPoolError; - -type CLPoolLoader = T extends "success" ? - CLPoolLoaderSuccess : - T extends CLPoolLoaderErrorType ? - CLPoolLoaderError : never; - -/** - * Fetches the liquidity pool aggregator and token instances for a given event. - * - * @param event - The event containing the pool address - * @param context - The context object containing the database - * - * @returns {CLPoolLoader} - The loader return object - */ -async function fetchCLPoolLoaderData(liquidityPoolAddress: string, context: loaderContext, chainId: number): Promise > { - const liquidityPoolAggregator = await context.LiquidityPoolAggregator.get(liquidityPoolAddress); - if (!liquidityPoolAggregator) { - return { - _type: "LiquidityPoolAggregatorNotFoundError", - message: `LiquidityPoolAggregator ${liquidityPoolAddress} not found on chain ${chainId}`, - available: { - liquidityPoolAggregator: undefined, - token0Instance: undefined, - token1Instance: undefined - } - }; - } - const [token0Instance, token1Instance] = - await Promise.all([ - context.Token.get(liquidityPoolAggregator.token0_id), - context.Token.get(liquidityPoolAggregator.token1_id), - ]); - - if (token0Instance == undefined || token1Instance == undefined) { - return { - _type: "TokenNotFoundError", - message: `Token not found for pool ${liquidityPoolAddress} on chain ${chainId}`, - available: { - liquidityPoolAggregator, - token0Instance, - token1Instance - } }; - } - - return { _type: "success", liquidityPoolAggregator, token0Instance, token1Instance }; -} - CLPool.Burn.handlerWithLoader({ loader: async ({ event, context }) => { return null; @@ -281,7 +201,7 @@ CLPool.Burn.handlerWithLoader({ CLPool.Collect.handlerWithLoader({ loader: async ({ event, context }) => { - return fetchCLPoolLoaderData(event.srcAddress, context, event.chainId); + return fetchPoolLoaderData(event.srcAddress, context, event.chainId); }, handler: async ({ event, context, loaderReturn }) => { const entity: CLPool_Collect = { @@ -326,13 +246,23 @@ CLPool.Collect.handlerWithLoader({ context, event.block.number ); + return + case "TokenNotFoundError": + context.log.error(loaderReturn.message); + return; + case "LiquidityPoolAggregatorNotFoundError": + context.log.error(loaderReturn.message); + return; + default: + const _exhaustiveCheck: never = loaderReturn; + return _exhaustiveCheck; } }, }); CLPool.CollectFees.handlerWithLoader({ loader: async ({ event, context }) => { - return fetchCLPoolLoaderData(event.srcAddress, context, event.chainId); + return fetchPoolLoaderData(event.srcAddress, context, event.chainId); }, handler: async ({ event, context, loaderReturn }) => { const entity: CLPool_CollectFees = { @@ -387,6 +317,16 @@ CLPool.CollectFees.handlerWithLoader({ context, event.block.number ); + return + case "TokenNotFoundError": + context.log.error(loaderReturn.message); + return; + case "LiquidityPoolAggregatorNotFoundError": + context.log.error(loaderReturn.message); + return; + default: + const _exhaustiveCheck: never = loaderReturn; + return _exhaustiveCheck; } }, }); @@ -447,7 +387,7 @@ CLPool.Initialize.handler(async ({ event, context }) => { CLPool.Mint.handlerWithLoader({ loader: async ({ event, context }) => { - return fetchCLPoolLoaderData(event.srcAddress, context, event.chainId); + return fetchPoolLoaderData(event.srcAddress, context, event.chainId); }, handler: async ({ event, context, loaderReturn }) => { const entity: CLPool_Mint = { @@ -494,6 +434,16 @@ CLPool.Mint.handlerWithLoader({ context, event.block.number ); + return; + case "TokenNotFoundError": + context.log.error(loaderReturn.message); + return; + case "LiquidityPoolAggregatorNotFoundError": + context.log.error(loaderReturn.message); + return; + default: + const _exhaustiveCheck: never = loaderReturn; + return _exhaustiveCheck; } }, }); @@ -619,7 +569,7 @@ const updateLiquidityPoolAggregatorDiffSwap = (data: SwapEntityData, reserveResu CLPool.Swap.handlerWithLoader({ loader: async ({ event, context }) => { - return fetchCLPoolLoaderData(event.srcAddress, context, event.chainId); + return fetchPoolLoaderData(event.srcAddress, context, event.chainId); }, handler: async ({ event, context, loaderReturn }) => { const blockDatetime = new Date(event.block.timestamp * 1000); @@ -688,38 +638,9 @@ CLPool.Swap.handlerWithLoader({ context, event.block.number ); - return; case "TokenNotFoundError": - let tokenNotFoundSwapEntityData: SwapEntityData = { - liquidityPoolAggregator: loaderReturn.available.liquidityPoolAggregator, - token0Instance: loaderReturn.available.token0Instance, - token1Instance: loaderReturn.available.token1Instance, - tokenUpdateData, - liquidityPoolAggregatorDiff, - } - - tokenNotFoundSwapEntityData = await updateToken0SwapData(tokenNotFoundSwapEntityData, event, context); - tokenNotFoundSwapEntityData = await updateToken1SwapData(tokenNotFoundSwapEntityData, event, context); - - let tokenNotFoundReserveResult = updateCLPoolLiquidity( - tokenNotFoundSwapEntityData.liquidityPoolAggregator, - event, - tokenNotFoundSwapEntityData.token0Instance, - tokenNotFoundSwapEntityData.token1Instance - ); - - // Merge with previous liquidity pool aggregator values. - tokenNotFoundSwapEntityData = updateLiquidityPoolAggregatorDiffSwap(tokenNotFoundSwapEntityData, tokenNotFoundReserveResult); - - updateLiquidityPoolAggregator( - tokenNotFoundSwapEntityData.liquidityPoolAggregatorDiff, - tokenNotFoundSwapEntityData.liquidityPoolAggregator, - blockDatetime, - context, - event.block.number - ); - + context.log.error(loaderReturn.message); return; case "LiquidityPoolAggregatorNotFoundError": context.log.error(loaderReturn.message); diff --git a/src/EventHandlers/Pool.ts b/src/EventHandlers/Pool.ts index 1606597..daf465f 100644 --- a/src/EventHandlers/Pool.ts +++ b/src/EventHandlers/Pool.ts @@ -1,13 +1,11 @@ import { Pool, Pool_Swap, Pool_Sync, Pool_Mint, Pool_Burn } from "generated"; -import { LiquidityPoolAggregator, Token, User } from "./../src/Types.gen"; import { normalizeTokenAmountTo1e18 } from "./../Helpers"; -import { abs, multiplyBase1e18 } from "./../Maths"; +import { multiplyBase1e18 } from "./../Maths"; import { refreshTokenPrice } from "../PriceOracle"; import { updateLiquidityPoolAggregator } from "../Aggregators/LiquidityPoolAggregator"; import { TokenIdByChain } from "../Constants"; - -// Helper function to get checksum address +import { fetchPoolLoaderData } from "../Pools/common"; Pool.Mint.handler(async ({ event, context }) => { const entity: Pool_Mint = { @@ -46,113 +44,79 @@ Pool.Burn.handler(async ({ event, context }) => { Pool.Fees.handlerWithLoader({ loader: async ({ event, context }) => { - const liquidityPoolAggregator = await context.LiquidityPoolAggregator.get( - event.srcAddress - ); - - if (!liquidityPoolAggregator) { - context.log.error(`LiquidityPoolAggregator ${event.srcAddress} not found during collect fees on chain ${event.chainId}`); - return null; - } - - const [token0Instance, token1Instance] = await Promise.all([ - context.Token.get( - TokenIdByChain(liquidityPoolAggregator.token0_address, event.chainId) - ), - context.Token.get( - TokenIdByChain(liquidityPoolAggregator.token1_address, event.chainId) - ), - ]); - - return { liquidityPoolAggregator, token0Instance, token1Instance }; + return fetchPoolLoaderData(event.srcAddress, context, event.chainId); }, handler: async ({ event, context, loaderReturn }) => { - if (!loaderReturn) return; + switch (loaderReturn._type) { + case "success": + const { liquidityPoolAggregator, token0Instance, token1Instance } = loaderReturn; + + let tokenUpdateData = { + totalFees0: event.params.amount0, + totalFees1: event.params.amount1, + totalFeesNormalized0: 0n, + totalFeesNormalized1: 0n, + totalFeesUSD: 0n, + totalFeesUSDWhitelisted: 0n, + }; - const { liquidityPoolAggregator, token0Instance, token1Instance } = loaderReturn; + tokenUpdateData.totalFeesNormalized0 = normalizeTokenAmountTo1e18( + event.params.amount0, + Number(token0Instance.decimals) + ); + const token0FeesUSD = multiplyBase1e18( + tokenUpdateData.totalFeesNormalized0, + token0Instance.pricePerUSDNew + ); - let tokenUpdateData = { - totalFees0: event.params.amount0, - totalFees1: event.params.amount1, - totalFeesNormalized0: 0n, - totalFeesNormalized1: 0n, - totalFeesUSD: 0n, - totalFeesUSDWhitelisted: 0n, - }; + tokenUpdateData.totalFeesUSD += token0FeesUSD; + tokenUpdateData.totalFeesUSDWhitelisted += (token0Instance.isWhitelisted) ? token0FeesUSD : 0n; - tokenUpdateData.totalFees0 = event.params.amount0 - if (token0Instance) { - tokenUpdateData.totalFeesNormalized0 = normalizeTokenAmountTo1e18( - event.params.amount0, - Number(token0Instance.decimals) - ); - const token0FeesUSD = multiplyBase1e18( - tokenUpdateData.totalFeesNormalized0, - token0Instance.pricePerUSDNew - ); - - tokenUpdateData.totalFeesUSD += token0FeesUSD; - tokenUpdateData.totalFeesUSDWhitelisted += (token0Instance.isWhitelisted) ? token0FeesUSD : 0n; - } + tokenUpdateData.totalFees1 = event.params.amount1 + tokenUpdateData.totalFeesNormalized1 = normalizeTokenAmountTo1e18( + event.params.amount1, + Number(token1Instance.decimals) + ); + const token1FeesUSD = multiplyBase1e18( + tokenUpdateData.totalFeesNormalized1, + token1Instance.pricePerUSDNew + ); + tokenUpdateData.totalFeesUSD += token1FeesUSD; + tokenUpdateData.totalFeesUSDWhitelisted += (token1Instance.isWhitelisted) ? token1FeesUSD: 0n; + + const liquidityPoolDiff = { + totalFees0: liquidityPoolAggregator.totalFees0 + tokenUpdateData.totalFees0, + totalFees1: liquidityPoolAggregator.totalFees1 + tokenUpdateData.totalFees1, + totalFeesUSD: liquidityPoolAggregator.totalFeesUSD + tokenUpdateData.totalFeesUSD, + totalFeesUSDWhitelisted: liquidityPoolAggregator.totalFeesUSDWhitelisted + tokenUpdateData.totalFeesUSDWhitelisted, + lastUpdatedTimestamp: new Date(event.block.timestamp * 1000), + }; - tokenUpdateData.totalFees1 = event.params.amount1 - if (token1Instance) { - tokenUpdateData.totalFeesNormalized1 = normalizeTokenAmountTo1e18( - event.params.amount1, - Number(token1Instance.decimals) - ); - const token1FeesUSD = multiplyBase1e18( - tokenUpdateData.totalFeesNormalized1, - token1Instance.pricePerUSDNew - ); - tokenUpdateData.totalFeesUSD += token1FeesUSD; - tokenUpdateData.totalFeesUSDWhitelisted += (token1Instance.isWhitelisted) ? token1FeesUSD: 0n; + updateLiquidityPoolAggregator( + liquidityPoolDiff, + liquidityPoolAggregator, + liquidityPoolDiff.lastUpdatedTimestamp, + context, + event.block.number + ); + return; + case "TokenNotFoundError": + context.log.error(loaderReturn.message); + return; + case "LiquidityPoolAggregatorNotFoundError": + context.log.error(loaderReturn.message); + return; + + default: + const _exhaustiveCheck: never = loaderReturn; + return _exhaustiveCheck; } - - const liquidityPoolDiff = { - totalFees0: liquidityPoolAggregator.totalFees0 + tokenUpdateData.totalFees0, - totalFees1: liquidityPoolAggregator.totalFees1 + tokenUpdateData.totalFees1, - totalFeesUSD: liquidityPoolAggregator.totalFeesUSD + tokenUpdateData.totalFeesUSD, - totalFeesUSDWhitelisted: liquidityPoolAggregator.totalFeesUSDWhitelisted + tokenUpdateData.totalFeesUSDWhitelisted, - lastUpdatedTimestamp: new Date(event.block.timestamp * 1000), - }; - - updateLiquidityPoolAggregator( - liquidityPoolDiff, - liquidityPoolAggregator, - liquidityPoolDiff.lastUpdatedTimestamp, - context, - event.block.number - ); - }, + } }); Pool.Swap.handlerWithLoader({ loader: async ({ event, context }) => { - const liquidityPoolAggregator = await context.LiquidityPoolAggregator.get( - event.srcAddress - ); - - if (!liquidityPoolAggregator) { - context.log.error(`LiquidityPoolAggregator ${event.srcAddress} not found during swap on chain ${event.chainId}`); - return null; - } - - const [token0Instance, token1Instance, user, isLiquidityPool] = - await Promise.all([ - context.Token.get(liquidityPoolAggregator.token0_id), - context.Token.get(liquidityPoolAggregator.token1_id), - context.User.get(event.params.to), - context.LiquidityPoolAggregator.get(event.params.to) - ]); - - return { - liquidityPoolAggregator, - token0Instance, - token1Instance, - to_address: event.params.to, - user, - }; + return fetchPoolLoaderData(event.srcAddress, context, event.chainId); }, handler: async ({ event, context, loaderReturn }) => { const blockDatetime = new Date(event.block.timestamp * 1000); @@ -174,24 +138,24 @@ Pool.Swap.handlerWithLoader({ }; context.Pool_Swap.set(entity); - if (loaderReturn) { - const { liquidityPoolAggregator, token0Instance, token1Instance, to_address, user } = - loaderReturn; - - let token0 = token0Instance; - let token1 = token1Instance; - - let tokenUpdateData = { - netAmount0: 0n, - netAmount1: 0n, - netVolumeToken0USD: 0n, - netVolumeToken1USD: 0n, - volumeInUSD: 0n, - volumeInUSDWhitelisted: 0n, - }; - - tokenUpdateData.netAmount0 = event.params.amount0In + event.params.amount0Out; - if (token0) { + switch (loaderReturn._type) { + case "success": + const { liquidityPoolAggregator, token0Instance, token1Instance} = + loaderReturn; + + let token0 = token0Instance; + let token1 = token1Instance; + + let tokenUpdateData = { + netAmount0: 0n, + netAmount1: 0n, + netVolumeToken0USD: 0n, + netVolumeToken1USD: 0n, + volumeInUSD: 0n, + volumeInUSDWhitelisted: 0n, + }; + + tokenUpdateData.netAmount0 = event.params.amount0In + event.params.amount0Out; try { token0 = await refreshTokenPrice(token0, event.block.number, event.block.timestamp, event.chainId, context); } catch (error) { @@ -205,10 +169,8 @@ Pool.Swap.handlerWithLoader({ normalizedAmount0, token0.pricePerUSDNew ); - } - tokenUpdateData.netAmount1 = event.params.amount1In + event.params.amount1Out; - if (token1) { + tokenUpdateData.netAmount1 = event.params.amount1In + event.params.amount1Out; try { token1 = await refreshTokenPrice(token1, event.block.number, event.block.timestamp, event.chainId, context); } catch (error) { @@ -222,67 +184,50 @@ Pool.Swap.handlerWithLoader({ normalizedAmount1, token1.pricePerUSDNew ); - } - - // Use volume from token 0 if it's priced, otherwise use token 1 - tokenUpdateData.volumeInUSD = - tokenUpdateData.netVolumeToken0USD != 0n - ? tokenUpdateData.netVolumeToken0USD - : tokenUpdateData.netVolumeToken1USD; - - // If both tokens are whitelisted, add the volume of token0 to the whitelisted volume - tokenUpdateData.volumeInUSDWhitelisted += (token0?.isWhitelisted && token1?.isWhitelisted) ? tokenUpdateData.netVolumeToken0USD : 0n; - - const liquidityPoolDiff = { - totalVolume0: liquidityPoolAggregator.totalVolume0 + tokenUpdateData.netAmount0, - totalVolume1: liquidityPoolAggregator.totalVolume1 + tokenUpdateData.netAmount1, - totalVolumeUSD: - liquidityPoolAggregator.totalVolumeUSD + tokenUpdateData.volumeInUSD, - totalVolumeUSDWhitelisted: - liquidityPoolAggregator.totalVolumeUSDWhitelisted + tokenUpdateData.volumeInUSDWhitelisted, - token0Price: - token0Instance?.pricePerUSDNew ?? liquidityPoolAggregator.token0Price, - token1Price: - token1Instance?.pricePerUSDNew ?? liquidityPoolAggregator.token1Price, - numberOfSwaps: liquidityPoolAggregator.numberOfSwaps + 1n, - token0IsWhitelisted: token0Instance?.isWhitelisted ?? false, - token1IsWhitelisted: token1Instance?.isWhitelisted ?? false, - lastUpdatedTimestamp: new Date(event.block.timestamp * 1000), - }; - - updateLiquidityPoolAggregator( - liquidityPoolDiff, - liquidityPoolAggregator, - liquidityPoolDiff.lastUpdatedTimestamp, - context, - event.block.number - ); - - - // Update user and create if they don't exist - if (!user) { - let newUser: User = { - id: to_address, - numberOfSwaps: 1n, - joined_at_timestamp: blockDatetime, + + // Use volume from token 0 if it's priced, otherwise use token 1 + tokenUpdateData.volumeInUSD = + tokenUpdateData.netVolumeToken0USD != 0n + ? tokenUpdateData.netVolumeToken0USD + : tokenUpdateData.netVolumeToken1USD; + + // If both tokens are whitelisted, add the volume of token0 to the whitelisted volume + tokenUpdateData.volumeInUSDWhitelisted += (token0?.isWhitelisted && token1?.isWhitelisted) ? tokenUpdateData.netVolumeToken0USD : 0n; + + const liquidityPoolDiff = { + totalVolume0: liquidityPoolAggregator.totalVolume0 + tokenUpdateData.netAmount0, + totalVolume1: liquidityPoolAggregator.totalVolume1 + tokenUpdateData.netAmount1, + totalVolumeUSD: + liquidityPoolAggregator.totalVolumeUSD + tokenUpdateData.volumeInUSD, + totalVolumeUSDWhitelisted: + liquidityPoolAggregator.totalVolumeUSDWhitelisted + tokenUpdateData.volumeInUSDWhitelisted, + token0Price: + token0Instance?.pricePerUSDNew ?? liquidityPoolAggregator.token0Price, + token1Price: + token1Instance?.pricePerUSDNew ?? liquidityPoolAggregator.token1Price, + numberOfSwaps: liquidityPoolAggregator.numberOfSwaps + 1n, + token0IsWhitelisted: token0Instance?.isWhitelisted ?? false, + token1IsWhitelisted: token1Instance?.isWhitelisted ?? false, + lastUpdatedTimestamp: new Date(event.block.timestamp * 1000), }; - context.User.set(newUser); - } else { - let existingUser: User = { - ...user, - numberOfSwaps: user.numberOfSwaps + 1n, - joined_at_timestamp: - user.joined_at_timestamp < - blockDatetime - ? user.joined_at_timestamp - : blockDatetime, - }; // for unordered head mode this correctly categorizes base users who may have joined early on optimism. - try { - context.User.set(existingUser); - } catch (error) { - console.log("Error updating user:", error); - } - } + + updateLiquidityPoolAggregator( + liquidityPoolDiff, + liquidityPoolAggregator, + liquidityPoolDiff.lastUpdatedTimestamp, + context, + event.block.number + ); + return; + case "TokenNotFoundError": + context.log.error(loaderReturn.message); + return; + case "LiquidityPoolAggregatorNotFoundError": + context.log.error(loaderReturn.message); + return; + default: + const _exhaustiveCheck: never = loaderReturn; + return _exhaustiveCheck; } }, }); @@ -293,92 +238,88 @@ Pool.Swap.handlerWithLoader({ */ Pool.Sync.handlerWithLoader({ loader: async ({ event, context }) => { - const liquidityPoolAggregator = await context.LiquidityPoolAggregator.get( - event.srcAddress - ); - - if (!liquidityPoolAggregator) { - context.log.error(`LiquidityPoolAggregator ${event.srcAddress} not found during sync on chain ${event.chainId}`); - 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 fetchPoolLoaderData(event.srcAddress, context, event.chainId); }, handler: async ({ event, context, loaderReturn }) => { - if (!loaderReturn) return; - const blockDatetime = new Date(event.block.timestamp * 1000); + switch (loaderReturn._type) { + case "success": + const { liquidityPoolAggregator, token0Instance, token1Instance} = loaderReturn; + const blockDatetime = new Date(event.block.timestamp * 1000); + const entity: Pool_Sync = { + id: `${event.chainId}_${liquidityPoolAggregator.id}_${event.block.number}_${event.logIndex}`, + reserve0: event.params.reserve0, + reserve1: event.params.reserve1, + sourceAddress: event.srcAddress, + timestamp: blockDatetime, + blockNumber: event.block.number, + logIndex: event.logIndex, + chainId: event.chainId, + transactionHash: event.transaction.hash + }; - const { liquidityPoolAggregator, token0Instance, token1Instance } = loaderReturn; + context.Pool_Sync.set(entity); - const entity: Pool_Sync = { - id: `${event.chainId}_${liquidityPoolAggregator.id}_${event.block.number}_${event.logIndex}`, - reserve0: event.params.reserve0, - reserve1: event.params.reserve1, - sourceAddress: event.srcAddress, - timestamp: blockDatetime, - blockNumber: event.block.number, - logIndex: event.logIndex, - chainId: event.chainId, - transactionHash: event.transaction.hash - }; + let tokenUpdateData = { + totalLiquidityUSD: 0n, + normalizedReserve0: 0n, + normalizedReserve1: 0n, + token0PricePerUSDNew: token0Instance?.pricePerUSDNew ?? liquidityPoolAggregator.token0Price, + token1PricePerUSDNew: token1Instance?.pricePerUSDNew ?? liquidityPoolAggregator.token1Price, + }; - context.Pool_Sync.set(entity); - let tokenUpdateData = { - totalLiquidityUSD: 0n, - normalizedReserve0: 0n, - normalizedReserve1: 0n, - token0PricePerUSDNew: token0Instance?.pricePerUSDNew ?? liquidityPoolAggregator.token0Price, - token1PricePerUSDNew: token1Instance?.pricePerUSDNew ?? liquidityPoolAggregator.token1Price, - }; + // Update price and liquidity if the token is priced + if (token0Instance) { + tokenUpdateData.normalizedReserve0 += normalizeTokenAmountTo1e18( + event.params.reserve0, + Number(token0Instance.decimals) + ); + tokenUpdateData.token0PricePerUSDNew = token0Instance.pricePerUSDNew; + tokenUpdateData.totalLiquidityUSD += multiplyBase1e18( + tokenUpdateData.normalizedReserve0, + tokenUpdateData.token0PricePerUSDNew + ); + } + if (token1Instance) { + tokenUpdateData.normalizedReserve1 += normalizeTokenAmountTo1e18( + event.params.reserve1, + Number(token1Instance.decimals) + ); + + tokenUpdateData.token1PricePerUSDNew = token1Instance.pricePerUSDNew; + tokenUpdateData.totalLiquidityUSD += multiplyBase1e18( + tokenUpdateData.normalizedReserve1, + tokenUpdateData.token1PricePerUSDNew + ); + } - // Update price and liquidity if the token is priced - if (token0Instance) { - tokenUpdateData.normalizedReserve0 += normalizeTokenAmountTo1e18( - event.params.reserve0, - Number(token0Instance.decimals) - ); - tokenUpdateData.token0PricePerUSDNew = token0Instance.pricePerUSDNew; - tokenUpdateData.totalLiquidityUSD += multiplyBase1e18( - tokenUpdateData.normalizedReserve0, - tokenUpdateData.token0PricePerUSDNew - ); - } + const liquidityPoolDiff = { + reserve0: event.params.reserve0, + reserve1: event.params.reserve1, + totalLiquidityUSD: tokenUpdateData.totalLiquidityUSD || liquidityPoolAggregator.totalLiquidityUSD, + token0Price: tokenUpdateData.token0PricePerUSDNew, + token1Price: tokenUpdateData.token1PricePerUSDNew, + lastUpdatedTimestamp: blockDatetime, + }; - if (token1Instance) { - tokenUpdateData.normalizedReserve1 += normalizeTokenAmountTo1e18( - event.params.reserve1, - Number(token1Instance.decimals) - ); - - tokenUpdateData.token1PricePerUSDNew = token1Instance.pricePerUSDNew; - tokenUpdateData.totalLiquidityUSD += multiplyBase1e18( - tokenUpdateData.normalizedReserve1, - tokenUpdateData.token1PricePerUSDNew - ); + updateLiquidityPoolAggregator( + liquidityPoolDiff, + liquidityPoolAggregator, + liquidityPoolDiff.lastUpdatedTimestamp, + context, + event.block.number + ); + return; + case "TokenNotFoundError": + context.log.error(loaderReturn.message); + return; + case "LiquidityPoolAggregatorNotFoundError": + context.log.error(loaderReturn.message); + return; + default: + const _exhaustiveCheck: never = loaderReturn; + return _exhaustiveCheck; } - - const liquidityPoolDiff = { - reserve0: event.params.reserve0, - reserve1: event.params.reserve1, - totalLiquidityUSD: tokenUpdateData.totalLiquidityUSD || liquidityPoolAggregator.totalLiquidityUSD, - token0Price: tokenUpdateData.token0PricePerUSDNew, - token1Price: tokenUpdateData.token1PricePerUSDNew, - lastUpdatedTimestamp: blockDatetime, - }; - - updateLiquidityPoolAggregator( - liquidityPoolDiff, - liquidityPoolAggregator, - liquidityPoolDiff.lastUpdatedTimestamp, - context, - event.block.number - ); - }, + } }); diff --git a/src/Pools/common.ts b/src/Pools/common.ts new file mode 100644 index 0000000..52cf8a2 --- /dev/null +++ b/src/Pools/common.ts @@ -0,0 +1,85 @@ +import { + LiquidityPoolAggregator, + Token +} from "generated"; +import { loaderContext } from "generated/src/Types.gen"; + +export type PoolLoaderSuccessType = "success"; +export type PoolLoaderErrorType = "LiquidityPoolAggregatorNotFoundError" | "TokenNotFoundError"; + +export type PoolLoaderStatus = PoolLoaderSuccessType | PoolLoaderErrorType; + +export type PoolLoaderData = { + liquidityPoolAggregator: LiquidityPoolAggregator; + token0Instance: Token; + token1Instance: Token; +} + +export type PoolLoaderSuccess = { + _type: "success"; +} & PoolLoaderData; + +export type PoolLoaderTokenError = { + _type: "TokenNotFoundError"; + message: string; + available: { + liquidityPoolAggregator: LiquidityPoolAggregator; + token0Instance?: Token; + token1Instance?: Token; + } +} + +export type PoolLoaderLiquidityPoolError = { + _type: "LiquidityPoolAggregatorNotFoundError"; + message: string; + available: Partial +} + +export type PoolLoaderError = T extends "TokenNotFoundError" ? + PoolLoaderTokenError : PoolLoaderLiquidityPoolError; + +export type PoolLoader = T extends "success" ? + PoolLoaderSuccess : + T extends PoolLoaderErrorType ? + PoolLoaderError : never; + +/** + * Fetches the liquidity pool aggregator and token instances for a given event. + * + * @param event - The event containing the pool address + * @param context - The context object containing the database + * + * @returns {PoolLoader} - The loader return object + */ +export async function fetchPoolLoaderData(liquidityPoolAddress: string, context: loaderContext, chainId: number): Promise > { + const liquidityPoolAggregator = await context.LiquidityPoolAggregator.get(liquidityPoolAddress); + if (!liquidityPoolAggregator) { + return { + _type: "LiquidityPoolAggregatorNotFoundError", + message: `LiquidityPoolAggregator ${liquidityPoolAddress} not found on chain ${chainId}`, + available: { + liquidityPoolAggregator: undefined, + token0Instance: undefined, + token1Instance: undefined + } + }; + } + const [token0Instance, token1Instance] = + await Promise.all([ + context.Token.get(liquidityPoolAggregator.token0_id), + context.Token.get(liquidityPoolAggregator.token1_id), + ]); + + if (token0Instance == undefined || token1Instance == undefined) { + return { + _type: "TokenNotFoundError", + message: `Token not found for pool ${liquidityPoolAddress} on chain ${chainId}`, + available: { + liquidityPoolAggregator, + token0Instance, + token1Instance + } }; + } + + return { _type: "success", liquidityPoolAggregator, token0Instance, token1Instance }; +} \ No newline at end of file From 5dd512dd31a68c4176012c8c0440e5999fb050da Mon Sep 17 00:00:00 2001 From: "jrab227@gmail.com" Date: Thu, 27 Feb 2025 12:25:18 -0500 Subject: [PATCH 2/2] Remove old tests. --- test/EventHandlers/CLPool/CLPool.test.ts | 29 ---- test/EventHandlers/Pool/Swap.test.ts | 210 ----------------------- test/EventHandlers/Pool/Sync.test.ts | 27 --- 3 files changed, 266 deletions(-) diff --git a/test/EventHandlers/CLPool/CLPool.test.ts b/test/EventHandlers/CLPool/CLPool.test.ts index d90d590..d8cd20c 100644 --- a/test/EventHandlers/CLPool/CLPool.test.ts +++ b/test/EventHandlers/CLPool/CLPool.test.ts @@ -508,34 +508,5 @@ describe("CLPool Event Handlers", () => { expect(updatedLiquidityPool.token1IsWhitelisted).to.equal(mockToken1Data.isWhitelisted); }); }); - - describe("when tokens do not exist", () => { - beforeEach(async () => { - let updatedDB = mockDb.entities.LiquidityPoolAggregator.set(mockLiquidityPoolData); - updatedDB = updatedDB.entities.Token.set(mockToken1Data); - - await CLPool.Swap.processEvent({ - event: mockEvent, - mockDb: updatedDB, - }); - }); - - it("should handle missing token instances", async () => { - expect(updateLiquidityPoolAggregatorStub.calledOnce).to.be.true; - const [diff] = updateLiquidityPoolAggregatorStub.firstCall.args; - - expect(diff.totalVolume0).to.equal( - mockLiquidityPoolData.totalVolume0 + abs(mockEvent.params.amount0) - ); - expect(diff.totalVolume1).to.equal( - mockLiquidityPoolData.totalVolume1 + abs(mockEvent.params.amount1) - ); - expect(diff.totalVolumeUSD).to.equal( - mockLiquidityPoolData.totalVolumeUSD + - (abs(mockEvent.params.amount1) * mockToken1Data.pricePerUSDNew) / - 10n ** mockToken1Data.decimals - ); - }); - }); }); }); diff --git a/test/EventHandlers/Pool/Swap.test.ts b/test/EventHandlers/Pool/Swap.test.ts index 4e36ac2..f6d2c53 100644 --- a/test/EventHandlers/Pool/Swap.test.ts +++ b/test/EventHandlers/Pool/Swap.test.ts @@ -90,216 +90,6 @@ describe("Pool Swap Event", () => { mockPriceOracle.restore(); }); - describe("when both tokens are missing", () => { - let updatedPool: any; - let modifiedMockLiquidityPoolData: any; - beforeEach(async () => { - - modifiedMockLiquidityPoolData = { - ...mockLiquidityPoolData, - token0_id: TokenIdByChain( - "0x9999999999999999999999999999999999999990", - 10 - ), - token1_id: TokenIdByChain( - "0x9999999999999999999999999999999999999999", - 10 - ), - }; - - const updatedDB1 = mockDb.entities.LiquidityPoolAggregator.set( - modifiedMockLiquidityPoolData as LiquidityPoolAggregator - ); - const mockEvent = Pool.Swap.createMockEvent(eventData); - - const postEventDB = await Pool.Swap.processEvent({ - event: mockEvent, - mockDb: updatedDB1, - }); - - updatedPool = postEventDB.entities.LiquidityPoolAggregator.get( - toChecksumAddress(eventData.mockEventData.srcAddress) - ); - - }); - it("should update the liquidity pool and timestamp", () => { - expect(updatedPool).to.not.be.undefined; - expect(updatedPool?.lastUpdatedTimestamp).to.deep.equal( - new Date(eventData.mockEventData.block.timestamp * 1000) - ); - }); - it("should update the liquidity pool with swap count", async () => { - expect(updatedPool?.numberOfSwaps).to - .equal(mockLiquidityPoolData.numberOfSwaps + 1n, "Swap count should be updated"); - }); - it("should update the liquidity pool with swap volume", () => { - expect(updatedPool?.totalVolume0).to.equal( - modifiedMockLiquidityPoolData.totalVolume0 + expectations.expectedNetAmount0 - ); - expect(updatedPool?.totalVolume1).to.equal( - modifiedMockLiquidityPoolData.totalVolume1 + expectations.expectedNetAmount1 - ); - - }); - - it("should update the liquidity pool with token0IsWhitelisted as false", () => { - expect(updatedPool?.token0IsWhitelisted).to.equal(false); - }); - it("should update the liquidity pool with token1IsWhitelisted as false", () => { - expect(updatedPool?.token1IsWhitelisted).to.equal(false); - }); - - it("shouldn't update the liquidity pool volume in USD since it has no prices", () => { - expect(updatedPool?.totalVolumeUSD).to.equal( - modifiedMockLiquidityPoolData.totalVolumeUSD - ); - }); - it("shouldn't update the liquidity pool volume in USD whitelisted since it has no prices", () => { - expect(updatedPool?.totalVolumeUSDWhitelisted).to.equal( - modifiedMockLiquidityPoolData.totalVolumeUSDWhitelisted - ); - }); - it("should not call refreshTokenPrice", () => { - expect(mockPriceOracle.calledOnce).to.be.false; - }); - }); - describe("when token0 is missing", () => { - let postEventDB: ReturnType; - - let updatedPool: any; - beforeEach(async () => { - // Set token0 to a different address not in the db tokens - mockLiquidityPoolData.token0_id = TokenIdByChain( - "0x9999999999999999999999999999999999999999", - 10 - ); - - const updatedDB1 = mockDb.entities.LiquidityPoolAggregator.set( - mockLiquidityPoolData as LiquidityPoolAggregator - ); - const updatedDB2 = updatedDB1.entities.Token.set(mockToken0Data as Token); - const updatedDB3 = updatedDB2.entities.Token.set(mockToken1Data as Token); - - const mockEvent = Pool.Swap.createMockEvent(eventData); - - postEventDB = await Pool.Swap.processEvent({ - event: mockEvent, - mockDb: updatedDB3, - }); - updatedPool = postEventDB.entities.LiquidityPoolAggregator.get( - toChecksumAddress(eventData.mockEventData.srcAddress) - ); - }); - - it('should update nominal swap volumes', () => { - expect(updatedPool?.totalVolume0).to.equal( - expectations.totalVolume0, - "Token0 nominal swap volume should be updated" - ); - expect(updatedPool?.totalVolume1).to.equal( - expectations.totalVolume1, - "Token1 nominal swap volume should be updated" - ); - }); - - it("should update the liquidity pool with token1 data only", async () => { - expect(updatedPool?.totalVolumeUSD).to.equal( - expectations.expectedLPVolumeUSD1, - "Swap volume in USD should be updated for token 1" - ); - - expect(updatedPool?.totalVolumeUSDWhitelisted).to.equal( - mockLiquidityPoolData.totalVolumeUSDWhitelisted, - "Total volume USD whitelisted should not be updated since token1 is not whitelisted." - ); - - expect(updatedPool?.numberOfSwaps).to.equal( - mockLiquidityPoolData.numberOfSwaps + 1n, - "Swap count should be updated" - ); - }); - - it("should update the liquidity pool with token0IsWhitelisted as false", () => { - expect(updatedPool?.token0IsWhitelisted).to.equal(false); - }); - it("should update the liquidity pool with token1IsWhitelisted as true", () => { - expect(updatedPool?.token1IsWhitelisted).to.equal(mockToken1Data.isWhitelisted); - }); - - it("should call refreshTokenPrice on token1", () => { - expect(mockPriceOracle.calledOnce).to.be.true; - const calledToken = mockPriceOracle.firstCall.args[0]; - expect(calledToken.address).to.equal(mockToken1Data.address); - }); - }); - - describe("when token1 is missing", () => { - let postEventDB: ReturnType; - let updatedPool: any; - - beforeEach(async () => { - mockLiquidityPoolData.token1_id = TokenIdByChain( - "0x9999999999999999999999999999999999999999", - 10 - ); - - const updatedDB1 = mockDb.entities.LiquidityPoolAggregator.set( - mockLiquidityPoolData as LiquidityPoolAggregator - ); - const updatedDB2 = updatedDB1.entities.Token.set(mockToken0Data as Token); - const updatedDB3 = updatedDB2.entities.Token.set(mockToken1Data as Token); - - const mockEvent = Pool.Swap.createMockEvent(eventData); - - postEventDB = await Pool.Swap.processEvent({ - event: mockEvent, - mockDb: updatedDB3, - }); - updatedPool = postEventDB.entities.LiquidityPoolAggregator.get( - toChecksumAddress(eventData.mockEventData.srcAddress) - ); - }); - - it('should update nominal swap volumes', () => { - expect(updatedPool?.totalVolume0).to.equal( - expectations.totalVolume0, - "Token0 nominal swap volume should be updated" - ); - expect(updatedPool?.totalVolume1).to.equal( - expectations.totalVolume1, - "Token1 nominal swap volume should be updated" - ); - - }); - - it('should update swap count', () => { - expect(updatedPool?.numberOfSwaps).to.equal( - mockLiquidityPoolData.numberOfSwaps + 1n, - "Swap count should be updated" - ); - }); - - it("should update the liquidity pool USD volume with token0 data only", async () => { - expect(updatedPool?.totalVolumeUSD).to.equal( - expectations.expectedLPVolumeUSD0, - "Total volume USD should be updated." - ); - - expect(updatedPool?.totalVolumeUSDWhitelisted).to.equal( - mockLiquidityPoolData.totalVolumeUSDWhitelisted, - "Total volume USD whitelisted should not be updated since token0 is not whitelisted." - ); - }); - it("should update the liquidity pool with token0IsWhitelisted", () => { - expect(updatedPool?.token0IsWhitelisted).to.equal(mockToken0Data.isWhitelisted); - }); - it("should call refreshTokenPrice on token0", () => { - expect(mockPriceOracle.calledOnce).to.be.true; - const calledToken = mockPriceOracle.firstCall.args[0]; - expect(calledToken.address).to.equal(mockToken0Data.address); - }); - }); - describe("when both tokens exist", () => { let postEventDB: ReturnType; let updatedPool: any; diff --git a/test/EventHandlers/Pool/Sync.test.ts b/test/EventHandlers/Pool/Sync.test.ts index fb640b1..82d794c 100644 --- a/test/EventHandlers/Pool/Sync.test.ts +++ b/test/EventHandlers/Pool/Sync.test.ts @@ -55,33 +55,6 @@ describe("Pool Sync Event", () => { mockDb = MockDb.createMockDb(); }); - describe("when both tokens are missing", () => { - it("should update LiquidityPool reserves, but not liquidity USD", async () => { - const modifiedMockLiquidityPoolData = { - ...mockLiquidityPoolData, - token0_id: TokenIdByChain("0x9999999999999999999999999999999999999990", 10), - token1_id: TokenIdByChain("0x9999999999999999999999999999999999999999", 10), - }; - - const updatedDB1 = mockDb.entities.LiquidityPoolAggregator.set(modifiedMockLiquidityPoolData as LiquidityPoolAggregator); - const mockEvent = Pool.Sync.createMockEvent(eventData); - - const postEventDB = await Pool.Sync.processEvent({ - event: mockEvent, - mockDb: updatedDB1, - }); - - const updatedPool = postEventDB.entities.LiquidityPoolAggregator.get( - toChecksumAddress(eventData.mockEventData.srcAddress) - ); - expect(updatedPool).to.not.be.undefined; - expect(updatedPool?.reserve0).to.equal(expectations.expectedReserve0InMissing); - expect(updatedPool?.reserve1).to.equal(expectations.expectedReserve1InMissing); - expect(updatedPool?.totalLiquidityUSD) - .to.equal(mockLiquidityPoolData.totalLiquidityUSD, "totalLiquidityUSD should be the same as the original value"); - }); - }); - describe("when both tokens exist", () => { let postEventDB: ReturnType;