From c5858761da869d9cc51bb84d45d9444077d7c7e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pr=C3=A9vost?= <998369+prevostc@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:55:34 +0100 Subject: [PATCH] Update snapshots on clock logic --- README.md | 4 +- config/arbitrum.json | 2 +- config/base.json | 2 +- config/optimism.json | 2 +- src/clock.ts | 351 ++++++++++++--------------------------- src/entity/clock.ts | 30 ++-- src/utils/price.ts | 20 +-- src/vault-interaction.ts | 5 +- 8 files changed, 141 insertions(+), 275 deletions(-) diff --git a/README.md b/README.md index 83f20b2..a8e69cd 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,13 @@ This Subgraph sources events from the Beefy CLM contracts in different networks. ### GraphiQL Explorer - Arbitrum: [https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-arbitrum/graphql](https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-arbitrum/graphql) +- Base: [https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-base/graphql](https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-base/graphql) - Optimism: [https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-optimism/graphql](https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-optimism/graphql) ### Api Endpoints - Arbitrum: [https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-arbitrum](https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-arbitrum) +- Base: [https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-base](https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-base) - Optimism: [https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-optimism](https://api.0xgraph.xyz/subgraphs/name/beefyfinance/clm-optimism) # Contributing @@ -76,7 +78,7 @@ yarn test:lint # run prettier linter ### How to add a new network 1. Add the network configuration [config/.json](config/). - - `clockTickBlocks` is the number of blocks between each clock tick, aim for a clock tick every 15 minutes. + - `clockTickBlocks` is the number of blocks between each clock tick, aim for a clock tick every 5 minutes. - Find the uniswap v3, QuoterV2 contract address [on uniswap's documentation](https://docs.uniswap.org/contracts/v3/reference/deployments) - Find the /USD price feed [on chainlink's documentation](https://docs.chain.link/data-feeds/price-feeds/addresses#networks). Verify that it's a ChainLink `AggregatorV3Interface` with the `latestRoundData()` method. 2. Add dev RPCs in graph-node config [docker/graph-node/config.toml](docker/graph-node/config.toml). diff --git a/config/arbitrum.json b/config/arbitrum.json index dd1513c..9db0a73 100644 --- a/config/arbitrum.json +++ b/config/arbitrum.json @@ -9,7 +9,7 @@ "strategyFactoryStartBlock": 193389019, "boostFactoryAddress": "0x2951c806a75b19954ce0bed477676a54f3c1c200", "boostFactoryStartBlock": 193388971, - "clockTickBlocks": 3600, + "clockTickBlocks": 1300, "wrappedNativeAddress": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", "wrappedNativeDecimals": 18, diff --git a/config/base.json b/config/base.json index c0d87f7..d748bad 100644 --- a/config/base.json +++ b/config/base.json @@ -9,7 +9,7 @@ "strategyFactoryStartBlock": 12294025, "boostFactoryAddress": "0x1111111111111111111111111111111111111111", "boostFactoryStartBlock": 12294025, - "clockTickBlocks": 450, + "clockTickBlocks": 150, "wrappedNativeAddress": "0x4200000000000000000000000000000000000006", "wrappedNativeDecimals": 18, diff --git a/config/optimism.json b/config/optimism.json index da41962..c7d16b4 100644 --- a/config/optimism.json +++ b/config/optimism.json @@ -9,7 +9,7 @@ "strategyFactoryStartBlock": 117800962, "boostFactoryAddress": "0xf0a7626eccbee00af144bb1f77cd187af85bbf41", "boostFactoryStartBlock": 117800956, - "clockTickBlocks": 750, + "clockTickBlocks": 250, "wrappedNativeAddress": "0x4200000000000000000000000000000000000006", "wrappedNativeDecimals": 18, diff --git a/src/clock.ts b/src/clock.ts index 5eef595..8eee15c 100644 --- a/src/clock.ts +++ b/src/clock.ts @@ -1,7 +1,7 @@ import { Address, BigDecimal, Bytes, ethereum, log } from "@graphprotocol/graph-ts" -import { ClockTick, Investor, Token } from "../generated/schema" -import { DAY, MINUTES_15, getIntervalFromTimestamp } from "./utils/time" -import { getClockTickId } from "./entity/clock" +import { ClockTick, Investor } from "../generated/schema" +import { DAY, MINUTES_15, SNAPSHOT_PERIODS } from "./utils/time" +import { getClockTick } from "./entity/clock" import { getBeefyCLProtocol, getBeefyCLProtocolSnapshot } from "./entity/protocol" import { ZERO_BD, tokenAmountToDecimal } from "./utils/decimal" import { getToken } from "./entity/token" @@ -16,52 +16,20 @@ export function handleClockTick(block: ethereum.Block): void { log.debug("handleClockTick: new tick detected: {}", [timestamp.toString()]) - let period = MINUTES_15 - let interval = getIntervalFromTimestamp(timestamp, period) - let id = getClockTickId(timestamp, period) - let tick = ClockTick.load(id) - log.debug("handleClockTick: MINUTES_15 range {} (timestamp: {}, interval: {}, period: {}, id: {})", [ - tick ? "exists" : "new", - timestamp.toString(), - interval.toString(), - period.toString(), - id.toHexString(), - ]) - if (!tick) { - tick = new ClockTick(id) - tick.timestamp = timestamp - tick.period = period - tick.roundedTimestamp = interval - tick.save() - - handleNew15Minutes(tick) + let tickRes15min = getClockTick(timestamp, MINUTES_15) + if (!tickRes15min.isNew) { + log.debug("handleClockTick: tick already exists for 15 minutes period", []) + return } - period = DAY - interval = getIntervalFromTimestamp(timestamp, period) - id = getClockTickId(timestamp, period) - tick = ClockTick.load(id) - log.debug("handleClockTick: DAY range {} (timestamp: {}, interval: {}, period: {}, id: {})", [ - tick ? "exists" : "new", - timestamp.toString(), - interval.toString(), - period.toString(), - id.toHexString(), - ]) - if (!tick) { - tick = new ClockTick(id) - tick.timestamp = timestamp - tick.period = period - tick.roundedTimestamp = interval - tick.save() - - handleNewDay(tick) - } + let tickResDay = getClockTick(timestamp, DAY) + updateDataOnClockTick(tickRes15min.tick, tickResDay.isNew) } -function handleNew15Minutes(tick: ClockTick): void { - log.debug("handleNew15Minutes: new 15min range detected: {}", [tick.roundedTimestamp.toString()]) +function updateDataOnClockTick(tick: ClockTick, isNewDay: boolean): void { + log.debug("updateDataOnClockTick: new day detected: {}", [tick.roundedTimestamp.toString()]) + const periods = SNAPSHOT_PERIODS const protocol = getBeefyCLProtocol() let protocolTotalValueLockedUSD = ZERO_BD let protocolActiveVaultCount = 0 @@ -70,12 +38,12 @@ function handleNew15Minutes(tick: ClockTick): void { const vaults = protocol.vaults.load() const investorTVL = new Map() - log.debug("handleNew15Minutes: fetching data for {} vaults", [vaults.length.toString()]) + log.debug("updateDataOnClockTick: fetching data for {} vaults", [vaults.length.toString()]) for (let i = 0; i < vaults.length; i++) { const vault = vaults[i] if (!isVaultRunning(vault)) { - log.debug("handleNew15Minutes: vault {} is not running", [vault.id.toHexString()]) + log.debug("updateDataOnClockTick: vault {} is not running", [vault.id.toHexString()]) continue } @@ -86,12 +54,12 @@ function handleNew15Minutes(tick: ClockTick): void { /////// // fetch data on chain - log.debug("handleNew15Minutes: fetching on chain data for vault {}", [vault.id.toHexString()]) + log.debug("updateDataOnClockTick: fetching on chain data for vault {}", [vault.id.toHexString()]) const vaultContract = BeefyCLVaultContract.bind(Address.fromBytes(vault.id)) const vaultBalancesRes = vaultContract.try_balances() if (vaultBalancesRes.reverted) { - log.error("updateUserPosition: balances() reverted for strategy {}", [vault.strategy.toHexString()]) - throw Error("updateUserPosition: balances() reverted") + log.error("handleNew15Minutes: balances() reverted for strategy {}", [vault.strategy.toHexString()]) + throw Error("handleNew15Minutes: balances() reverted") } const vaultBalanceUnderlying0 = tokenAmountToDecimal(vaultBalancesRes.value.value0, token0.decimals) const vaultBalanceUnderlying1 = tokenAmountToDecimal(vaultBalancesRes.value.value1, token1.decimals) @@ -103,13 +71,13 @@ function handleNew15Minutes(tick: ClockTick): void { /////// // compute derived values - log.debug("handleNew15Minutes: computing derived values for vault {}", [vault.id.toHexString()]) + log.debug("updateDataOnClockTick: computing derived values for vault {}", [vault.id.toHexString()]) const token0PriceInUSD = token0PriceInNative.times(nativePriceUSD) const token1PriceInUSD = token1PriceInNative.times(nativePriceUSD) ////// - // update latest vault usd values - log.debug("handleNew15Minutes: updating vault usd values for vault {}", [vault.id.toHexString()]) + // update vault usd values + log.debug("updateDataOnClockTick: updating vault usd values for vault {}", [vault.id.toHexString()]) vault.currentPriceOfToken0InToken1 = currentPriceInToken1 vault.currentPriceOfToken0InUSD = token0PriceInUSD vault.priceRangeMinUSD = vault.priceRangeMin1.times(token1PriceInUSD) @@ -120,13 +88,36 @@ function handleNew15Minutes(tick: ClockTick): void { vault.underlyingAmount1USD = vault.underlyingAmount1.times(token1PriceInUSD) vault.totalValueLockedUSD = vault.underlyingAmount0USD.plus(vault.underlyingAmount1USD) vault.save() + // update vault snapshots + for (let j = 0; j < periods.length; j++) { + const period = periods[j] + log.debug("updateDataOnClockTick: updating vault snapshot for vault {} and period {}", [ + vault.id.toHexString(), + period.toString(), + ]) + const vaultSnapshot = getBeefyCLVaultSnapshot(vault, tick.timestamp, period) + vaultSnapshot.currentPriceOfToken0InToken1 = vault.currentPriceOfToken0InToken1 + vaultSnapshot.currentPriceOfToken0InUSD = vault.currentPriceOfToken0InUSD + vaultSnapshot.priceRangeMinUSD = vault.priceRangeMinUSD + vaultSnapshot.priceRangeMaxUSD = vault.priceRangeMaxUSD + vaultSnapshot.underlyingAmount0 = vault.underlyingAmount0 + vaultSnapshot.underlyingAmount1 = vault.underlyingAmount1 + vaultSnapshot.underlyingAmount0USD = vault.underlyingAmount0USD + vaultSnapshot.underlyingAmount1USD = vault.underlyingAmount1USD + vaultSnapshot.totalValueLockedUSD = vault.totalValueLockedUSD + vaultSnapshot.save() + } ////// // keep track of protocol values + log.debug("handleNew15Minutes: updating protocol values for vault {}, contributing TVL {}", [ + vault.id.toHexString(), + vault.totalValueLockedUSD.toString(), + ]) protocolTotalValueLockedUSD = protocolTotalValueLockedUSD.plus(vault.totalValueLockedUSD) protocolActiveVaultCount = protocolActiveVaultCount + 1 - log.debug("handleNew15Minutes: updating {} positions for vault {}", [ + log.debug("updateDataOnClockTick: updating {} positions for vault {}", [ positions.length.toString(), vault.id.toHexString(), ]) @@ -134,25 +125,48 @@ function handleNew15Minutes(tick: ClockTick): void { for (let j = 0; j < positions.length; j++) { const position = positions[j] if (position.sharesBalance.equals(ZERO_BD)) { - log.debug("handleNew15Minutes: position {} has zero shares", [position.id.toHexString()]) + log.debug("updateDataOnClockTick: position {} has zero shares", [position.id.toHexString()]) continue } const investor = Investor.load(position.investor) if (!investor) { - log.error("handleNew15Minutes: investor {} not found", [position.investor.toHexString()]) + log.error("updateDataOnClockTick: investor {} not found", [position.investor.toHexString()]) continue } ////// // update position usd values - log.debug("handleNew15Minutes: updating position usd values for position {}", [position.id.toHexString()]) + log.debug("updateDataOnClockTick: updating position usd values for position {}", [position.id.toHexString()]) position.underlyingBalance0USD = position.underlyingBalance0.times(token0PriceInUSD) position.underlyingBalance1USD = position.underlyingBalance1.times(token1PriceInUSD) position.positionValueUSD = position.underlyingBalance0USD.plus(position.underlyingBalance1USD) + if (isNewDay) { + let last30DailyPositionValuesUSD = position.last30DailyPositionValuesUSD // required by thegraph + last30DailyPositionValuesUSD.push(position.positionValueUSD) // most recent value last + while (last30DailyPositionValuesUSD.length > 30) { + last30DailyPositionValuesUSD.shift() // remove oldest value + } + position.last30DailyPositionValuesUSD = last30DailyPositionValuesUSD + position.averageDailyPositionValueUSD30D = last30DailyPositionValuesUSD + .reduce((acc, val) => acc.plus(val), ZERO_BD) + .div(BigDecimal.fromString(last30DailyPositionValuesUSD.length.toString())) + } position.save() + // update position snapshot + for (let k = 0; k < periods.length; k++) { + const period = periods[k] + log.debug("updateDataOnClockTick: updating position snapshot for position {} and period {}", [ + position.id.toHexString(), + period.toString(), + ]) + const positionSnapshot = getInvestorPositionSnapshot(vault, investor, tick.timestamp, period) + positionSnapshot.underlyingBalance0USD = position.underlyingBalance0USD + positionSnapshot.underlyingBalance1USD = position.underlyingBalance1USD + positionSnapshot.positionValueUSD = position.positionValueUSD + positionSnapshot.save() + } - // update investor tvl if (!investorTVL.has(investor.id.toHexString())) { investorTVL.set(investor.id.toHexString(), ZERO_BD) } @@ -163,10 +177,11 @@ function handleNew15Minutes(tick: ClockTick): void { } } - // update investor moving averages // @ts-ignore let investorIdStrings: Array = investorTVL.keys() - log.debug("handleNew15Minutes: updating investor moving averages for {} investors", [ + + // update investor moving averages + log.debug("updateDataOnClockTick: updating investor moving averages for {} investors", [ investorIdStrings.length.toString(), ]) for (let i = 0; i < investorIdStrings.length; i++) { @@ -174,7 +189,7 @@ function handleNew15Minutes(tick: ClockTick): void { const id = Bytes.fromHexString(investorIdStr) const investor = Investor.load(id) if (!investor) { - log.error("handleNew15Minutes: investor {} not found", [investorIdStr]) + log.error("updateDataOnClockTick: investor {} not found", [investorIdStr]) continue } ////// @@ -184,206 +199,44 @@ function handleNew15Minutes(tick: ClockTick): void { // @ts-ignore const tvl: BigDecimal = investorTVL.get(investorIdStr) investor.totalPositionValueUSD = tvl - investor.save() - } - - /////// - // update protocol values - log.debug("handleNewDay: updating protocol values", []) - protocol.totalValueLockedUSD = protocolTotalValueLockedUSD - protocol.save() - - log.debug("handleNew15Minutes: done for {} vaults", [vaults.length.toString()]) -} - -function handleNewDay(tick: ClockTick): void { - log.debug("handleNewDay: new day detected: {}", [tick.roundedTimestamp.toString()]) - - const protocol = getBeefyCLProtocol() - let protocolTotalValueLockedUSD = ZERO_BD - let protocolActiveVaultCount = 0 - let protocolActiveInvestorCount = 0 - - const vaults = protocol.vaults.load() - const investorTVL = new Map() - - log.debug("handleNewDay: fetching data for {} vaults", [vaults.length.toString()]) - - for (let i = 0; i < vaults.length; i++) { - const vault = vaults[i] - if (!isVaultRunning(vault)) { - log.debug("handleNewDay: vault {} is not running", [vault.id.toHexString()]) - continue - } - - const strategy = getBeefyCLStrategy(vault.strategy) - const positions = vault.positions.load() - const token0 = getToken(vault.underlyingToken0) - const token1 = getToken(vault.underlyingToken1) - - /////// - // fetch data on chain - log.debug("handleNewDay: fetching on chain data for vault {}", [vault.id.toHexString()]) - const vaultContract = BeefyCLVaultContract.bind(Address.fromBytes(vault.id)) - const vaultBalancesRes = vaultContract.try_balances() - if (vaultBalancesRes.reverted) { - log.error("updateUserPosition: balances() reverted for strategy {}", [vault.strategy.toHexString()]) - throw Error("updateUserPosition: balances() reverted") - } - const vaultBalanceUnderlying0 = tokenAmountToDecimal(vaultBalancesRes.value.value0, token0.decimals) - const vaultBalanceUnderlying1 = tokenAmountToDecimal(vaultBalancesRes.value.value1, token1.decimals) - const currentPriceInToken1 = fetchCurrentPriceInToken1(vault.strategy, false) - const prices = fetchVaultPrices(vault, strategy, token0, token1) - const token0PriceInNative = prices.token0ToNative - const token1PriceInNative = prices.token1ToNative - const nativePriceUSD = prices.nativeToUsd - - /////// - // compute derived values - log.debug("handleNewDay: computing derived values for vault {}", [vault.id.toHexString()]) - const token0PriceInUSD = token0PriceInNative.times(nativePriceUSD) - const token1PriceInUSD = token1PriceInNative.times(nativePriceUSD) - - ////// - // update vault usd values - log.debug("handleNewDay: updating vault usd values for vault {}", [vault.id.toHexString()]) - vault.currentPriceOfToken0InToken1 = currentPriceInToken1 - vault.currentPriceOfToken0InUSD = token0PriceInUSD - vault.priceRangeMinUSD = vault.priceRangeMin1.times(token1PriceInUSD) - vault.priceRangeMaxUSD = vault.priceRangeMax1.times(token1PriceInUSD) - vault.underlyingAmount0 = vaultBalanceUnderlying0 - vault.underlyingAmount1 = vaultBalanceUnderlying1 - vault.underlyingAmount0USD = vault.underlyingAmount0.times(token0PriceInUSD) - vault.underlyingAmount1USD = vault.underlyingAmount1.times(token1PriceInUSD) - vault.totalValueLockedUSD = vault.underlyingAmount0USD.plus(vault.underlyingAmount1USD) - vault.save() - // update vault snapshot - const period = DAY - log.debug("handleNewDay: updating vault snapshot for vault {} and period {}", [ - vault.id.toHexString(), - period.toString(), - ]) - const vaultSnapshot = getBeefyCLVaultSnapshot(vault, tick.timestamp, period) - vaultSnapshot.currentPriceOfToken0InToken1 = vault.currentPriceOfToken0InToken1 - vaultSnapshot.currentPriceOfToken0InUSD = vault.currentPriceOfToken0InUSD - vaultSnapshot.priceRangeMinUSD = vault.priceRangeMinUSD - vaultSnapshot.priceRangeMaxUSD = vault.priceRangeMaxUSD - vaultSnapshot.underlyingAmount0 = vault.underlyingAmount0 - vaultSnapshot.underlyingAmount1 = vault.underlyingAmount1 - vaultSnapshot.underlyingAmount0USD = vault.underlyingAmount0USD - vaultSnapshot.underlyingAmount1USD = vault.underlyingAmount1USD - vaultSnapshot.totalValueLockedUSD = vault.totalValueLockedUSD - vaultSnapshot.save() - - ////// - // keep track of protocol values - protocolTotalValueLockedUSD = protocolTotalValueLockedUSD.plus(vault.totalValueLockedUSD) - protocolActiveVaultCount = protocolActiveVaultCount + 1 - - log.debug("handleNewDay: updating {} positions for vault {}", [positions.length.toString(), vault.id.toHexString()]) - - for (let j = 0; j < positions.length; j++) { - const position = positions[j] - if (position.sharesBalance.equals(ZERO_BD)) { - log.debug("handleNewDay: position {} has zero shares", [position.id.toHexString()]) - continue + if (isNewDay) { + let last30DailyTotalPositionValuesUSD = investor.last30DailyTotalPositionValuesUSD + last30DailyTotalPositionValuesUSD.push(tvl) // most recent value last + while (last30DailyTotalPositionValuesUSD.length > 30) { + last30DailyTotalPositionValuesUSD.shift() // remove oldest value } - - const investor = Investor.load(position.investor) - if (!investor) { - log.error("handleNewDay: investor {} not found", [position.investor.toHexString()]) - continue - } - - ////// - // update position usd values - log.debug("handleNewDay: updating position usd values for position {}", [position.id.toHexString()]) - position.underlyingBalance0USD = position.underlyingBalance0.times(token0PriceInUSD) - position.underlyingBalance1USD = position.underlyingBalance1.times(token1PriceInUSD) - position.positionValueUSD = position.underlyingBalance0USD.plus(position.underlyingBalance1USD) - let last30DailyPositionValuesUSD = position.last30DailyPositionValuesUSD // required by thegraph - last30DailyPositionValuesUSD.push(position.positionValueUSD) // most recent value last - while (last30DailyPositionValuesUSD.length > 30) { - last30DailyPositionValuesUSD.shift() // remove oldest value - } - position.last30DailyPositionValuesUSD = last30DailyPositionValuesUSD - position.averageDailyPositionValueUSD30D = last30DailyPositionValuesUSD + investor.last30DailyTotalPositionValuesUSD = last30DailyTotalPositionValuesUSD + investor.averageDailyTotalPositionValueUSD30D = last30DailyTotalPositionValuesUSD .reduce((acc, val) => acc.plus(val), ZERO_BD) - .div(BigDecimal.fromString(last30DailyPositionValuesUSD.length.toString())) - position.save() - // update position snapshot - const period = DAY - log.debug("handleNewDay: updating position snapshot for position {} and period {}", [ - position.id.toHexString(), + .div(BigDecimal.fromString(last30DailyTotalPositionValuesUSD.length.toString())) + } + investor.save() + for (let j = 0; j < periods.length; j++) { + const period = periods[j] + log.debug("updateDataOnClockTick: updating investor snapshot for investor {} and period {}", [ + investor.id.toHexString(), period.toString(), ]) - const positionSnapshot = getInvestorPositionSnapshot(vault, investor, tick.timestamp, period) - positionSnapshot.underlyingBalance0USD = position.underlyingBalance0USD - positionSnapshot.underlyingBalance1USD = position.underlyingBalance1USD - positionSnapshot.positionValueUSD = position.positionValueUSD - positionSnapshot.save() - - if (!investorTVL.has(investor.id.toHexString())) { - investorTVL.set(investor.id.toHexString(), ZERO_BD) - } - let tvl = investorTVL.get(investor.id.toHexString()) - // @ts-ignore - tvl = tvl.plus(position.positionValueUSD) - investorTVL.set(investor.id.toHexString(), tvl) + const investorSnapshot = getInvestorSnapshot(investor, tick.timestamp, period) + investorSnapshot.totalPositionValueUSD = tvl + investorSnapshot.save() } } - // @ts-ignore - let investorIdStrings: Array = investorTVL.keys() - - // update investor moving averages - log.debug("handleNewDay: updating investor moving averages for {} investors", [investorIdStrings.length.toString()]) - for (let i = 0; i < investorIdStrings.length; i++) { - const investorIdStr = investorIdStrings[i] - const id = Bytes.fromHexString(investorIdStr) - const investor = Investor.load(id) - if (!investor) { - log.error("handleNewDay: investor {} not found", [investorIdStr]) - continue - } - ////// - // keep track of protocol values - protocolActiveInvestorCount = protocolActiveInvestorCount + 1 - - // @ts-ignore - const tvl: BigDecimal = investorTVL.get(investorIdStr) - investor.totalPositionValueUSD = tvl - let last30DailyTotalPositionValuesUSD = investor.last30DailyTotalPositionValuesUSD - last30DailyTotalPositionValuesUSD.push(tvl) // most recent value last - while (last30DailyTotalPositionValuesUSD.length > 30) { - last30DailyTotalPositionValuesUSD.shift() // remove oldest value - } - investor.last30DailyTotalPositionValuesUSD = last30DailyTotalPositionValuesUSD - investor.averageDailyTotalPositionValueUSD30D = last30DailyTotalPositionValuesUSD - .reduce((acc, val) => acc.plus(val), ZERO_BD) - .div(BigDecimal.fromString(last30DailyTotalPositionValuesUSD.length.toString())) - investor.save() - const period = DAY - log.debug("handleNewDay: updating investor snapshot for investor {} and period {}", [ - investor.id.toHexString(), - period.toString(), - ]) - const investorSnapshot = getInvestorSnapshot(investor, tick.timestamp, period) - investorSnapshot.totalPositionValueUSD = tvl - investorSnapshot.save() - } - /////// // update protocol values - log.debug("handleNewDay: updating protocol values", []) + log.debug("updateDataOnClockTick: updating protocol values", []) protocol.totalValueLockedUSD = protocolTotalValueLockedUSD protocol.activeVaultCount = protocolActiveVaultCount protocol.activeInvestorCount = protocolActiveInvestorCount protocol.save() - const period = DAY - log.debug("handleNewDay: updating protocol snapshot for period {}", [period.toString()]) - const protocolSnapshot = getBeefyCLProtocolSnapshot(tick.timestamp, period) - protocolSnapshot.totalValueLockedUSD = protocol.totalValueLockedUSD - protocolSnapshot.activeVaultCount = protocol.activeVaultCount - log.debug("handleNewDay: done for {} vaults", [vaults.length.toString()]) + for (let i = 0; i < periods.length; i++) { + const period = periods[i] + log.debug("updateDataOnClockTick: updating protocol snapshot for period {}", [period.toString()]) + const protocolSnapshot = getBeefyCLProtocolSnapshot(tick.timestamp, period) + protocolSnapshot.totalValueLockedUSD = protocol.totalValueLockedUSD + protocolSnapshot.activeVaultCount = protocol.activeVaultCount + } + + log.debug("updateDataOnClockTick: done for {} vaults", [vaults.length.toString()]) } diff --git a/src/entity/clock.ts b/src/entity/clock.ts index 72d5279..14e3d8e 100644 --- a/src/entity/clock.ts +++ b/src/entity/clock.ts @@ -1,16 +1,26 @@ -import { BigInt, Bytes } from "@graphprotocol/graph-ts" +import { BigInt } from "@graphprotocol/graph-ts" import { getIntervalFromTimestamp } from "../utils/time" import { getSnapshotIdSuffix } from "../utils/snapshot" +import { ClockTick } from "../../generated/schema" - -@inline -export function getClockTickId(timestamp: BigInt, period: BigInt): Bytes { - const interval = getIntervalFromTimestamp(timestamp, period) - return getSnapshotIdSuffix(period, interval) +export function getClockTick(timestamp: BigInt, period: BigInt): ClockRes { + let interval = getIntervalFromTimestamp(timestamp, period) + let clockTickId = getSnapshotIdSuffix(period, interval) + let clockTick = ClockTick.load(clockTickId) + let isNew = false + if (!clockTick) { + isNew = true + clockTick = new ClockTick(clockTickId) + clockTick.timestamp = timestamp + clockTick.roundedTimestamp = interval + clockTick.period = period + } + return new ClockRes(clockTick, isNew) } - -@inline -export function getPreviousClockTickId(timestamp: BigInt, period: BigInt): Bytes { - return getClockTickId(timestamp.minus(period), period) +class ClockRes { + constructor( + public tick: ClockTick, + public isNew: boolean, + ) {} } diff --git a/src/utils/price.ts b/src/utils/price.ts index 079933f..b06c3c2 100644 --- a/src/utils/price.ts +++ b/src/utils/price.ts @@ -21,7 +21,7 @@ export function fetchVaultPrices( token0: Token, token1: Token, ): VaultPrices { - log.debug("updateUserPosition: fetching data for vault {}", [vault.id.toHexString()]) + log.debug("fetchVaultPrices: fetching data for vault {}", [vault.id.toHexString()]) const token0Path = strategy.lpToken0ToNativePath const token1Path = strategy.lpToken1ToNativePath @@ -30,39 +30,39 @@ export function fetchVaultPrices( if (token0Path.length > 0 && token0.id.notEqual(WNATIVE_TOKEN_ADDRESS)) { const token0PriceInNativeRes = quoter.try_quoteExactInput(token0Path, exponentToBigInt(token0.decimals)) if (token0PriceInNativeRes.reverted) { - log.error("updateUserPosition: quoteExactInput() of vault {} reverted for token {} (token0) with path {}", [ + log.error("fetchVaultPrices: quoteExactInput() of vault {} reverted for token {} (token0) with path {}", [ vault.id.toHexString(), token0.id.toHexString(), token0Path.toHexString(), ]) - throw Error("updateUserPosition: quoteExactInput() reverted") + throw Error("fetchVaultPrices: quoteExactInput() reverted") } token0PriceInNative = tokenAmountToDecimal(token0PriceInNativeRes.value.getAmountOut(), WNATIVE_DECIMALS) - log.debug("updateUserPosition: token0PriceInNativeRes: {}", [token0PriceInNative.toString()]) + log.debug("fetchVaultPrices: token0PriceInNativeRes: {}", [token0PriceInNative.toString()]) } let token1PriceInNative = ONE_BD if (token1Path.length > 0 && token1.id.notEqual(WNATIVE_TOKEN_ADDRESS)) { const token1PriceInNativeRes = quoter.try_quoteExactInput(token1Path, exponentToBigInt(token1.decimals)) if (token1PriceInNativeRes.reverted) { - log.error("updateUserPosition: quoteExactInput() of vault {} reverted for token {} (token1) with path {}", [ + log.error("fetchVaultPrices: quoteExactInput() of vault {} reverted for token {} (token1) with path {}", [ vault.id.toHexString(), token1.id.toHexString(), token1Path.toHexString(), ]) - throw Error("updateUserPosition: quoteExactInput() reverted") + throw Error("fetchVaultPrices: quoteExactInput() reverted") } token1PriceInNative = tokenAmountToDecimal(token1PriceInNativeRes.value.getAmountOut(), WNATIVE_DECIMALS) - log.debug("updateUserPosition: token1PriceInNativeRes: {}", [token1PriceInNative.toString()]) + log.debug("fetchVaultPrices: token1PriceInNativeRes: {}", [token1PriceInNative.toString()]) } // fetch the native price in USD const nativePriceUSDRes = nativePriceFeed.try_latestRoundData() if (nativePriceUSDRes.reverted) { - log.error("updateUserPosition: latestRoundData() reverted for native token", []) - throw Error("updateUserPosition: latestRoundData() reverted") + log.error("fetchVaultPrices: latestRoundData() reverted for native token", []) + throw Error("fetchVaultPrices: latestRoundData() reverted") } const nativePriceUSD = tokenAmountToDecimal(nativePriceUSDRes.value.getAnswer(), PRICE_FEED_DECIMALS) - log.debug("updateUserPosition: nativePriceUSD: {}", [nativePriceUSD.toString()]) + log.debug("fetchVaultPrices: nativePriceUSD: {}", [nativePriceUSD.toString()]) return new VaultPrices(token0PriceInNative, token1PriceInNative, nativePriceUSD) } diff --git a/src/vault-interaction.ts b/src/vault-interaction.ts index f5edbef..1294623 100644 --- a/src/vault-interaction.ts +++ b/src/vault-interaction.ts @@ -313,11 +313,12 @@ function updateUserPosition( if (isNewPosition) protocol.activeInvestorCount += 1 protocol.save() for (let i = 0; i < periods.length; i++) { + const period = periods[i] log.debug("updateUserPosition: updating protocol snapshot for vault {} and period {}", [ vault.id.toHexString(), - periods[i].toString(), + period.toString(), ]) - const protocolSnapshot = getBeefyCLProtocolSnapshot(event.block.timestamp, periods[i]) + const protocolSnapshot = getBeefyCLProtocolSnapshot(event.block.timestamp, period) protocolSnapshot.totalValueLockedUSD = protocol.totalValueLockedUSD if (newInvestor) protocolSnapshot.newInvestorCount += 1 if (investor.lastInteractionAt.lt(protocolSnapshot.roundedTimestamp))