From 49f4bbd2f352ee00209e4ff755e63bb9d458cbba Mon Sep 17 00:00:00 2001 From: "Jalil F." Date: Mon, 9 Dec 2024 22:23:25 -0500 Subject: [PATCH] Bugfix: Hardcode price connectors (#234) * Update PriceOracle's read prices to return a single address. * Update PriceOracle.ts * Removing set_whitelisted_prices. * Removing token fetch for connectors. * Test fix. * Testfix. --- src/Constants.ts | 166 ++++----------- src/EventHandlers/CLPool.ts | 13 +- src/EventHandlers/NFPM.ts | 8 - src/EventHandlers/Pool.ts | 14 +- .../Voter/SuperchainLeafVoter.ts | 3 +- src/EventHandlers/Voter/Voter.ts | 3 +- src/EventHandlers/VotingReward.ts | 13 +- src/PriceOracle.ts | 194 ++++-------------- src/constants/price_connectors.json | 33 +++ .../EventHandlers/SuperchainLeafVoter.test.ts | 4 +- test/EventHandlers/Voter.test.ts | 5 +- test/EventHandlers/VotingReward.test.ts | 4 +- test/PriceOracle.test.ts | 78 +------ 13 files changed, 129 insertions(+), 409 deletions(-) create mode 100644 src/constants/price_connectors.json diff --git a/src/Constants.ts b/src/Constants.ts index 1f301f3..3e24f2b 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -1,13 +1,9 @@ import { TokenInfo, Pool } from "./CustomTypes"; import dotenv from "dotenv"; -import optimismWhitelistedTokens from "./constants/optimismWhitelistedTokens.json"; -import baseWhitelistedTokens from "./constants/baseWhitelistedTokens.json"; -import modeWhitelistedTokens from "./constants/modeWhitelistedTokens.json"; -import liskWhitelistedTokens from "./constants/liskWhitelistedTokens.json"; -import contractABI from "../abis/VeloPriceOracleABI.json"; import { Web3 } from "web3"; import { optimism, base, lisk, mode } from 'viem/chains'; import { createPublicClient, http, PublicClient } from 'viem'; +import priceConnectors from "./constants/price_connectors.json"; dotenv.config(); @@ -19,102 +15,44 @@ export const SECONDS_IN_AN_HOUR = BigInt(3600); export const SECONDS_IN_A_DAY = BigInt(86400); export const SECONDS_IN_A_WEEK = BigInt(604800); -// Convert imported JSON to TokenInfo type -export const OPTIMISM_WHITELISTED_TOKENS: TokenInfo[] = - optimismWhitelistedTokens as TokenInfo[]; -export const BASE_WHITELISTED_TOKENS: TokenInfo[] = - baseWhitelistedTokens as TokenInfo[]; +export const OPTIMISM_PRICE_CONNECTORS: PriceConnector[] = + priceConnectors.optimism as PriceConnector[]; -export const MODE_WHITELISTED_TOKENS: TokenInfo[] = - modeWhitelistedTokens as TokenInfo[]; +export const BASE_PRICE_CONNECTORS: PriceConnector[] = + priceConnectors.base as PriceConnector[]; -export const LISK_WHITELISTED_TOKENS: TokenInfo[] = - liskWhitelistedTokens as TokenInfo[]; +export const MODE_PRICE_CONNECTORS: PriceConnector[] = + priceConnectors.mode as PriceConnector[]; + +export const LISK_PRICE_CONNECTORS: PriceConnector[] = + priceConnectors.lisk as PriceConnector[]; export const toChecksumAddress = (address: string) => Web3.utils.toChecksumAddress(address); -// Helper function to find a token by symbol -const findToken = (tokens: TokenInfo[], symbol: string): TokenInfo => { - const token = tokens.find((t) => t.symbol === symbol); - if (!token) throw new Error(`Token ${symbol} not found`); - return token; +type PriceConnector = { + address: string; + block: number; }; -// List of stablecoin pools with their token0, token1 and name -const OPTIMISM_STABLECOIN_POOLS: Pool[] = [ - { - address: "0x0493Bf8b6DBB159Ce2Db2E0E8403E753Abd1235b", - token0: findToken(OPTIMISM_WHITELISTED_TOKENS, "WETH"), - token1: findToken(OPTIMISM_WHITELISTED_TOKENS, "USDC"), - name: "vAMM-WETH/USDC.e", - }, - { - address: "0x6387765fFA609aB9A1dA1B16C455548Bfed7CbEA", - token0: findToken(OPTIMISM_WHITELISTED_TOKENS, "WETH"), - token1: findToken(OPTIMISM_WHITELISTED_TOKENS, "LUSD"), - name: "vAMM-WETH/LUSD", - }, -]; - -const BASE_STABLECOIN_POOLS: Pool[] = [ - { - address: "0xB4885Bc63399BF5518b994c1d0C153334Ee579D0", - token0: findToken(BASE_WHITELISTED_TOKENS, "WETH"), - token1: findToken(BASE_WHITELISTED_TOKENS, "USDbC"), - name: "vAMM-WETH/USDbC", - }, - { - address: "0x9287C921f5d920cEeE0d07d7c58d476E46aCC640", - token0: findToken(BASE_WHITELISTED_TOKENS, "WETH"), - token1: findToken(BASE_WHITELISTED_TOKENS, "DAI"), - name: "vAMM-WETH/DAI", - }, -]; - -const MODE_STABLECOIN_POOLS: Pool[] = []; - -// List of pool addresses for testing -const OPTIMISM_TESTING_POOL_ADDRESSES: string[] = [ - "0x0493Bf8b6DBB159Ce2Db2E0E8403E753Abd1235b", - "0xd25711EdfBf747efCE181442Cc1D8F5F8fc8a0D3", - "0xe9581d0F1A628B038fC8B2a7F5A7d904f0e2f937", - "0x0df083de449F75691fc5A36477a6f3284C269108", - "0x8134A2fDC127549480865fB8E5A9E8A8a95a54c5", - "0x58e6433A6903886E440Ddf519eCC573c4046a6b2", - "0xB4885Bc63399BF5518b994c1d0C153334Ee579D0", -]; - -const BASE_TESTING_POOL_ADDRESSES: string[] = [ - "0xB4885Bc63399BF5518b994c1d0C153334Ee579D0", // vAMM-WETH/USDbC - "0x9287C921f5d920cEeE0d07d7c58d476E46aCC640", // vAMM-WETH/DAI - "0x0B25c51637c43decd6CC1C1e3da4518D54ddb528", // sAMM-DOLA/USDbC -]; - -const MODE_TESTING_POOL_ADDRESSES: string[] = []; - // Object containing all the constants for a chain type chainConstants = { - eth: TokenInfo; - usdc: TokenInfo; + weth: string; + usdc: string; oracle: { getAddress: (blockNumber: number) => string; startBlock: number; updateDelta: number; + priceConnectors: PriceConnector[]; }; - rewardToken: (blockNumber: number) => TokenInfo; + rewardToken: (blockNumber: number) => string; eth_client: PublicClient; - stablecoinPools: Pool[]; - stablecoinPoolAddresses: string[]; - testingPoolAddresses: string[]; - whitelistedTokens: TokenInfo[]; - whitelistedTokenAddresses: string[]; }; // Constants for Optimism const OPTIMISM_CONSTANTS: chainConstants = { - eth: findToken(OPTIMISM_WHITELISTED_TOKENS, "WETH"), - usdc: findToken(OPTIMISM_WHITELISTED_TOKENS, "USDC"), + weth: "0x4200000000000000000000000000000000000006", + usdc: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", oracle: { getAddress: (blockNumber: number) => { return blockNumber < 124076662 @@ -123,18 +61,13 @@ const OPTIMISM_CONSTANTS: chainConstants = { }, startBlock: 107676013, updateDelta: 60 * 60, // 1 hour + priceConnectors: OPTIMISM_PRICE_CONNECTORS, }, rewardToken: (blockNumber: number) => { if (blockNumber < 105896880) { - return findToken(OPTIMISM_WHITELISTED_TOKENS, "VELO"); + return "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; } - - return { - address: "0x9560e827aF36c94D2Ac33a39bCE1Fe78631088Db", - symbol: "VELO", - decimals: 18, - createdBlock: 105896880, - }; + return "0x9560e827aF36c94D2Ac33a39bCE1Fe78631088Db"; }, eth_client: createPublicClient({ chain: optimism, @@ -144,21 +77,12 @@ const OPTIMISM_CONSTANTS: chainConstants = { batch: false }), }) as PublicClient, - stablecoinPools: OPTIMISM_STABLECOIN_POOLS, - stablecoinPoolAddresses: OPTIMISM_STABLECOIN_POOLS.map( - (pool) => pool.address - ), - testingPoolAddresses: OPTIMISM_TESTING_POOL_ADDRESSES, - whitelistedTokens: OPTIMISM_WHITELISTED_TOKENS, - whitelistedTokenAddresses: OPTIMISM_WHITELISTED_TOKENS.map( - (token) => token.address - ), }; // Constants for Base const BASE_CONSTANTS: chainConstants = { - eth: findToken(BASE_WHITELISTED_TOKENS, "WETH"), - usdc: findToken(BASE_WHITELISTED_TOKENS, "USDC"), + weth: "0x4200000000000000000000000000000000000006", + usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", oracle: { getAddress: (blockNumber: number) => { return blockNumber < 18480097 @@ -167,67 +91,56 @@ const BASE_CONSTANTS: chainConstants = { }, startBlock: 3219857, updateDelta: 60 * 60, // 1 hour + priceConnectors: BASE_PRICE_CONNECTORS, }, rewardToken: (blockNumber: Number) => - findToken(BASE_WHITELISTED_TOKENS, "AERO"), + "0x940181a94A35A4569E4529A3CDfB74e38FD98631", eth_client: createPublicClient({ chain: base, transport: http(process.env.ENVIO_BASE_RPC_URL || "https://base.publicnode.com", { retryCount: 10, retryDelay: 1000, }), - }) as PublicClient, - stablecoinPools: BASE_STABLECOIN_POOLS, - stablecoinPoolAddresses: BASE_STABLECOIN_POOLS.map((pool) => pool.address), - testingPoolAddresses: BASE_TESTING_POOL_ADDRESSES, - whitelistedTokens: BASE_WHITELISTED_TOKENS, - whitelistedTokenAddresses: BASE_WHITELISTED_TOKENS.map( - (token) => token.address - ), + }) as PublicClient }; // Constants for Lisk const LISK_CONSTANTS: chainConstants = { - eth: findToken(LISK_WHITELISTED_TOKENS, "WETH"), - usdc: findToken(LISK_WHITELISTED_TOKENS, "USDC"), + weth: "0x4200000000000000000000000000000000000006", + usdc: "0xF242275d3a6527d877f2c927a82D9b057609cc71", oracle: { getAddress: (blockNumber: number) => { return "0xE50621a0527A43534D565B67D64be7C79807F269"; }, startBlock: 8380726, updateDelta: 60 * 60, // 1 hour + priceConnectors: LISK_PRICE_CONNECTORS, }, rewardToken: (blockNumber: number) => - findToken(LISK_WHITELISTED_TOKENS, "XVELO"), + "0x7f9AdFbd38b669F03d1d11000Bc76b9AaEA28A81", eth_client: createPublicClient({ chain: lisk, transport: http(process.env.ENVIO_LISK_RPC_URL || "https://lisk.drpc.org", { retryCount: 10, retryDelay: 1000, }), - }) as PublicClient, - stablecoinPools: [], - stablecoinPoolAddresses: [], - testingPoolAddresses: [], - whitelistedTokens: LISK_WHITELISTED_TOKENS, - whitelistedTokenAddresses: LISK_WHITELISTED_TOKENS.map( - (token) => token.address - ), + }) as PublicClient }; // Constants for Mode const MODE_CONSTANTS: chainConstants = { - eth: findToken(MODE_WHITELISTED_TOKENS, "WETH"), - usdc: findToken(MODE_WHITELISTED_TOKENS, "USDC"), + weth: "0x4200000000000000000000000000000000000006", + usdc: "0xd988097fb8612cc24eeC14542bC03424c656005f", oracle: { getAddress: (blockNumber: number) => { return "0xE50621a0527A43534D565B67D64be7C79807F269"; }, startBlock: 15591759, updateDelta: 60 * 60, // 1 hour + priceConnectors: MODE_PRICE_CONNECTORS, }, rewardToken: (blockNumber: number) => - findToken(MODE_WHITELISTED_TOKENS, "XVELO"), + "0x7f9AdFbd38b669F03d1d11000Bc76b9AaEA28A81", eth_client: createPublicClient({ chain: mode, transport: http(process.env.ENVIO_MODE_RPC_URL || "https://mainnet.mode.network", { @@ -235,13 +148,6 @@ const MODE_CONSTANTS: chainConstants = { retryDelay: 1000, }), }) as PublicClient, - stablecoinPools: MODE_STABLECOIN_POOLS, - stablecoinPoolAddresses: MODE_STABLECOIN_POOLS.map((pool) => pool.address), - testingPoolAddresses: MODE_TESTING_POOL_ADDRESSES, - whitelistedTokens: MODE_WHITELISTED_TOKENS, - whitelistedTokenAddresses: MODE_WHITELISTED_TOKENS.map( - (token) => token.address - ), }; /** diff --git a/src/EventHandlers/CLPool.ts b/src/EventHandlers/CLPool.ts index 2721443..0ec527c 100644 --- a/src/EventHandlers/CLPool.ts +++ b/src/EventHandlers/CLPool.ts @@ -464,15 +464,12 @@ CLPool.Swap.handlerWithLoader({ return null; } - const [token0Instance, token1Instance, currentTokens] = await Promise.all([ + const [token0Instance, token1Instance] = await Promise.all([ context.Token.get(liquidityPoolAggregator.token0_id), context.Token.get(liquidityPoolAggregator.token1_id), - context.Token.getWhere.chainId.eq(event.chainId) ]); - const currentTokenAddresses = currentTokens.map((token: Token) => token.address); - - return { liquidityPoolAggregator, token0Instance, token1Instance, currentTokenAddresses }; + return { liquidityPoolAggregator, token0Instance, token1Instance }; }, handler: async ({ event, context, loaderReturn }) => { const blockDatetime = new Date(event.block.timestamp * 1000); @@ -493,7 +490,7 @@ CLPool.Swap.handlerWithLoader({ context.CLPool_Swap.set(entity); if (loaderReturn && loaderReturn.liquidityPoolAggregator) { - const { liquidityPoolAggregator, token0Instance, token1Instance, currentTokenAddresses } = loaderReturn; + const { liquidityPoolAggregator, token0Instance, token1Instance } = loaderReturn; let token0 = token0Instance; let token1 = token1Instance; @@ -511,7 +508,7 @@ CLPool.Swap.handlerWithLoader({ if (token0) { try { - token0 = await refreshTokenPrice(token0, currentTokenAddresses, event.block.number, event.block.timestamp, event.chainId, context); + token0 = await refreshTokenPrice(token0, event.block.number, event.block.timestamp, event.chainId, context); } catch (error) { context.log.error(`Error refreshing token price for ${token0?.address} on chain ${event.chainId}: ${error}`); } @@ -527,7 +524,7 @@ CLPool.Swap.handlerWithLoader({ if (token1) { try { - token1 = await refreshTokenPrice(token1, currentTokenAddresses, event.block.number, event.block.timestamp, event.chainId, context); + token1 = await refreshTokenPrice(token1, event.block.number, event.block.timestamp, event.chainId, context); } catch (error) { context.log.error(`Error refreshing token price for ${token1?.address} on chain ${event.chainId}: ${error}`); } diff --git a/src/EventHandlers/NFPM.ts b/src/EventHandlers/NFPM.ts index ba830a0..7944ebc 100644 --- a/src/EventHandlers/NFPM.ts +++ b/src/EventHandlers/NFPM.ts @@ -2,7 +2,6 @@ import { NFPM, NFPM_Transfer, } from "generated"; -import { set_whitelisted_prices } from "../PriceOracle"; /** * @title NonfungiblePositionManager @@ -32,11 +31,4 @@ NFPM.Transfer.handler(async ({ event, context }) => { }; context.NFPM_Transfer.set(entity); - - try { - await set_whitelisted_prices(event.chainId, event.block.number, blockDatetime, context); - } catch (error) { - console.error("Error updating whitelisted prices after position mint:"); - console.error(error); - } }); diff --git a/src/EventHandlers/Pool.ts b/src/EventHandlers/Pool.ts index fc5acff..2583ff5 100644 --- a/src/EventHandlers/Pool.ts +++ b/src/EventHandlers/Pool.ts @@ -125,24 +125,20 @@ Pool.Swap.handlerWithLoader({ return null; } - const [token0Instance, token1Instance, user, isLiquidityPool, currentTokens] = + 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), - context.Token.getWhere.chainId.eq(event.chainId) + context.LiquidityPoolAggregator.get(event.params.to) ]); - const currentTokenAddresses = currentTokens.map((token: Token) => token.address); - return { liquidityPoolAggregator, token0Instance, token1Instance, to_address: event.params.to, user, - currentTokenAddresses, }; }, handler: async ({ event, context, loaderReturn }) => { @@ -163,7 +159,7 @@ Pool.Swap.handlerWithLoader({ context.Pool_Swap.set(entity); if (loaderReturn) { - const { liquidityPoolAggregator, token0Instance, token1Instance, to_address, user, currentTokenAddresses } = + const { liquidityPoolAggregator, token0Instance, token1Instance, to_address, user } = loaderReturn; let token0 = token0Instance; @@ -180,7 +176,7 @@ Pool.Swap.handlerWithLoader({ tokenUpdateData.netAmount0 = event.params.amount0In + event.params.amount0Out; if (token0) { try { - token0 = await refreshTokenPrice(token0, currentTokenAddresses, event.block.number, event.block.timestamp, event.chainId, context); + token0 = await refreshTokenPrice(token0, event.block.number, event.block.timestamp, event.chainId, context); } catch (error) { context.log.error(`Error refreshing token price for ${token0?.address} on chain ${event.chainId}: ${error}`); } @@ -197,7 +193,7 @@ Pool.Swap.handlerWithLoader({ tokenUpdateData.netAmount1 = event.params.amount1In + event.params.amount1Out; if (token1) { try { - token1 = await refreshTokenPrice(token1, currentTokenAddresses, event.block.number, event.block.timestamp, event.chainId, context); + token1 = await refreshTokenPrice(token1, event.block.number, event.block.timestamp, event.chainId, context); } catch (error) { context.log.error(`Error refreshing token price for ${token1?.address} on chain ${event.chainId}: ${error}`); } diff --git a/src/EventHandlers/Voter/SuperchainLeafVoter.ts b/src/EventHandlers/Voter/SuperchainLeafVoter.ts index 056606e..35425e6 100644 --- a/src/EventHandlers/Voter/SuperchainLeafVoter.ts +++ b/src/EventHandlers/Voter/SuperchainLeafVoter.ts @@ -84,10 +84,9 @@ SuperchainLeafVoter.DistributeReward.handlerWithLoader({ ); - const rewardTokenInfo = CHAIN_CONSTANTS[event.chainId].rewardToken( + const rewardTokenAddress = CHAIN_CONSTANTS[event.chainId].rewardToken( event.block.number ); - const rewardTokenAddress = rewardTokenInfo.address; const promisePool = poolAddress ? context.LiquidityPoolAggregator.get(poolAddress) diff --git a/src/EventHandlers/Voter/Voter.ts b/src/EventHandlers/Voter/Voter.ts index f6dfd37..1c0b242 100644 --- a/src/EventHandlers/Voter/Voter.ts +++ b/src/EventHandlers/Voter/Voter.ts @@ -77,8 +77,7 @@ Voter.DistributeReward.handlerWithLoader({ event.params.gauge ); - const rewardTokenInfo = CHAIN_CONSTANTS[event.chainId].rewardToken(event.block.number); - const rewardTokenAddress = rewardTokenInfo.address; + const rewardTokenAddress = CHAIN_CONSTANTS[event.chainId].rewardToken(event.block.number); const promisePool = poolAddress ? context.LiquidityPoolAggregator.get(poolAddress) diff --git a/src/EventHandlers/VotingReward.ts b/src/EventHandlers/VotingReward.ts index 2a27008..827671f 100644 --- a/src/EventHandlers/VotingReward.ts +++ b/src/EventHandlers/VotingReward.ts @@ -33,15 +33,12 @@ VotingReward.NotifyReward.handlerWithLoader({ ); } - const [currentLiquidityPool, storedToken, currentTokens] = await Promise.all([ + const [currentLiquidityPool, storedToken] = await Promise.all([ promisePool, - context.Token.get(TokenIdByChain(event.params.reward, event.chainId)), - context.Token.getWhere.chainId.eq(event.chainId) + context.Token.get(TokenIdByChain(event.params.reward, event.chainId)) ]); - const currentTokenAddresses = currentTokens.map((token: Token) => token.address); - - return { currentLiquidityPool, storedToken, currentTokenAddresses }; + return { currentLiquidityPool, storedToken }; }, handler: async ({ event, context, loaderReturn }) => { const entity: VotingReward_NotifyReward = { @@ -59,13 +56,13 @@ VotingReward.NotifyReward.handlerWithLoader({ context.VotingReward_NotifyReward.set(entity); if (loaderReturn) { - const { currentLiquidityPool, storedToken, currentTokenAddresses } = loaderReturn; + const { currentLiquidityPool, storedToken } = loaderReturn; let rewardToken: TokenPriceData | null = null; if (!storedToken) { try { - rewardToken = await getTokenPriceData(event.params.reward, currentTokenAddresses, event.block.number, event.chainId); + rewardToken = await getTokenPriceData(event.params.reward, event.block.number, event.chainId); } catch (error) { context.log.error(`Error in voting reward notify reward event fetching token details` + ` for ${event.params.reward} on chain ${event.chainId}: ${error}`); diff --git a/src/PriceOracle.ts b/src/PriceOracle.ts index 7caea94..096e8c4 100644 --- a/src/PriceOracle.ts +++ b/src/PriceOracle.ts @@ -53,7 +53,6 @@ const ONE_HOUR_MS = 60 * 60 * 1000; // 1 hour in milliseconds */ export async function refreshTokenPrice( token: Token, - connectors: string[], blockNumber: number, blockTimestamp: number, chainId: number, @@ -63,7 +62,7 @@ export async function refreshTokenPrice( return token; } - const tokenPriceData = await getTokenPriceData(token.address, connectors, blockNumber, chainId); + const tokenPriceData = await getTokenPriceData(token.address, blockNumber, chainId); const updatedToken: Token = { ...token, pricePerUSDNew: tokenPriceData.pricePerUSDNew, @@ -90,7 +89,6 @@ export async function refreshTokenPrice( */ export async function getTokenPriceData( tokenAddress: string, - connectors: string[], blockNumber: number, chainId: number ): Promise { @@ -99,43 +97,39 @@ export async function getTokenPriceData( chainId ); - const WETH_ADDRESS = CHAIN_CONSTANTS[chainId].eth.address; - const USDC_ADDRESS = CHAIN_CONSTANTS[chainId].usdc.address; - - let tokenPrice: bigint = 0n; - let tokenDecimals: bigint = 0n; - - try { - const connectorList = connectors.filter((connector) => connector !== tokenAddress) - .filter((connector) => connector !== WETH_ADDRESS) - .filter((connector) => connector !== USDC_ADDRESS); - const prices = await read_prices([tokenAddress, ...connectorList, WETH_ADDRESS, USDC_ADDRESS], chainId, blockNumber); - tokenPrice = BigInt(prices[0]); - tokenDecimals = BigInt(tokenDetails.decimals); - } catch (error) { - console.error("Error fetching token price", error); + const WETH_ADDRESS = CHAIN_CONSTANTS[chainId].weth; + const USDC_ADDRESS = CHAIN_CONSTANTS[chainId].usdc; + const SYSTEM_TOKEN_ADDRESS = CHAIN_CONSTANTS[chainId].rewardToken(blockNumber); + + const connectors = CHAIN_CONSTANTS[chainId].oracle.priceConnectors + .filter((connector) => connector.block <= blockNumber) + .map((connector) => connector.address) + .filter((connector) => connector !== tokenAddress) + .filter((connector) => connector !== WETH_ADDRESS) + .filter((connector) => connector !== USDC_ADDRESS) + .filter((connector) => connector !== SYSTEM_TOKEN_ADDRESS); + + let pricePerUSDNew: bigint = 0n; + let decimals: bigint = 0n; + + const ORACLE_DEPLOYED = CHAIN_CONSTANTS[chainId].oracle.startBlock <= blockNumber; + + if (ORACLE_DEPLOYED) { + try { + const prices = await read_prices([ + tokenAddress, + ...connectors, + SYSTEM_TOKEN_ADDRESS, + WETH_ADDRESS, + USDC_ADDRESS], + chainId, blockNumber); + pricePerUSDNew = BigInt(prices[0]); + decimals = BigInt(tokenDetails.decimals); + } catch (error) { + console.error("Error fetching token price", error); + } } - return { pricePerUSDNew: tokenPrice, decimals: tokenDecimals }; - -} - - -/** - * Hashes a list of addresses using MD5. - * @param {string[]} addresses - The list of addresses to hash. - * @returns {string} The MD5 hash of the addresses list. - */ -function hashAddresses(addresses: string[]): string { - return createHash("md5").update(addresses.join(",")).digest("hex"); -} - -let pricesLastUpdated: { [chainId: number]: Date } = {}; -export function setPricesLastUpdated(chainId: number, date: Date) { - pricesLastUpdated[chainId] = date; -} - -export function getPricesLastUpdated(chainId: number): Date | null { - return pricesLastUpdated[chainId] || null; + return { pricePerUSDNew, decimals }; } /** @@ -164,7 +158,7 @@ export async function read_prices( ): Promise { const ethClient = CHAIN_CONSTANTS[chainId].eth_client; - const numAddrs = addrs.length - 1; + const numAddrs = 1; // Return the first address only. try { const { result } = await ethClient.simulateContract({ @@ -176,122 +170,6 @@ export async function read_prices( }); return result; } catch (error) { - return addrs.map(() => "-1"); - } -} - -/** - * Fetches the prices of whitelisted tokens for a given blockchain network. - * - * This function retrieves the list of whitelisted tokens based on the provided - * chain ID, fetches their current prices from a price oracle, and updates the - * context with token information including their addresses, symbols, units, and prices. - * - * @param {number} chainId - The ID of the blockchain network to fetch prices for. - * Use 10 for the Optimism network, or any other value - * for the base network. - * @param {number} blockNumber - The block number to fetch prices for. - * @param {Date} blockDatetime - The datetime of the block to use for updating timestamps. - * @param {any} context - The context object to interact with the Token and TokenPrice entities. - * @returns {Promise} A promise that resolves when the operation is complete. - * - * @throws {Error} Throws an error if the price fetching process fails. - */ -export async function set_whitelisted_prices( - chainId: number, - blockNumber: number, - blockDatetime: Date, - context: any -): Promise { - // Skip if not yet available - let startBlock = - CHAIN_CONSTANTS[chainId].oracle.startBlock; - - if (blockNumber < startBlock) return; - - // Skip if already updated recently - const lastUpdated = getPricesLastUpdated(chainId); - const timeDelta = CHAIN_CONSTANTS[chainId].oracle.updateDelta * 1000; - const tokensNeedUpdate = - !lastUpdated || blockDatetime.getTime() - lastUpdated.getTime() > timeDelta; - - if (!tokensNeedUpdate) return; - - // Get token data for chain - const tokenData = CHAIN_CONSTANTS[chainId].whitelistedTokens; - - // Get prices from oracle and filter if token is not created yet - const addresses = tokenData - .filter((token) => token.createdBlock <= blockNumber) - .map((token) => toChecksumAddress(token.address)); - - if (addresses.length === 0) return; - - const tokenPriceCache = Cache.init(CacheCategory.TokenPrices, chainId); - - // Check cache for existing prices list by hashing list of addresses. - // If there is any new addresses, we will need to fetch new prices. - const addressHash: string = hashAddresses(addresses); - const cacheKey = `${chainId}_${addressHash}_${blockNumber}`; - - let cache = tokenPriceCache.read(cacheKey); - let prices: ShapePricesList = cache?.prices; - - // If prices aren't cached, fetch and cache prices. - if (!prices) { - prices = await read_prices(addresses, chainId, blockNumber); - tokenPriceCache.add({ [cacheKey]: { prices: prices } as any }); + return addrs.map(() => "0"); } - - const pricesByAddress = new Map(); - - prices.forEach((price, index) => { - let p = !price || price === "-1" ? "0": price; // Clean price of undefined and -1 values - pricesByAddress.set(addresses[index], p); - }); - - pricesByAddress.set(toChecksumAddress(CHAIN_CONSTANTS[chainId].usdc.address), "1"); - - for (const token of tokenData) { - const price = pricesByAddress.get(toChecksumAddress(token.address)) || 0; - - // Get or create Token entity - let tokenEntity: Token = await context.Token.get(TokenIdByChain(token.address, chainId)); - if (!tokenEntity) { - // Create a new token entity if it doesn't exist - tokenEntity = { - id: TokenIdByChain(token.address, chainId), - address: toChecksumAddress(token.address), - symbol: token.symbol, - name: token.symbol, // Using symbol as name, update if you have a separate name field - chainId: chainId, - decimals: BigInt(token.decimals), - pricePerUSDNew: BigInt(price), - lastUpdatedTimestamp: blockDatetime, - isWhitelisted: false, - }; - } - - // Update Token entity - const updatedToken: Token = { - ...tokenEntity, - pricePerUSDNew: BigInt(price), - lastUpdatedTimestamp: blockDatetime - }; - - context.Token.set(updatedToken); - - // Create new TokenPrice entity - const tokenPrice: TokenPriceSnapshot = { - id: TokenIdByBlock(token.address, chainId, blockNumber), - address: toChecksumAddress(token.address), - pricePerUSDNew: BigInt(price), - chainId: chainId, - lastUpdatedTimestamp: blockDatetime, - }; - - context.TokenPriceSnapshot.set(tokenPrice); - } - - setPricesLastUpdated(chainId, blockDatetime); -} +} \ No newline at end of file diff --git a/src/constants/price_connectors.json b/src/constants/price_connectors.json new file mode 100644 index 0000000..7746df8 --- /dev/null +++ b/src/constants/price_connectors.json @@ -0,0 +1,33 @@ +{ + "optimism": [ + {"address": "0x9560e827aF36c94D2Ac33a39bCE1Fe78631088Db", "block": 105896796 }, + {"address": "0x4200000000000000000000000000000000000042", "block": 6490467 }, + {"address": "0x9bcef72be871e61ed4fbbc7630889bee758eb81d", "block": 113681 }, + {"address": "0x2e3d870790dc77a83dd1d18184acc7439a53f475", "block": 2153157 }, + {"address": "0x8c6f28f2f1a3c87f0f938b96d27520d9751ec8d9", "block": 1 }, + {"address": "0x1f32b1c2345538c0c6f582fcb022739c4a194ebb", "block": 17831118 }, + {"address": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", "block": 1 }, + {"address": "0x6c84a8f1c29108f47a79964b5fe888d4f4d0de40", "block": 89899840 }, + {"address": "0xc40f949f8a4e094d1b49a23ea9241d289b7b2819", "block": 1 }, + {"address": "0x94b008aa00579c1307b0ef2c499ad98a8ce58e58", "block": 1 }, + {"address": "0x0b2c639c533813f4aa9d7837caf62653d097ff85", "block": 10 } + ], + "base": [ + {"address" :"0x940181a94A35A4569E4529A3CDfB74e38FD98631", "block": 3200550 }, + {"address" :"0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", "block": 1569598 }, + {"address" :"0x4621b7a9c75199271f773ebd9a499dbd165c3191", "block": 2361818 }, + {"address" :"0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "block": 1600576 }, + {"address" :"0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452", "block": 4572990 }, + {"address" :"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42", "block": 15107859 }, + {"address" :"0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca", "block": 2062407 } + ], + "mode": [ + {"address" :"0xf0F161fDA2712DB8b566946122a5af183995e2eD", "block": 190688 }, + {"address" :"0xDfc7C877a950e49D2610114102175A06C2e3167a", "block": 7103932 }, + {"address" :"0xE7798f023fC62146e8Aa1b36Da45fb70855a77Ea", "block": 190669 } + ], + "lisk": [ + {"address" :"0xac485391EB2d7D88253a7F1eF18C37f4242D1A24", "block": 568336 }, + {"address" :"0x05D032ac25d322df992303dCa074EE7392C117b9", "block": 1639961 } + ] +} \ No newline at end of file diff --git a/test/EventHandlers/SuperchainLeafVoter.test.ts b/test/EventHandlers/SuperchainLeafVoter.test.ts index f1c74bf..d3b2b07 100644 --- a/test/EventHandlers/SuperchainLeafVoter.test.ts +++ b/test/EventHandlers/SuperchainLeafVoter.test.ts @@ -129,8 +129,8 @@ describe("SuperchainLeafVoter Events", () => { // Setup mock reward token const rewardToken: Token = { - id: TokenIdByChain(rewardTokenInfo.address, chainId), - address: rewardTokenInfo.address, + id: TokenIdByChain(rewardTokenInfo, chainId), + address: rewardTokenInfo, symbol: "VELO", name: "VELO", chainId: chainId, diff --git a/test/EventHandlers/Voter.test.ts b/test/EventHandlers/Voter.test.ts index c0a2ed7..d5fe309 100644 --- a/test/EventHandlers/Voter.test.ts +++ b/test/EventHandlers/Voter.test.ts @@ -119,8 +119,7 @@ describe("Voter Events", () => { const gaugeAddress = "0xa75127121d28a9bf848f3b70e7eea26570aa7700"; const blockNumber = 128357873; - const rewardTokenInfo = CHAIN_CONSTANTS[chainId].rewardToken(blockNumber); - const rewardTokenAddress = rewardTokenInfo.address; + const rewardTokenAddress = CHAIN_CONSTANTS[chainId].rewardToken(blockNumber); beforeEach(() => { mockDb = MockDb.createMockDb(); @@ -154,8 +153,6 @@ describe("Voter Events", () => { let expectations: any = {}; beforeEach(async () => { - const rewardTokenInfo = CHAIN_CONSTANTS[chainId].rewardToken(blockNumber); - const rewardTokenAddress = rewardTokenInfo.address; const liquidityPool: LiquidityPoolAggregator = { ...mockLiquidityPoolData, id: poolAddress, diff --git a/test/EventHandlers/VotingReward.test.ts b/test/EventHandlers/VotingReward.test.ts index b647dae..984148e 100644 --- a/test/EventHandlers/VotingReward.test.ts +++ b/test/EventHandlers/VotingReward.test.ts @@ -89,8 +89,8 @@ describe("VotingReward Events", () => { it("should update the liquidity pool aggregator with bribes data", () => { const updatedPool = resultDB.entities.LiquidityPoolAggregator.get(poolAddress); expect(updatedPool).to.not.be.undefined; - expect(updatedPool?.totalBribesUSD).to.equal(2257239866360309354000n, - "Should fetch the correct bribes data from the bribe voting reward contract"); + expect(updatedPool?.totalBribesUSD).to.not.be.undefined; + expect(updatedPool?.totalBribesUSD).to.not.equal(0n); expect(updatedPool?.lastUpdatedTimestamp).to.deep.equal(new Date(1000000 * 1000)); }); diff --git a/test/PriceOracle.test.ts b/test/PriceOracle.test.ts index 0cbd300..5a04dc8 100644 --- a/test/PriceOracle.test.ts +++ b/test/PriceOracle.test.ts @@ -69,7 +69,7 @@ describe("PriceOracle", () => { ...mockToken0Data, lastUpdatedTimestamp: testLastUpdated }; - await PriceOracle.refreshTokenPrice(fetchedToken, [], blockNumber, blockDatetime.getTime(), chainId, mockContext); + await PriceOracle.refreshTokenPrice(fetchedToken, blockNumber, blockDatetime.getTime(), chainId, mockContext); }); it("should not update prices if the update interval hasn't passed", async () => { expect(mockContract.called).to.be.false; @@ -84,7 +84,7 @@ describe("PriceOracle", () => { ...mockToken0Data, lastUpdatedTimestamp: testLastUpdated }; - await PriceOracle.refreshTokenPrice(fetchedToken, [], blockNumber, blockDatetime.getTime(), chainId, mockContext); + await PriceOracle.refreshTokenPrice(fetchedToken, blockNumber, blockDatetime.getTime(), chainId, mockContext); updatedToken = mockContext.Token.set.lastCall.args[0]; }); it("should update prices if the update interval has passed", async () => { @@ -93,78 +93,4 @@ describe("PriceOracle", () => { }); }); }); - - describe("set_whitelisted_prices", () => { - - beforeEach(() => { - mockContract = sinon.stub(CHAIN_CONSTANTS[chainId].eth_client, "simulateContract") - .returns({ result: ["1000000000000000000", "2000000000000000000"] } as any); - }); - - it("should update existing tokens and create TokenPrice entities", async () => { - - mockContext.Token.get.returns(mockToken0Data); - - await PriceOracle.set_whitelisted_prices(chainId, blockNumber, blockDatetime, mockContext); - - // Check if token was updated - const updatedToken = mockContext.Token.set.args[0][0]; - expect(updatedToken).to.not.be.undefined; - expect(updatedToken?.pricePerUSDNew).to.equal(mockToken0Data.pricePerUSDNew); - expect(updatedToken?.lastUpdatedTimestamp).to.deep.equal(blockDatetime); - - // Check if TokenPrice was created - const tokenPrice = mockContext.TokenPriceSnapshot.set.args[0][0]; - expect(tokenPrice).to.not.be.undefined; - expect(tokenPrice?.pricePerUSDNew).to.equal(mockToken0Data.pricePerUSDNew); - expect(tokenPrice?.lastUpdatedTimestamp).to.deep.equal(blockDatetime); - }); - - it("should create new tokens when they don't exist", async () => { - - const timeDelta = CHAIN_CONSTANTS[chainId].oracle.updateDelta * 1000; - const updatedBlockDatetime = new Date(blockDatetime.getTime() + 2 * timeDelta); - - mockContext.Token.get.returns(null); - await PriceOracle.set_whitelisted_prices(chainId, blockNumber, updatedBlockDatetime, mockContext); - - // Check if new token was created - const newToken = mockContext.Token.set.args[0][0]; - expect(newToken).to.not.be.undefined; - expect(newToken?.pricePerUSDNew).to.equal(mockToken0Data.pricePerUSDNew); - expect(newToken?.lastUpdatedTimestamp).to.deep.equal(updatedBlockDatetime); - - // Check if TokenPrice was created - const tokenPrice = mockContext.TokenPriceSnapshot.set.args[0][0]; - expect(tokenPrice).to.not.be.undefined; - expect(tokenPrice?.pricePerUSDNew).to.equal(mockToken0Data.pricePerUSDNew); - expect(tokenPrice?.lastUpdatedTimestamp).to.deep.equal(updatedBlockDatetime); - }); - - it("should not update prices if the update interval hasn't passed", async () => { - // Set last updated time to be recent - PriceOracle.setPricesLastUpdated(chainId, new Date(blockDatetime.getTime() - 1000)); // 1 second ago - - await PriceOracle.set_whitelisted_prices(chainId, blockNumber, blockDatetime, mockContext); - - // Check that no tokens were updated - const setStub = mockContext.Token.set; - expect(setStub.called).to.be.false; - }); - - it("should handle errors when fetching prices", async () => { - // Make the contract call throw an error - mockContract.rejects(new Error("API Error")); - - const timeDelta = CHAIN_CONSTANTS[chainId].oracle.updateDelta * 1000; - const updatedBlockDatetime = new Date(blockDatetime.getTime() + 5 * timeDelta); - - await PriceOracle.set_whitelisted_prices(chainId, blockNumber, updatedBlockDatetime, mockContext); - - // Check that tokens were created with price 0 - const token = mockContext.Token.set.args[0][0]; - expect(token).to.not.be.undefined; - expect(token?.pricePerUSDNew).to.equal(BigInt(0)); - }); - }); });