diff --git a/abis/beefy/clm/RewardPool.json b/abis/beefy/reward-pool/RewardPool.json similarity index 100% rename from abis/beefy/clm/RewardPool.json rename to abis/beefy/reward-pool/RewardPool.json diff --git a/abis/beefy/clm/RewardPoolFactory.json b/abis/beefy/reward-pool/RewardPoolFactory.json similarity index 100% rename from abis/beefy/clm/RewardPoolFactory.json rename to abis/beefy/reward-pool/RewardPoolFactory.json diff --git a/package.json b/package.json index 97143db..588c884 100644 --- a/package.json +++ b/package.json @@ -44,5 +44,6 @@ }, "lint-staged": { "*.*": "prettier --write" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/schema.graphql b/schema.graphql index 7b9f303..7c3301c 100644 --- a/schema.graphql +++ b/schema.graphql @@ -386,22 +386,12 @@ type CLM @entity { manager: ClmManager! "The strategy of the CLM. The strategy is responsible for managing the CLM's assets." strategy: ClmStrategy! - """ - The reward pool of the CLM. - The reward pool is where the CLM's earnings are sent if the ClmManager does not automatically compound. - It only makes sense to have one reward pool per CLM, but it is possible to have multiple by design. - """ - rewardPools: [ClmRewardPool!]! @derivedFrom(field: "clm") "The current lifecycle status of the CLM" lifecycle: ProductLifecycle! "The manager token data of the CLM. The ClmManager is also an ERC20 token. This tokens represents the shares of the manager." managerToken: Token! - "The reward pool tokens of the CLM. This is where the CLM's earnings are sent if the CLM does not automatically compound." - rewardPoolTokens: [Token!]! - "The reward pool token addresses of the CLM. This is the source of truth for other tables reward ordering." - rewardPoolTokensOrder: [Bytes!]! "The underlying tokens contained in the CLM. This is the first token." underlyingToken0: Token! @@ -412,17 +402,11 @@ type CLM @entity { outputTokens: [Token!]! "The output token addresses of the CLM. This is the source of truth for other tables output ordering." outputTokensOrder: [Bytes!]! - "The reward tokens of the CLM's rewardpool. These will be rewarded to positions when the CLM earns non-compounding yield." - rewardTokens: [Token!]! - "The reward token addresses of the CLM. This is the source of truth for other tables reward ordering." - rewardTokensOrder: [Bytes!]! # ----- PRICES & STATS ----- "The total supply of the CLM shares token in circulation. Express with `managerToken.decimals` decimals." managerTotalSupply: BigInt! - "Total supply of the reward pool token. Express with `clm.rewardTokensOrder[idx].decimals` decimals." - rewardPoolsTotalSupply: [BigInt!]! "Latest token 0 price in native we have seen. Expressed with 18 decimals." token0ToNativePrice: BigInt! @@ -430,8 +414,6 @@ type CLM @entity { token1ToNativePrice: BigInt! "Latest output token prices in native we have seen. Ordered by clm.outputTokensOrder. Expressed with 18 decimals." outputToNativePrices: [BigInt!]! - "Latest reward token prices in native we have seen. Ordered by clm.rewardTokensOrder. Expressed with 18 decimals." - rewardToNativePrices: [BigInt!]! "Latest native token price we have seen. Expressed with 18 decimals." nativeToUSDPrice: BigInt! @@ -469,6 +451,9 @@ type CLM @entity { "All CLM interactions for this CLM" interactions: [ClmPositionInteraction!]! @derivedFrom(field: "clm") + + "Reward pools of the CLM" + rewardPools: [RewardPool!]! @derivedFrom(field: "clm") } """ @@ -499,8 +484,6 @@ type ClmSnapshot @entity { "The total supply of the manager token in circulation. Express with `sharesToken.decimals` decimals." managerTotalSupply: BigInt! - "Total supply of the reward pool token. Express with `clm.rewardPoolTokensOrder[idx].decimals` decimals." - rewardPoolsTotalSupply: [BigInt!]! "Latest token 0 price in native of this snapshot. Expressed with 18 decimals." token0ToNativePrice: BigInt! @@ -508,8 +491,6 @@ type ClmSnapshot @entity { token1ToNativePrice: BigInt! "Latest output token prices in native of this snapshot. Ordered by clm.outputTokensOrder. Expressed with 18 decimals." outputToNativePrices: [BigInt!]! - "Latest reward token prices in native of this snapshot. Ordered by clm.rewardTokensOrder. Expressed with 18 decimals." - rewardToNativePrices: [BigInt!]! "Latest native token price of this snapshot. Expressed with 18 decimals." nativeToUSDPrice: BigInt! @@ -567,23 +548,6 @@ type ClmStrategy @entity { isInitialized: Boolean! } -""" -Some ClmManager do not automatically compound their earnings. Those earnings are sent to the reward pool instead. -This entity is mostly used to start tracking the events and link them to the CLM on new event -""" -type ClmRewardPool @entity { - "The strategy address" - id: Bytes! - "The CLM the reward pool is for" - clm: CLM! - "The manager that this reward pool is linked to" - manager: ClmManager! - "The transaction that created the reward pool" - createdWith: Transaction! - "Technical field to remember if the strategy was already initialized" - isInitialized: Boolean! -} - """ ClmManagers are harvested by the strategy. This event is emitted when the strategy harvests the manager. """ @@ -619,8 +583,6 @@ type ClmHarvestEvent @entity(immutable: true) { "Total amount of liquidity in the manager at time of harvest" managerTotalSupply: BigInt! - "Total amount of reward pool shares at time of harvest. Ordered by clm.rewardPoolTokensOrder." - rewardPoolsTotalSupply: [BigInt!]! "Token 0 price in native at the time of harvest. Expressed with 18 decimals." token0ToNativePrice: BigInt! @@ -628,8 +590,6 @@ type ClmHarvestEvent @entity(immutable: true) { token1ToNativePrice: BigInt! "Output token prices in native at the time of harvest. Expressed with 18 decimals. Ordered by clm.outputTokensOrder." outputToNativePrices: [BigInt!]! - "Reward token prices in native at the time of harvest. Expressed with 18 decimals. Ordered by clm.rewardTokensOrder." - rewardToNativePrices: [BigInt!]! "Native token price at the time of harvest. Expressed with 18 decimals." nativeToUSDPrice: BigInt! } @@ -677,8 +637,6 @@ type ClmManagerCollectionEvent @entity(immutable: true) { token1ToNativePrice: BigInt! "Output token prices in native at the time of the collection. Expressed with 18 decimals. Ordered by clm.outputTokensOrder." outputToNativePrices: [BigInt!]! - "Reward token prices in native at the time of the collection. Expressed with 18 decimals. Ordered by clm.rewardTokensOrder." - rewardToNativePrices: [BigInt!]! "Native token price at the time of the interaction. Expressed with 18 decimals." nativeToUSDPrice: BigInt! } @@ -699,8 +657,6 @@ type ClmPosition @entity { "The amount of manager shares the investor holds" managerBalance: BigInt! - "Amount of reward pool shares the investor holds. Ordered by clm.rewardPoolTokensOrder." - rewardPoolBalances: [BigInt!]! "Total amount of CLM shares the investor holds. Should always equal to managerBalance + rewardPoolBalance. This is mostly used for filtering." totalBalance: BigInt! @@ -713,12 +669,6 @@ enum ClmPositionInteractionType { MANAGER_DEPOSIT "The investor withdrew funds from the CLM" MANAGER_WITHDRAW - "The investor staked in the reward pool of the CLM and received reward pool shares" - REWARD_POOL_STAKE - "The investor unstaked from the reward pool of the CLM and received underlying tokens" - REWARD_POOL_UNSTAKE - "The investor claimed their rewards from the reward pool of the CLM" - REWARD_POOL_CLAIM } type ClmPositionInteraction @entity(immutable: true) { @@ -745,8 +695,6 @@ type ClmPositionInteraction @entity(immutable: true) { "The amount of manager shares the investor holds at the time of the interaction" managerBalance: BigInt! - "The amount of reward pool shares the investor holds at the time of the interaction. Ordered by clm.rewardPoolTokensOrder." - rewardPoolBalances: [BigInt!]! "Total amount of CLM shares the investor holds. Should always equal to managerBalance + rewardPoolBalance. This is mostly used for filtering." totalBalance: BigInt! "The amount of first underlying tokens the investor is entitled to at the time of the interaction" @@ -756,10 +704,6 @@ type ClmPositionInteraction @entity(immutable: true) { "Amount of manager shares change in the interaction" managerBalanceDelta: BigInt! - "Amount of reward pool shares change in the interaction. Ordered by clm.rewardPoolTokensOrder." - rewardPoolBalancesDelta: [BigInt!]! - "Amount of reward tokens change in the interaction. Ordered by clm.rewardTokensOrder." - rewardBalancesDelta: [BigInt!]! "Amount of underlying token 0 change in the interaction" underlyingBalance0Delta: BigInt! "Amount of underlying token 0 change in the interaction" @@ -771,7 +715,176 @@ type ClmPositionInteraction @entity(immutable: true) { token1ToNativePrice: BigInt! "Output token prices in native at the time of the interaction. Expressed with 18 decimals. Ordered by clm.outputTokensOrder." outputToNativePrices: [BigInt!]! - "Reward token prices in native at the time of the interaction. Expressed with 18 decimals. Ordered by clm.rewardTokensOrder." + "Native token price at the time of the interaction. Expressed with 18 decimals." + nativeToUSDPrice: BigInt! +} + +############################### +##### Reward Pool Contracts ### +############################### + +type RewardPool @entity { + "The reward pool address" + id: Bytes! + + "The protocol the reward pool belongs to" + protocol: Protocol! + + "The current lifecycle status of the reward pool" + lifecycle: ProductLifecycle! + "The transaction that created this reward pool" + createdWith: Transaction! + + "The CLM this reward pool applies to." + clm: CLM + "The classic vault this reward pool applies to." + classic: Classic + + "The reward pool's share token" + shareToken: Token! + + "The reward pool reward tokens. Tokens earned by staking in the reward pool." + rewardTokens: [Token!]! + "The reward pool reward token addresses. This is the source of truth for other tables reward ordering." + rewardTokensOrder: [Bytes!]! + + "The reward pool's underlying LP token. This can be a classic vault or a CLM manager" + underlyingToken: Token! + + # ----- PRICES & STATS ----- + + "The total supply of the reward pool shares token in circulation. Express with `sharesToken.decimals` decimals." + rewardPoolSharesTotalSupply: BigInt! + + "Latest LP token price in native we have seen. Expressed with 18 decimals." + underlyingToNativePrice: BigInt! + "Latest reward token prices in native we have seen. Ordered by rewardPool.rewardTokensOrder. Expressed with 18 decimals." + rewardToNativePrices: [BigInt!]! + "Latest native token price we have seen. Expressed with 18 decimals." + nativeToUSDPrice: BigInt! + + "Amount of underlying token in the reward pool" + underlyingAmount: BigInt! + + "All positions in the Reward Pool" + positions: [RewardPoolPosition!]! @derivedFrom(field: "rewardPool") + + "Snapshot of the Reward Pool's stats" + snapshots: [RewardPoolSnapshot!]! @derivedFrom(field: "rewardPool") + + "All Reward Pool interactions for this Reward Pool" + interactions: [RewardPoolPositionInteraction!]! @derivedFrom(field: "rewardPool") +} + +""" +A snapshot of the Reward Pool's stats. +Any event that happens in the Reward Pool is recorded in a snapshot. +We keep multiple snapshots time frames as noted by the "period" field. +Snapshots include: daily, weekly, yearly. +""" +type RewardPoolSnapshot @entity { + "RewardPool.id + period + timestamp" + id: Bytes! + + "The Reward Pool the snapshot is for" + rewardPool: RewardPool! + + """ + Duration of the snapshot period in seconds. + Available periods: + - 1 day: 86400 + - 1 week: 604800 + - 1 year: 31536000 + """ + period: BigInt! + "Timestamp the snapshot was initiated at, rounded to period" + roundedTimestamp: BigInt! + "Actual timestamp snapshot was initiated at" + timestamp: BigInt! + + "The total supply of the reward pool shares token in circulation. Express with `sharesToken.decimals` decimals." + rewardPoolSharesTotalSupply: BigInt! + + "Latest LP token price in native of this snapshot. Expressed with 18 decimals." + underlyingToNativePrice: BigInt! + "Latest reward token prices in native of this snapshot. Ordered by rewardPool.rewardTokensOrder. Expressed with 18 decimals." + rewardToNativePrices: [BigInt!]! + "Latest native token price of this snapshot. Expressed with 18 decimals." + nativeToUSDPrice: BigInt! + + "Amount of underlying tokens in the reward pool" + underlyingAmount: BigInt! +} + +""" +An investor position is a record of an investor's position in a Reward Pool. +""" +type RewardPoolPosition @entity { + "RewardPool.id + investor address" + id: Bytes! + + "The Reward Pool the investor has a position in" + rewardPool: RewardPool! + "The investor that has a position in the Reward Pool" + investor: Investor! + "The transaction that created the investor position" + createdWith: Transaction! + + "The amount of reward pool shares the investor holds" + rewardPoolBalance: BigInt! + "Total amount of reward pool shares the investor holds. This is mostly used for filtering." + totalBalance: BigInt! + + "All investor position interactions" + interactions: [RewardPoolPositionInteraction!]! @derivedFrom(field: "investorPosition") +} + +enum RewardPoolPositionInteractionType { + "The investor deposited funds into the Reward Pool" + REWARD_POOL_DEPOSIT + "The investor withdrew funds from the Reward Pool" + REWARD_POOL_WITHDRAW + "The investor claimed their rewards from the Reward Pool" + REWARD_CLAIM +} + +type RewardPoolPositionInteraction @entity(immutable: true) { + "transaction hash + event log index" + id: Bytes! + + "The Reward Pool the investor has a position in" + rewardPool: RewardPool! + "The investor that has a position in the Reward Pool" + investor: Investor! + "The investor position the interaction is for" + investorPosition: RewardPoolPosition! + + "The transaction that created the investor position interaction" + createdWith: Transaction! + + "Block number of the interaction" + blockNumber: BigInt! + "The timestamp of the interaction" + timestamp: BigInt! + + "The type of the interaction" + type: RewardPoolPositionInteractionType! + + "The amount of reward pool shares the investor holds at the time of the interaction" + rewardPoolBalance: BigInt! + "Total amount of reward pool shares the investor holds. This is mostly used for filtering." + totalBalance: BigInt! + "Amount of underlying token the investor is entitled to at the time of the interaction" + underlyingBalance: BigInt! + + "Amount of reward pool shares change in the interaction" + rewardPoolBalanceDelta: BigInt! + "Amount of underlying token change in the interaction" + underlyingBalanceDelta: BigInt! + + "LP token price in native at the time of the interaction. Expressed with 18 decimals." + underlyingToNativePrice: BigInt! + "Reward token prices in native at the time of the interaction. Expressed with 18 decimals." rewardToNativePrices: [BigInt!]! "Native token price at the time of the interaction. Expressed with 18 decimals." nativeToUSDPrice: BigInt! diff --git a/src/classic/clock.ts b/src/classic/clock.ts index 1cb13aa..d711482 100644 --- a/src/classic/clock.ts +++ b/src/classic/clock.ts @@ -1,8 +1,9 @@ import { ClockTick } from "../../generated/schema" import { getBeefyClassicProtocol } from "../common/entity/protocol" import { isClassicInitialized } from "./entity/classic" -import { isClmManager, isClmRewardPool } from "../clm/entity/clm" +import { isClmManager } from "../clm/entity/clm" import { fetchClassicData, updateClassicDataAndSnapshots } from "./utils/classic-data" +import { isRewardPool } from "../reward-pool/entity/reward-pool" export function updateClassicDataOnClockTick(tick: ClockTick): void { const protocol = getBeefyClassicProtocol() @@ -15,7 +16,7 @@ export function updateClassicDataOnClockTick(tick: ClockTick): void { } // speed up the process by skipping vaults on non-reward pools - if (isClmRewardPool(classic.underlyingToken) || isClmManager(classic.underlyingToken)) { + if (isRewardPool(classic.underlyingToken) || isClmManager(classic.underlyingToken)) { const classicData = fetchClassicData(classic) updateClassicDataAndSnapshots(classic, classicData, tick.timestamp) } diff --git a/src/classic/entity/classic.ts b/src/classic/entity/classic.ts index e3332af..8361d00 100644 --- a/src/classic/entity/classic.ts +++ b/src/classic/entity/classic.ts @@ -11,6 +11,10 @@ export function isClassicInitialized(classic: Classic): boolean { return classic.lifecycle != PRODUCT_LIFECYCLE_INITIALIZING } +export function isClassicVault(address: Bytes): boolean { + return ClassicVault.load(address) != null +} + export function getClassic(vaultAddress: Bytes): Classic { let classic = Classic.load(vaultAddress) if (!classic) { diff --git a/src/classic/utils/classic-data.ts b/src/classic/utils/classic-data.ts index a67e4b1..afeb99c 100644 --- a/src/classic/utils/classic-data.ts +++ b/src/classic/utils/classic-data.ts @@ -16,8 +16,9 @@ import { import { Multicall3Params, MulticallResult, multicall } from "../../common/utils/multicall" import { CLASSIC_SNAPSHOT_PERIODS } from "./snapshot" import { getClassicSnapshot } from "../entity/classic" -import { getCLM, getClmRewardPool, isClmManager, isClmRewardPool } from "../../clm/entity/clm" +import { getCLM, isClmManager } from "../../clm/entity/clm" import { getToken } from "../../common/entity/token" +import { getRewardPool, isRewardPool } from "../../reward-pool/entity/reward-pool" export function fetchClassicData(classic: Classic): ClassicData { const vaultAddress = classic.vault @@ -126,9 +127,17 @@ export function fetchClassicData(classic: Classic): ClassicData { let underlyingToNativePrice = ZERO_BI let clm: CLM | null = null - if (isClmRewardPool(classic.underlyingToken)) { - const rewardPool = getClmRewardPool(classic.underlyingToken) - clm = getCLM(rewardPool.clm) + if (isRewardPool(classic.underlyingToken)) { + const rewardPool = getRewardPool(classic.underlyingToken) + const rpClm = rewardPool.clm + if (!rpClm) { + log.error( + "Reward Pool {} does not have a CLM when fetching data for a vault clearly on top of a reward pool. Check RP init function and set the correct address in the clm field", + [rewardPool.id.toHexString()], + ) + } else { + clm = getCLM(rpClm) + } } if (isClmManager(classic.underlyingToken)) { diff --git a/src/clm/compound.ts b/src/clm/compound.ts index a09b0e2..6bcc084 100644 --- a/src/clm/compound.ts +++ b/src/clm/compound.ts @@ -68,11 +68,9 @@ function handleClmStrategyHarvest( harvest.compoundedAmount1 = compoundedAmount1 harvest.collectedOutputAmounts = collectedOutputAmounts harvest.managerTotalSupply = clmData.managerTotalSupply - harvest.rewardPoolsTotalSupply = clmData.rewardPoolsTotalSupply harvest.token0ToNativePrice = clmData.token0ToNativePrice harvest.token1ToNativePrice = clmData.token1ToNativePrice harvest.outputToNativePrices = clmData.outputToNativePrices - harvest.rewardToNativePrices = clmData.rewardToNativePrices harvest.nativeToUSDPrice = clmData.nativeToUSDPrice harvest.save() } @@ -141,7 +139,6 @@ function handleClmStrategyFees( collect.token0ToNativePrice = clmData.token0ToNativePrice collect.token1ToNativePrice = clmData.token1ToNativePrice collect.outputToNativePrices = clmData.outputToNativePrices - collect.rewardToNativePrices = clmData.rewardToNativePrices collect.nativeToUSDPrice = clmData.nativeToUSDPrice collect.save() diff --git a/src/clm/entity/clm.ts b/src/clm/entity/clm.ts index aa0c390..8b3f3a5 100644 --- a/src/clm/entity/clm.ts +++ b/src/clm/entity/clm.ts @@ -1,5 +1,5 @@ import { BigInt, Bytes } from "@graphprotocol/graph-ts" -import { ClmRewardPool, ClmStrategy, ClmManager, ClmSnapshot, CLM } from "../../../generated/schema" +import { ClmStrategy, ClmManager, ClmSnapshot, CLM } from "../../../generated/schema" import { ADDRESS_ZERO } from "../../common/utils/address" import { ZERO_BI } from "../../common/utils/decimal" import { getIntervalFromTimestamp } from "../../common/utils/time" @@ -12,6 +12,10 @@ export function isClmInitialized(clm: CLM): boolean { return clm.lifecycle != PRODUCT_LIFECYCLE_INITIALIZING } +export function isCLMManager(managerAddress: Bytes): boolean { + return CLM.load(managerAddress) != null +} + export function getCLM(managerAddress: Bytes): CLM { let clm = CLM.load(managerAddress) if (!clm) { @@ -23,23 +27,17 @@ export function getCLM(managerAddress: Bytes): CLM { clm.lifecycle = PRODUCT_LIFECYCLE_INITIALIZING clm.managerToken = managerAddress - clm.rewardPoolTokens = [] - clm.rewardPoolTokensOrder = [] clm.underlyingToken0 = ADDRESS_ZERO clm.underlyingToken1 = ADDRESS_ZERO clm.outputTokens = [] clm.outputTokensOrder = [] - clm.rewardTokens = [] - clm.rewardTokensOrder = [] clm.managerTotalSupply = ZERO_BI - clm.rewardPoolsTotalSupply = [] clm.token0ToNativePrice = ZERO_BI clm.token1ToNativePrice = ZERO_BI clm.outputToNativePrices = [] - clm.rewardToNativePrices = [] clm.nativeToUSDPrice = ZERO_BI clm.priceOfToken0InToken1 = ZERO_BI @@ -82,22 +80,6 @@ export function getClmStrategy(strategyAddress: Bytes): ClmStrategy { return strategy } -export function isClmRewardPool(rewardPoolAddress: Bytes): boolean { - return ClmRewardPool.load(rewardPoolAddress) != null -} - -export function getClmRewardPool(rewardPoolAddress: Bytes): ClmRewardPool { - let rewardPool = ClmRewardPool.load(rewardPoolAddress) - if (!rewardPool) { - rewardPool = new ClmRewardPool(rewardPoolAddress) - rewardPool.clm = ADDRESS_ZERO - rewardPool.manager = ADDRESS_ZERO - rewardPool.createdWith = ADDRESS_ZERO - rewardPool.isInitialized = false - } - return rewardPool -} - export function getClmSnapshot(clm: CLM, timestamp: BigInt, period: BigInt): ClmSnapshot { const interval = getIntervalFromTimestamp(timestamp, period) const snapshotId = clm.id.concat(getSnapshotIdSuffix(period, interval)) @@ -111,12 +93,10 @@ export function getClmSnapshot(clm: CLM, timestamp: BigInt, period: BigInt): Clm snapshot.roundedTimestamp = interval snapshot.managerTotalSupply = ZERO_BI - snapshot.rewardPoolsTotalSupply = [] snapshot.token0ToNativePrice = ZERO_BI snapshot.token1ToNativePrice = ZERO_BI snapshot.outputToNativePrices = [] - snapshot.rewardToNativePrices = [] snapshot.nativeToUSDPrice = ZERO_BI snapshot.priceOfToken0InToken1 = ZERO_BI @@ -133,11 +113,9 @@ export function getClmSnapshot(clm: CLM, timestamp: BigInt, period: BigInt): Clm const previousSnapshot = ClmSnapshot.load(previousSnapshotId) if (previousSnapshot) { snapshot.managerTotalSupply = previousSnapshot.managerTotalSupply - snapshot.rewardPoolsTotalSupply = previousSnapshot.rewardPoolsTotalSupply snapshot.token0ToNativePrice = previousSnapshot.token0ToNativePrice snapshot.token1ToNativePrice = previousSnapshot.token1ToNativePrice snapshot.outputToNativePrices = previousSnapshot.outputToNativePrices - snapshot.rewardToNativePrices = previousSnapshot.rewardToNativePrices snapshot.nativeToUSDPrice = previousSnapshot.nativeToUSDPrice snapshot.priceOfToken0InToken1 = previousSnapshot.priceOfToken0InToken1 snapshot.priceRangeMin1 = previousSnapshot.priceRangeMin1 diff --git a/src/clm/entity/position.ts b/src/clm/entity/position.ts index 18418ca..735e15c 100644 --- a/src/clm/entity/position.ts +++ b/src/clm/entity/position.ts @@ -18,7 +18,6 @@ export function getClmPosition(clm: CLM, investor: Investor): ClmPosition { position.investor = investor.id position.createdWith = ADDRESS_ZERO position.managerBalance = ZERO_BI - position.rewardPoolBalances = [] position.totalBalance = ZERO_BI } return position diff --git a/src/clm/interaction.ts b/src/clm/interaction.ts index 6a93bbc..081cdcc 100644 --- a/src/clm/interaction.ts +++ b/src/clm/interaction.ts @@ -1,10 +1,6 @@ import { Address, BigInt, Bytes, ethereum, log } from "@graphprotocol/graph-ts" import { Transfer as ClmManagerTransferEvent } from "../../generated/templates/ClmManager/ClmManager" -import { - Transfer as RewardPoolTransferEvent, - RewardPaid as RewardPoolRewardPaidEvent, -} from "../../generated/templates/ClmRewardPool/RewardPool" -import { getClmRewardPool, getCLM, isClmInitialized } from "./entity/clm" +import { getCLM, isClmInitialized } from "./entity/clm" import { getTransaction } from "../common/entity/transaction" import { getInvestor } from "../common/entity/investor" import { getClmPosition } from "./entity/position" @@ -27,116 +23,23 @@ export function handleClmManagerTransfer(event: ClmManagerTransferEvent): void { const clm = getCLM(event.address) const managerAddress = clm.manager - const rewardPoolAddresses = clm.rewardPoolTokensOrder - - let isRewardPoolFrom = false - let isRewardPoolTo = false - for (let i = 0; i < rewardPoolAddresses.length; i++) { - const rewardPoolAddress = rewardPoolAddresses[i] - if (event.params.from.equals(rewardPoolAddress)) { - isRewardPoolFrom = true - } - if (event.params.to.equals(rewardPoolAddress)) { - isRewardPoolTo = true - } - } // don't store transfers to/from the share token mint address if ( !event.params.from.equals(SHARE_TOKEN_MINT_ADDRESS) && !event.params.from.equals(BURN_ADDRESS) && - !event.params.from.equals(managerAddress) && - !isRewardPoolFrom - ) { - updateUserPosition(clm, event, event.params.from, event.params.value.neg(), [], []) - } - - if ( - !event.params.to.equals(SHARE_TOKEN_MINT_ADDRESS) && - !event.params.to.equals(BURN_ADDRESS) && - !event.params.to.equals(managerAddress) && - !isRewardPoolTo + !event.params.from.equals(managerAddress) ) { - updateUserPosition(clm, event, event.params.to, event.params.value, [], []) - } -} - -export function handleRewardPoolTransfer(event: RewardPoolTransferEvent): void { - // sending to self - if (event.params.from.equals(event.params.to)) { - return - } - - // transfering nothing - if (event.params.value.equals(ZERO_BI)) { - return - } - - const rewardPool = getClmRewardPool(event.address) - const clm = getCLM(rewardPool.clm) - const managerAddress = clm.manager - - const rewardPoolAddresses = clm.rewardPoolTokensOrder - let isRewardPoolFrom = false - let isRewardPoolTo = false - for (let i = 0; i < rewardPoolAddresses.length; i++) { - const rewardPoolAddress = rewardPoolAddresses[i] - if (event.params.from.equals(rewardPoolAddress)) { - isRewardPoolFrom = true - } - if (event.params.to.equals(rewardPoolAddress)) { - isRewardPoolTo = true - } - } - - const rewardPoolBalancesDelta = new Array() - for (let i = 0; i < rewardPoolAddresses.length; i++) { - if (rewardPoolAddresses[i].equals(event.address)) { - rewardPoolBalancesDelta.push(event.params.value) - } else { - rewardPoolBalancesDelta.push(ZERO_BI) - } + updateUserPosition(clm, event, event.params.from, event.params.value.neg()) } - // don't store transfers to/from the share token mint address or to self if ( !event.params.to.equals(SHARE_TOKEN_MINT_ADDRESS) && !event.params.to.equals(BURN_ADDRESS) && - !event.params.to.equals(managerAddress) && - !isRewardPoolTo - ) { - updateUserPosition(clm, event, event.params.to, ZERO_BI, rewardPoolBalancesDelta, []) - } - - if ( - !event.params.from.equals(SHARE_TOKEN_MINT_ADDRESS) && - !event.params.from.equals(BURN_ADDRESS) && - !event.params.from.equals(managerAddress) && - !isRewardPoolFrom + !event.params.to.equals(managerAddress) ) { - const negRewardPoolBalancesDelta = new Array() - for (let i = 0; i < rewardPoolBalancesDelta.length; i++) { - negRewardPoolBalancesDelta.push(rewardPoolBalancesDelta[i].neg()) - } - updateUserPosition(clm, event, event.params.from, ZERO_BI, negRewardPoolBalancesDelta, []) - } -} - -export function handleRewardPoolRewardPaid(event: RewardPoolRewardPaidEvent): void { - const rewardPool = getClmRewardPool(event.address) - const clm = getCLM(rewardPool.clm) - - const rewardTokensAddresses = clm.rewardTokensOrder - const rewardBalancesDelta = new Array() - for (let i = 0; i < rewardTokensAddresses.length; i++) { - if (rewardTokensAddresses[i].equals(event.params.reward)) { - rewardBalancesDelta.push(event.params.amount) - } else { - rewardBalancesDelta.push(ZERO_BI) - } + updateUserPosition(clm, event, event.params.to, event.params.value) } - - updateUserPosition(clm, event, event.params.user, ZERO_BI, [], rewardBalancesDelta) } function updateUserPosition( @@ -144,8 +47,6 @@ function updateUserPosition( event: ethereum.Event, investorAddress: Address, managerBalanceDelta: BigInt, - rewardPoolBalancesDelta: Array, - rewardBalancesDelta: Array, ): void { if (!isClmInitialized(clm)) { return @@ -168,49 +69,23 @@ function updateUserPosition( /////// // investor position - let hasPreviousRewardPoolBalance = false - for (let i = 0; i < rewardPoolBalancesDelta.length; i++) { - if (rewardPoolBalancesDelta[i].notEqual(ZERO_BI)) { - hasPreviousRewardPoolBalance = true - break - } - } - if (position.managerBalance.equals(ZERO_BI) && !hasPreviousRewardPoolBalance) { + if (position.managerBalance.equals(ZERO_BI)) { position.createdWith = event.transaction.hash } position.managerBalance = position.managerBalance.plus(managerBalanceDelta) - const positionRewardPoolBalances = position.rewardPoolBalances // required by thegraph - for (let i = 0; i < rewardPoolBalancesDelta.length; i++) { - if (positionRewardPoolBalances.length <= i) { - // happens when position is created before the second reward pool is added - positionRewardPoolBalances.push(ZERO_BI) - } - positionRewardPoolBalances[i] = positionRewardPoolBalances[i].plus(rewardPoolBalancesDelta[i]) - } - position.rewardPoolBalances = positionRewardPoolBalances - let totalBalance = position.managerBalance - for (let i = 0; i < positionRewardPoolBalances.length; i++) { - totalBalance = totalBalance.plus(positionRewardPoolBalances[i]) - } position.totalBalance = totalBalance position.save() /////// // interaction const isSharesTransfer = !managerBalanceDelta.equals(ZERO_BI) - const isRewardPoolTransfer = rewardPoolBalancesDelta.length > 0 - const isRewardClaim = rewardBalancesDelta.some((delta) => !delta.equals(ZERO_BI)) // if both shares and reward pool are transferred, we will create two interactions let interactionId = investor.id.concat(getEventIdentifier(event)) if (isSharesTransfer) { interactionId = interactionId.concat(Bytes.fromI32(0)) - } else if (isRewardPoolTransfer) { - interactionId = interactionId.concat(Bytes.fromI32(1)) - } else if (isRewardClaim) { - interactionId = interactionId.concat(Bytes.fromI32(2)) } const interaction = new ClmPositionInteraction(interactionId) interaction.clm = clm.id @@ -221,19 +96,11 @@ function updateUserPosition( interaction.timestamp = event.block.timestamp if (isSharesTransfer) { interaction.type = managerBalanceDelta.gt(ZERO_BI) ? "MANAGER_DEPOSIT" : "MANAGER_WITHDRAW" - } else if (isRewardPoolTransfer) { - const isRewardPoolStake = rewardPoolBalancesDelta.some((delta) => delta.gt(ZERO_BI)) - interaction.type = isRewardPoolStake ? "REWARD_POOL_STAKE" : "REWARD_POOL_UNSTAKE" - } else if (isRewardClaim) { - interaction.type = "REWARD_POOL_CLAIM" } interaction.managerBalance = position.managerBalance - interaction.rewardPoolBalances = position.rewardPoolBalances interaction.totalBalance = position.totalBalance interaction.managerBalanceDelta = managerBalanceDelta - interaction.rewardPoolBalancesDelta = rewardPoolBalancesDelta - interaction.rewardBalancesDelta = rewardBalancesDelta interaction.underlyingBalance0 = ZERO_BI interaction.underlyingBalance1 = ZERO_BI @@ -251,9 +118,6 @@ function updateUserPosition( // assumption: 1 rewardPool token === 1 manager token let totalRewardPoolBalanceDelta = ZERO_BI - for (let i = 0; i < rewardPoolBalancesDelta.length; i++) { - totalRewardPoolBalanceDelta = totalRewardPoolBalanceDelta.plus(rewardPoolBalancesDelta[i]) - } const positionEquivalentInManagerBalance = managerBalanceDelta.plus(totalRewardPoolBalanceDelta) interaction.underlyingBalance0Delta = interaction.underlyingBalance0Delta.plus( clmData.token0Balance.times(positionEquivalentInManagerBalance).div(clmData.managerTotalSupply), @@ -265,7 +129,6 @@ function updateUserPosition( interaction.token0ToNativePrice = clmData.token0ToNativePrice interaction.token1ToNativePrice = clmData.token1ToNativePrice interaction.outputToNativePrices = clmData.outputToNativePrices - interaction.rewardToNativePrices = clmData.rewardToNativePrices interaction.nativeToUSDPrice = clmData.nativeToUSDPrice interaction.save() } diff --git a/src/clm/lifecycle.ts b/src/clm/lifecycle.ts index 4850be9..3d142d1 100644 --- a/src/clm/lifecycle.ts +++ b/src/clm/lifecycle.ts @@ -1,16 +1,12 @@ -import { Address, Bytes } from "@graphprotocol/graph-ts" +import { Address } from "@graphprotocol/graph-ts" import { CLM } from "../../generated/schema" import { ClmManager as ClmManagerContract, Initialized as ClmManagerInitialized, } from "../../generated/templates/ClmManager/ClmManager" -import { getClmRewardPool, getClmStrategy, getCLM, getClmManager } from "./entity/clm" +import { getClmStrategy, getCLM, getClmManager } from "./entity/clm" import { log } from "@graphprotocol/graph-ts" -import { - ClmStrategy as ClmStrategyTemplate, - ClmManager as ClmManagerTemplate, - ClmRewardPool as ClmRewardPoolTemplate, -} from "../../generated/templates" +import { ClmStrategy as ClmStrategyTemplate, ClmManager as ClmManagerTemplate } from "../../generated/templates" import { ADDRESS_ZERO } from "../common/utils/address" import { Initialized as ClmStrategyInitializedEvent, @@ -20,13 +16,6 @@ import { } from "../../generated/templates/ClmStrategy/ClmStrategy" import { ProxyCreated as CLMManagerCreatedEvent } from "../../generated/ClmManagerFactory/ClmManagerFactory" import { GlobalPause as ClmStrategyFactoryGlobalPauseEvent } from "../../generated/ClmStrategyFactory/ClmStrategyFactory" -import { ProxyCreated as RewardPoolCreatedEvent } from "../../generated/RewardPoolFactory/RewardPoolFactory" -import { - Initialized as RewardPoolInitialized, - RewardPool as ClmRewardPoolContract, - AddReward as RewardPoolAddRewardEvent, - RemoveReward as RewardPoolRemoveRewardEvent, -} from "../../generated/RewardPoolFactory/RewardPool" import { getTransaction } from "../common/entity/transaction" import { fetchAndSaveTokenData } from "../common/utils/token" import { getBeefyCLProtocol } from "../common/entity/protocol" @@ -35,8 +24,6 @@ import { PRODUCT_LIFECYCLE_PAUSED, PRODUCT_LIFECYCLE_RUNNING, } from "../common/entity/lifecycle" -import { getNullToken } from "../common/entity/token" -import { ZERO_BI } from "../common/utils/decimal" export function handleClmManagerCreated(event: CLMManagerCreatedEvent): void { const tx = getTransaction(event.block, event.transaction) @@ -236,89 +223,3 @@ export function handleClmStrategyUnpaused(event: ClmStrategyUnpausedEvent): void clm.lifecycle = PRODUCT_LIFECYCLE_RUNNING clm.save() } - -export function handleRewardPoolCreated(event: RewardPoolCreatedEvent): void { - const rewardPoolAddress = event.params.proxy - - const rewardPool = getClmRewardPool(rewardPoolAddress) - rewardPool.isInitialized = false - rewardPool.save() - - // start indexing the new reward pool - ClmRewardPoolTemplate.create(rewardPoolAddress) -} - -export function handleRewardPoolInitialized(event: RewardPoolInitialized): void { - const rewardPoolAddress = event.address - const rewardPoolContract = ClmRewardPoolContract.bind(rewardPoolAddress) - const managerAddressRes = rewardPoolContract.try_stakedToken() - if (managerAddressRes.reverted) { - log.error("handleRewardPoolInitialized: Manager address is not available for reward pool {}", [ - rewardPoolAddress.toHexString(), - ]) - return - } - const managerAddress = managerAddressRes.value - - const tx = getTransaction(event.block, event.transaction) - tx.save() - - const rewardPool = getClmRewardPool(rewardPoolAddress) - rewardPool.isInitialized = true - rewardPool.clm = managerAddress - rewardPool.manager = managerAddress - rewardPool.createdWith = tx.id - rewardPool.save() - - const rewardPoolToken = fetchAndSaveTokenData(rewardPoolAddress) - - const clm = getCLM(managerAddress) - const rewardPoolTokens = clm.rewardPoolTokens - const rewardPoolTokensOrder = clm.rewardPoolTokensOrder - rewardPoolTokens.push(rewardPoolToken.id) - rewardPoolTokensOrder.push(rewardPoolToken.id) - clm.rewardPoolTokens = rewardPoolTokens - clm.rewardPoolTokensOrder = rewardPoolTokensOrder - const rewardPoolsTotalSupply = clm.rewardPoolsTotalSupply - rewardPoolsTotalSupply.push(ZERO_BI) - clm.rewardPoolsTotalSupply = rewardPoolsTotalSupply - clm.save() - - log.info("handleRewardPoolInitialized: Reward pool {} initialized for CLM {} on block {}", [ - rewardPool.id.toHexString(), - clm.id.toHexString(), - event.block.number.toString(), - ]) -} - -export function handleRewardPoolAddReward(event: RewardPoolAddRewardEvent): void { - const rewardPoolAddress = event.address - const rewardPool = getClmRewardPool(rewardPoolAddress) - const clm = getCLM(rewardPool.clm) - - const rewardTokens = clm.rewardTokens - const rewardTokensOrder = clm.rewardTokensOrder - rewardTokens.push(event.params.reward) - rewardTokensOrder.push(event.params.reward) - clm.rewardTokens = rewardTokens - clm.rewardTokensOrder = rewardTokensOrder - clm.save() -} - -export function handleRewardPoolRemoveReward(event: RewardPoolRemoveRewardEvent): void { - const rewardPoolAddress = event.address - const rewardPool = getClmRewardPool(rewardPoolAddress) - const clm = getCLM(rewardPool.clm) - const rewardTokenAddresses = clm.rewardTokensOrder - const tokens = new Array() - for (let i = 0; i < rewardTokenAddresses.length; i++) { - if (rewardTokenAddresses[i].equals(event.params.reward)) { - tokens.push(getNullToken().id) - } else { - tokens.push(rewardTokenAddresses[i]) - } - } - clm.rewardTokens = tokens - clm.rewardTokensOrder = tokens - clm.save() -} diff --git a/src/clm/utils/clm-data.ts b/src/clm/utils/clm-data.ts index 3bad752..36fcf1f 100644 --- a/src/clm/utils/clm-data.ts +++ b/src/clm/utils/clm-data.ts @@ -15,7 +15,7 @@ import { WNATIVE_DECIMALS, } from "../../config" import { Multicall3Params, MulticallResult, multicall } from "../../common/utils/multicall" -import { getToken, isNullToken } from "../../common/entity/token" +import { getToken } from "../../common/entity/token" import { CLM_SNAPSHOT_PERIODS } from "./snapshot" import { getClmSnapshot } from "../entity/clm" @@ -26,8 +26,6 @@ export function fetchCLMData(clm: CLM): CLMData { const token0 = getToken(clm.underlyingToken0) const token1 = getToken(clm.underlyingToken1) const outputTokenAddresses = clm.outputTokensOrder - const rewardTokenAddresses = clm.rewardTokensOrder - const rewardPoolTokenAddresses = clm.rewardPoolTokensOrder const calls = [ new Multicall3Params(managerAddress, "totalSupply()", "uint256"), @@ -37,11 +35,6 @@ export function fetchCLMData(clm: CLM): CLMData { new Multicall3Params(strategyAddress, "range()", "(uint256,uint256)"), ] - for (let i = 0; i < rewardPoolTokenAddresses.length; i++) { - const rewardPoolTokenAddress = Address.fromBytes(rewardPoolTokenAddresses[i]) - calls.push(new Multicall3Params(rewardPoolTokenAddress, "totalSupply()", "uint256")) - } - // wnative price to usd if (PRICE_ORACLE_TYPE == "chainlink") { calls.push( @@ -70,9 +63,6 @@ export function fetchCLMData(clm: CLM): CLMData { for (let i = 0; i < outputTokenAddresses.length; i++) { tokensToRefresh.push(Address.fromBytes(outputTokenAddresses[i])) } - for (let i = 0; i < rewardTokenAddresses.length; i++) { - tokensToRefresh.push(Address.fromBytes(rewardTokenAddresses[i])) - } for (let i = 0; i < tokensToRefresh.length; i++) { const tokenAddress = tokensToRefresh[i] calls.push( @@ -100,19 +90,6 @@ export function fetchCLMData(clm: CLM): CLMData { ]), ) - for (let i = 0; i < rewardTokenAddresses.length; i++) { - const rewardTokenAddress = Address.fromBytes(rewardTokenAddresses[i]) - const rewardToken = getToken(rewardTokenAddress) - const amountIn = changeValueEncoding(ONE_BI, ZERO_BI, rewardToken.decimals).div(BEEFY_SWAPPER_VALUE_SCALER) - calls.push( - new Multicall3Params(BEEFY_SWAPPER_ADDRESS, "getAmountOut(address,address,uint256)", "uint256", [ - ethereum.Value.fromAddress(rewardTokenAddress), - ethereum.Value.fromAddress(WNATIVE_TOKEN_ADDRESS), - ethereum.Value.fromUnsignedBigInt(amountIn), - ]), - ) - } - for (let i = 0; i < outputTokenAddresses.length; i++) { const outputTokenAddress = Address.fromBytes(outputTokenAddresses[i]) const amountIn = changeValueEncoding(ONE_BI, ZERO_BI, WNATIVE_DECIMALS).div(BEEFY_SWAPPER_VALUE_SCALER) @@ -134,20 +111,12 @@ export function fetchCLMData(clm: CLM): CLMData { const balanceOfPoolRes = results[idx++] const priceRes = results[idx++] const rangeRes = results[idx++] - const rewardPoolsTotalSupplyRes = new Array() - for (let i = 0; i < rewardPoolTokenAddresses.length; i++) { - rewardPoolsTotalSupplyRes.push(results[idx++]) - } const priceFeedRes = results[idx++] for (let i = 0; i < tokensToRefresh.length; i++) { idx++ } const token0ToNativePriceRes = results[idx++] const token1ToNativePriceRes = results[idx++] - const rewardTokenOutputAmountsRes = new Array() - for (let i = 0; i < rewardTokenAddresses.length; i++) { - rewardTokenOutputAmountsRes.push(results[idx++]) - } const outputTokenOutputAmountsRes = new Array() for (let i = 0; i < outputTokenAddresses.length; i++) { outputTokenOutputAmountsRes.push(results[idx++]) @@ -225,19 +194,6 @@ export function fetchCLMData(clm: CLM): CLMData { log.error("Failed to fetch token0ToNativePrice for CLM {}", [clm.id.toHexString()]) } - // only some strategies have this - let rewardToNativePrices = new Array() - for (let i = 0; i < rewardTokenOutputAmountsRes.length; i++) { - const amountOutRes = rewardTokenOutputAmountsRes[i] - if (!amountOutRes.reverted) { - const amountOut = amountOutRes.value.toBigInt().times(BEEFY_SWAPPER_VALUE_SCALER) - rewardToNativePrices.push(amountOut) - } else { - rewardToNativePrices.push(ZERO_BI) - log.error("Failed to fetch rewardToNativePrices for CLM {}", [clm.id.toHexString()]) - } - } - // and have a native price in USD let nativeToUSDPrice = ZERO_BI if (!priceFeedRes.reverted) { @@ -262,18 +218,6 @@ export function fetchCLMData(clm: CLM): CLMData { log.error("Failed to fetch nativeToUSDPrice for CLM {}", [clm.id.toHexString()]) } - // only some clms have a reward pool token - let rewardPoolsTotalSupply = new Array() - for (let i = 0; i < rewardPoolsTotalSupplyRes.length; i++) { - const totalSupplyRes = rewardPoolsTotalSupplyRes[i] - if (!totalSupplyRes.reverted) { - rewardPoolsTotalSupply.push(totalSupplyRes.value.toBigInt()) - } else { - rewardPoolsTotalSupply.push(ZERO_BI) - log.error("Failed to fetch rewardPoolsTotalSupply for CLM {}", [clm.id.toHexString()]) - } - } - let outputToNativePrices = new Array() for (let i = 0; i < outputTokenOutputAmountsRes.length; i++) { const amountOutRes = outputTokenOutputAmountsRes[i] @@ -288,7 +232,6 @@ export function fetchCLMData(clm: CLM): CLMData { return new CLMData( managerTotalSupply, - rewardPoolsTotalSupply, token0Balance, token1Balance, @@ -304,7 +247,6 @@ export function fetchCLMData(clm: CLM): CLMData { token0ToNativePrice, token1ToNativePrice, outputToNativePrices, - rewardToNativePrices, nativeToUSDPrice, ) } @@ -312,7 +254,6 @@ export function fetchCLMData(clm: CLM): CLMData { class CLMData { constructor( public managerTotalSupply: BigInt, - public rewardPoolsTotalSupply: Array, public token0Balance: BigInt, public token1Balance: BigInt, @@ -328,7 +269,6 @@ class CLMData { public token0ToNativePrice: BigInt, public token1ToNativePrice: BigInt, public outputToNativePrices: Array, - public rewardToNativePrices: Array, public nativeToUSDPrice: BigInt, ) {} } @@ -336,11 +276,9 @@ class CLMData { export function updateCLMDataAndSnapshots(clm: CLM, clmData: CLMData, nowTimestamp: BigInt): CLM { // update CLM data clm.managerTotalSupply = clmData.managerTotalSupply - clm.rewardPoolsTotalSupply = clmData.rewardPoolsTotalSupply clm.token0ToNativePrice = clmData.token0ToNativePrice clm.token1ToNativePrice = clmData.token1ToNativePrice clm.outputToNativePrices = clmData.outputToNativePrices - clm.rewardToNativePrices = clmData.rewardToNativePrices clm.nativeToUSDPrice = clmData.nativeToUSDPrice clm.priceOfToken0InToken1 = clmData.priceOfToken0InToken1 clm.priceRangeMin1 = clmData.priceRangeMin1 @@ -361,11 +299,9 @@ export function updateCLMDataAndSnapshots(clm: CLM, clmData: CLMData, nowTimesta const period = CLM_SNAPSHOT_PERIODS[i] const snapshot = getClmSnapshot(clm, nowTimestamp, period) snapshot.managerTotalSupply = clm.managerTotalSupply - snapshot.rewardPoolsTotalSupply = clm.rewardPoolsTotalSupply snapshot.token0ToNativePrice = clm.token0ToNativePrice snapshot.token1ToNativePrice = clm.token1ToNativePrice snapshot.outputToNativePrices = clm.outputToNativePrices - snapshot.rewardToNativePrices = clm.rewardToNativePrices snapshot.nativeToUSDPrice = clm.nativeToUSDPrice snapshot.priceOfToken0InToken1 = clm.priceOfToken0InToken1 snapshot.priceRangeMin1 = clm.priceRangeMin1 diff --git a/src/common/entity/protocol.ts b/src/common/entity/protocol.ts index 11591ec..2542978 100644 --- a/src/common/entity/protocol.ts +++ b/src/common/entity/protocol.ts @@ -3,10 +3,9 @@ import { Protocol } from "../../../generated/schema" import { ONE_BI } from "../utils/decimal" export type ProtocolId = String -export const PROTOCOL_BEEFY_CLASSIC: ProtocolId = "Beefy" -export const PROTOCOL_BEEFY_CLASSIC_ID: Bytes = Bytes.fromByteArray(Bytes.fromBigInt(ONE_BI)) -export const PROTOCOL_BEEFY_CL: ProtocolId = "CLM" -export const PROTOCOL_BEEFY_CL_ID: Bytes = Bytes.fromByteArray(Bytes.fromBigInt(ONE_BI.plus(ONE_BI))) +const PROTOCOL_BEEFY_CLASSIC_ID: Bytes = Bytes.fromByteArray(Bytes.fromBigInt(ONE_BI)) +const PROTOCOL_BEEFY_CL_ID: Bytes = Bytes.fromByteArray(Bytes.fromBigInt(ONE_BI.plus(ONE_BI))) +const PROTOCOL_BEEFY_RP_ID: Bytes = Bytes.fromByteArray(Bytes.fromBigInt(ONE_BI.plus(ONE_BI).plus(ONE_BI))) export function getBeefyClassicProtocol(): Protocol { const protocolId = PROTOCOL_BEEFY_CLASSIC_ID @@ -29,3 +28,14 @@ export function getBeefyCLProtocol(): Protocol { } return protocol } + +export function getBeefyRewardPoolProtocol(): Protocol { + const protocolId = PROTOCOL_BEEFY_RP_ID + let protocol = Protocol.load(protocolId) + if (!protocol) { + protocol = new Protocol(protocolId) + protocol.name = "Beefy Reward Pool" + protocol.save() + } + return protocol +} diff --git a/src/reward-pool/entity/position.ts b/src/reward-pool/entity/position.ts new file mode 100644 index 0000000..55162e7 --- /dev/null +++ b/src/reward-pool/entity/position.ts @@ -0,0 +1,29 @@ +import { Bytes, store } from "@graphprotocol/graph-ts" +import { Investor, RewardPool, RewardPoolPosition } from "../../../generated/schema" +import { ADDRESS_ZERO } from "../../common/utils/address" +import { ZERO_BI } from "../../common/utils/decimal" + +// @ts-ignore +@inline +function getPositionId(rewardPool: RewardPool, investor: Investor): Bytes { + return rewardPool.id.concat(investor.id) +} + +export function getRewardPoolPosition(rewardPool: RewardPool, investor: Investor): RewardPoolPosition { + let id = getPositionId(rewardPool, investor) + let position = RewardPoolPosition.load(id) + if (!position) { + position = new RewardPoolPosition(id) + position.rewardPool = rewardPool.id + position.investor = investor.id + position.createdWith = ADDRESS_ZERO + position.rewardPoolBalance = ZERO_BI + position.totalBalance = ZERO_BI + } + return position +} + +export function removeRewardPoolPosition(rewardPool: RewardPool, investor: Investor): void { + const id = getPositionId(rewardPool, investor) + store.remove("RewardPoolPosition", id.toHexString()) +} diff --git a/src/reward-pool/entity/reward-pool.ts b/src/reward-pool/entity/reward-pool.ts new file mode 100644 index 0000000..093d469 --- /dev/null +++ b/src/reward-pool/entity/reward-pool.ts @@ -0,0 +1,72 @@ +import { BigInt, Bytes } from "@graphprotocol/graph-ts" +import { RewardPool, RewardPoolSnapshot } from "../../../generated/schema" +import { ADDRESS_ZERO } from "../../common/utils/address" +import { ZERO_BI } from "../../common/utils/decimal" +import { getIntervalFromTimestamp } from "../../common/utils/time" +import { getPreviousSnapshotIdSuffix, getSnapshotIdSuffix } from "../../common/utils/snapshot" +import { PRODUCT_LIFECYCLE_INITIALIZING } from "../../common/entity/lifecycle" +import { getBeefyRewardPoolProtocol } from "../../common/entity/protocol" + +export function isRewardPool(rewardPoolAddress: Bytes): boolean { + return RewardPool.load(rewardPoolAddress) != null +} + +export function isRewardPoolInitialized(rewardPoolAddress: Bytes): boolean { + let rewardPool = RewardPool.load(rewardPoolAddress) + return rewardPool != null && rewardPool.lifecycle != PRODUCT_LIFECYCLE_INITIALIZING +} + +export function getRewardPool(rewardPoolAddress: Bytes): RewardPool { + let rewardPool = RewardPool.load(rewardPoolAddress) + if (!rewardPool) { + rewardPool = new RewardPool(rewardPoolAddress) + rewardPool.protocol = getBeefyRewardPoolProtocol().id + rewardPool.lifecycle = PRODUCT_LIFECYCLE_INITIALIZING + rewardPool.createdWith = ADDRESS_ZERO + rewardPool.shareToken = ADDRESS_ZERO + rewardPool.rewardTokens = [] + rewardPool.rewardTokensOrder = [] + rewardPool.underlyingToken = ADDRESS_ZERO + rewardPool.rewardPoolSharesTotalSupply = ZERO_BI + rewardPool.underlyingToNativePrice = ZERO_BI + rewardPool.rewardToNativePrices = [] + rewardPool.nativeToUSDPrice = ZERO_BI + rewardPool.underlyingAmount = ZERO_BI + } + return rewardPool +} + +export function getRewardPoolSnapshot(rewardPool: RewardPool, timestamp: BigInt, period: BigInt): RewardPoolSnapshot { + const interval = getIntervalFromTimestamp(timestamp, period) + const snapshotId = rewardPool.id.concat(getSnapshotIdSuffix(period, interval)) + let snapshot = RewardPoolSnapshot.load(snapshotId) + if (!snapshot) { + snapshot = new RewardPoolSnapshot(snapshotId) + snapshot.rewardPool = rewardPool.id + + snapshot.period = period + snapshot.timestamp = timestamp + snapshot.roundedTimestamp = interval + + snapshot.rewardPoolSharesTotalSupply = ZERO_BI + + snapshot.underlyingToNativePrice = ZERO_BI + snapshot.rewardToNativePrices = [] + snapshot.nativeToUSDPrice = ZERO_BI + + snapshot.underlyingAmount = ZERO_BI + + // copy non-reseting values from the previous snapshot to the new snapshot + const previousSnapshotId = rewardPool.id.concat(getPreviousSnapshotIdSuffix(period, interval)) + const previousSnapshot = RewardPoolSnapshot.load(previousSnapshotId) + if (previousSnapshot) { + snapshot.rewardPoolSharesTotalSupply = previousSnapshot.rewardPoolSharesTotalSupply + snapshot.underlyingToNativePrice = previousSnapshot.underlyingToNativePrice + snapshot.rewardToNativePrices = previousSnapshot.rewardToNativePrices + snapshot.nativeToUSDPrice = previousSnapshot.nativeToUSDPrice + snapshot.underlyingAmount = previousSnapshot.underlyingAmount + } + } + + return snapshot +} diff --git a/src/reward-pool/interaction.ts b/src/reward-pool/interaction.ts new file mode 100644 index 0000000..bed931b --- /dev/null +++ b/src/reward-pool/interaction.ts @@ -0,0 +1,136 @@ +import { Address, BigInt, Bytes, ethereum, log } from "@graphprotocol/graph-ts" +import { + Transfer as RewardPoolTransferEvent, + RewardPaid as RewardPoolRewardPaidEvent, +} from "../../generated/templates/RewardPool/RewardPool" +import { getTransaction } from "../common/entity/transaction" +import { getInvestor } from "../common/entity/investor" +import { getRewardPoolPosition } from "./entity/position" +import { RewardPool, RewardPoolPositionInteraction } from "../../generated/schema" +import { BURN_ADDRESS, SHARE_TOKEN_MINT_ADDRESS } from "../config" +import { ZERO_BI } from "../common/utils/decimal" +import { getEventIdentifier } from "../common/utils/event" +import { getRewardPool, isRewardPoolInitialized } from "./entity/reward-pool" +import { fetchRewardPoolData, updateRewardPoolDataAndSnapshots } from "./util/reward-pool-data" + +export function handleRewardPoolTransfer(event: RewardPoolTransferEvent): void { + // sending to self + if (event.params.from.equals(event.params.to)) { + return + } + + // transfering nothing + if (event.params.value.equals(ZERO_BI)) { + return + } + + const rewardPool = getRewardPool(event.address) + + // don't store transfers to/from the share token mint address + if (!event.params.from.equals(SHARE_TOKEN_MINT_ADDRESS) && !event.params.from.equals(BURN_ADDRESS)) { + updateUserPosition(rewardPool, event, event.params.from, event.params.value.neg(), []) + } + + if (!event.params.to.equals(SHARE_TOKEN_MINT_ADDRESS) && !event.params.to.equals(BURN_ADDRESS)) { + updateUserPosition(rewardPool, event, event.params.to, event.params.value, []) + } +} + +export function handleRewardPoolRewardPaid(event: RewardPoolRewardPaidEvent): void { + const rewardPool = getRewardPool(event.address) + + const rewardTokensAddresses = rewardPool.rewardTokensOrder + const rewardBalancesDelta = new Array() + for (let i = 0; i < rewardTokensAddresses.length; i++) { + if (rewardTokensAddresses[i].equals(event.params.reward)) { + rewardBalancesDelta.push(event.params.amount) + } else { + rewardBalancesDelta.push(ZERO_BI) + } + } + + updateUserPosition(rewardPool, event, event.params.user, ZERO_BI, rewardBalancesDelta) +} + +function updateUserPosition( + rewardPool: RewardPool, + event: ethereum.Event, + investorAddress: Address, + rewardPoolBalanceDelta: BigInt, + rewardBalancesDelta: Array, +): void { + if (!isRewardPoolInitialized(rewardPool.id)) { + return + } + + const investor = getInvestor(investorAddress) + const position = getRewardPoolPosition(rewardPool, investor) + + let tx = getTransaction(event.block, event.transaction) + tx.save() + + /////// + // fetch data on chain and update rewardPool + const rewardPoolData = fetchRewardPoolData(rewardPool) + rewardPool = updateRewardPoolDataAndSnapshots(rewardPool, rewardPoolData, event.block.timestamp) + + /////// + // investor + investor.save() + + /////// + // investor position + if (position.rewardPoolBalance.equals(ZERO_BI)) { + position.createdWith = event.transaction.hash + } + + position.rewardPoolBalance = position.rewardPoolBalance.plus(rewardPoolBalanceDelta) + position.totalBalance = position.rewardPoolBalance + position.save() + + /////// + // interaction + const isSharesTransfer = !rewardPoolBalanceDelta.equals(ZERO_BI) + const isRewardClaim = rewardBalancesDelta.some((delta) => !delta.equals(ZERO_BI)) + + // if both shares and reward pool are transferred, we will create two interactions + let interactionId = investor.id.concat(getEventIdentifier(event)) + if (isSharesTransfer) { + interactionId = interactionId.concat(Bytes.fromI32(0)) + } else if (isRewardClaim) { + interactionId = interactionId.concat(Bytes.fromI32(1)) + } + const interaction = new RewardPoolPositionInteraction(interactionId) + interaction.rewardPool = rewardPool.id + interaction.investor = investor.id + interaction.investorPosition = position.id + interaction.createdWith = event.transaction.hash + interaction.blockNumber = event.block.number + interaction.timestamp = event.block.timestamp + if (isSharesTransfer) { + interaction.type = rewardPoolBalanceDelta.gt(ZERO_BI) ? "REWARD_POOL_DEPOSIT" : "REWARD_POOL_WITHDRAW" + } else if (isRewardClaim) { + interaction.type = "REWARD_CLAIM" + } + + interaction.rewardPoolBalance = position.rewardPoolBalance + interaction.totalBalance = position.totalBalance + interaction.rewardPoolBalanceDelta = rewardPoolBalanceDelta + + // set the underlying balances at the time of the transaction + interaction.underlyingBalance = ZERO_BI + interaction.underlyingBalanceDelta = ZERO_BI + if (!rewardPoolData.rewardPoolTotalSupply.equals(ZERO_BI)) { + interaction.underlyingBalance = interaction.underlyingBalance.plus( + rewardPoolData.underlyingTokenBalance.times(position.totalBalance).div(rewardPoolData.rewardPoolTotalSupply), + ) + + interaction.underlyingBalanceDelta = interaction.underlyingBalanceDelta.plus( + rewardPoolData.underlyingTokenBalance.times(rewardPoolBalanceDelta).div(rewardPoolData.rewardPoolTotalSupply), + ) + } + interaction.underlyingToNativePrice = rewardPoolData.underlyingToNativePrice + interaction.rewardToNativePrices = rewardPoolData.rewardToNativePrices + interaction.nativeToUSDPrice = rewardPoolData.nativeToUSDPrice + interaction.save() +} diff --git a/src/reward-pool/lifecycle.ts b/src/reward-pool/lifecycle.ts new file mode 100644 index 0000000..ad4add5 --- /dev/null +++ b/src/reward-pool/lifecycle.ts @@ -0,0 +1,91 @@ +import { Bytes } from "@graphprotocol/graph-ts" +import { log } from "@graphprotocol/graph-ts" +import { RewardPool as RewardPoolTemplate } from "../../generated/templates" +import { ProxyCreated as RewardPoolCreatedEvent } from "../../generated/RewardPoolFactory/RewardPoolFactory" +import { + Initialized as RewardPoolInitialized, + RewardPool as RewardPoolContract, + AddReward as RewardPoolAddRewardEvent, + RemoveReward as RewardPoolRemoveRewardEvent, +} from "../../generated/RewardPoolFactory/RewardPool" +import { getTransaction } from "../common/entity/transaction" +import { fetchAndSaveTokenData } from "../common/utils/token" +import { PRODUCT_LIFECYCLE_INITIALIZING, PRODUCT_LIFECYCLE_RUNNING } from "../common/entity/lifecycle" +import { getNullToken } from "../common/entity/token" +import { getRewardPool } from "./entity/reward-pool" +import { isClassicVault } from "../classic/entity/classic" +import { isCLMManager } from "../clm/entity/clm" + +export function handleRewardPoolCreated(event: RewardPoolCreatedEvent): void { + const rewardPoolAddress = event.params.proxy + + const rewardPool = getRewardPool(rewardPoolAddress) + rewardPool.lifecycle = PRODUCT_LIFECYCLE_INITIALIZING + rewardPool.save() + + // start indexing the new reward pool + RewardPoolTemplate.create(rewardPoolAddress) +} + +export function handleRewardPoolInitialized(event: RewardPoolInitialized): void { + const rewardPoolAddress = event.address + const rewardPoolContract = RewardPoolContract.bind(rewardPoolAddress) + const stakedTokenAddressRes = rewardPoolContract.try_stakedToken() + if (stakedTokenAddressRes.reverted) { + log.error("handleRewardPoolInitialized: staked token address is not available for reward pool {}", [ + rewardPoolAddress.toHexString(), + ]) + return + } + const stakedTokenAddress = stakedTokenAddressRes.value + + const tx = getTransaction(event.block, event.transaction) + tx.save() + + const rewardPool = getRewardPool(rewardPoolAddress) + const rewardPoolToken = fetchAndSaveTokenData(rewardPoolAddress) + const stakedToken = fetchAndSaveTokenData(stakedTokenAddress) + rewardPool.shareToken = rewardPoolToken.id + rewardPool.lifecycle = PRODUCT_LIFECYCLE_RUNNING + rewardPool.createdWith = tx.id + rewardPool.underlyingToken = stakedToken.id + + // detect if it's a vault or a clm + if (isClassicVault(stakedToken.id)) { + rewardPool.classic = stakedToken.id + } else if (isCLMManager(stakedToken.id)) { + rewardPool.clm = stakedToken.id + } + rewardPool.save() +} + +export function handleRewardPoolAddReward(event: RewardPoolAddRewardEvent): void { + const rewardPoolAddress = event.address + const rewardPool = getRewardPool(rewardPoolAddress) + + const rewardTokens = rewardPool.rewardTokens + const rewardTokensOrder = rewardPool.rewardTokensOrder + rewardTokens.push(event.params.reward) + rewardTokensOrder.push(event.params.reward) + rewardPool.rewardTokens = rewardTokens + rewardPool.rewardTokensOrder = rewardTokensOrder + rewardPool.save() +} + +export function handleRewardPoolRemoveReward(event: RewardPoolRemoveRewardEvent): void { + const rewardPoolAddress = event.address + const rewardPool = getRewardPool(rewardPoolAddress) + + const rewardTokenAddresses = rewardPool.rewardTokensOrder + const tokens = new Array() + for (let i = 0; i < rewardTokenAddresses.length; i++) { + if (rewardTokenAddresses[i].equals(event.params.reward)) { + tokens.push(getNullToken().id) + } else { + tokens.push(rewardTokenAddresses[i]) + } + } + rewardPool.rewardTokens = tokens + rewardPool.rewardTokensOrder = tokens + rewardPool.save() +} diff --git a/src/clm/mapping/reward-pool-factory.ts b/src/reward-pool/mapping/reward-pool-factory.ts similarity index 100% rename from src/clm/mapping/reward-pool-factory.ts rename to src/reward-pool/mapping/reward-pool-factory.ts diff --git a/src/clm/mapping/reward-pool.ts b/src/reward-pool/mapping/reward-pool.ts similarity index 100% rename from src/clm/mapping/reward-pool.ts rename to src/reward-pool/mapping/reward-pool.ts diff --git a/src/reward-pool/util/reward-pool-data.ts b/src/reward-pool/util/reward-pool-data.ts new file mode 100644 index 0000000..dc8fa79 --- /dev/null +++ b/src/reward-pool/util/reward-pool-data.ts @@ -0,0 +1,214 @@ +import { BigInt, log, ethereum, Address } from "@graphprotocol/graph-ts" +import { RewardPool } from "../../../generated/schema" +import { ONE_BI, ZERO_BI, changeValueEncoding } from "../../common/utils/decimal" +import { + BEEFY_ORACLE_ADDRESS, + BEEFY_SWAPPER_ADDRESS, + CHAINLINK_NATIVE_PRICE_FEED_ADDRESS, + CHAINLINK_NATIVE_PRICE_FEED_DECIMALS, + PRICE_STORE_DECIMALS_USD, + WNATIVE_TOKEN_ADDRESS, + BEEFY_SWAPPER_VALUE_SCALER, + PYTH_PRICE_FEED_ADDRESS, + PYTH_NATIVE_PRICE_ID, + PRICE_ORACLE_TYPE, +} from "../../config" +import { Multicall3Params, MulticallResult, multicall } from "../../common/utils/multicall" +import { getToken } from "../../common/entity/token" +import { getRewardPoolSnapshot } from "../entity/reward-pool" +import { REWARD_POOL_SNAPSHOT_PERIODS } from "./snapshot" + +export function fetchRewardPoolData(rewardPool: RewardPool): RewardPoolData { + const rewardPoolAddress = rewardPool.id + const rewardTokenAddresses = rewardPool.rewardTokensOrder + + const calls = [ + new Multicall3Params(rewardPoolAddress, "totalSupply()", "uint256"), + new Multicall3Params(rewardPoolAddress, "balances()", "uint256"), + ] + + // wnative price to usd + if (PRICE_ORACLE_TYPE == "chainlink") { + calls.push( + new Multicall3Params( + CHAINLINK_NATIVE_PRICE_FEED_ADDRESS, + "latestRoundData()", + "(uint80,int256,uint256,uint256,uint80)", + ), + ) + } else if (PRICE_ORACLE_TYPE === "pyth") { + calls.push( + new Multicall3Params(PYTH_PRICE_FEED_ADDRESS, "getPriceUnsafe(bytes32)", "(int64,uint64,int32,uint256)", [ + ethereum.Value.fromFixedBytes(PYTH_NATIVE_PRICE_ID), + ]), + ) + } else { + log.error("Unsupported price oracle type {}", [PRICE_ORACLE_TYPE]) + throw new Error("Unsupported price oracle type") + } + + // warm up beefy swapper oracle + const tokensToRefresh = new Array
() + tokensToRefresh.push(WNATIVE_TOKEN_ADDRESS) + tokensToRefresh.push(Address.fromBytes(rewardPool.underlyingToken)) + for (let i = 0; i < rewardTokenAddresses.length; i++) { + tokensToRefresh.push(Address.fromBytes(rewardTokenAddresses[i])) + } + for (let i = 0; i < tokensToRefresh.length; i++) { + const tokenAddress = tokensToRefresh[i] + calls.push( + new Multicall3Params(BEEFY_ORACLE_ADDRESS, "getFreshPrice(address)", "(uint256,bool)", [ + ethereum.Value.fromAddress(tokenAddress), + ]), + ) + } + + const underlyingToken = getToken(Address.fromBytes(rewardPool.underlyingToken)) + const amountId = changeValueEncoding(ONE_BI, ZERO_BI, underlyingToken.decimals).div(BEEFY_SWAPPER_VALUE_SCALER) + calls.push( + new Multicall3Params(BEEFY_SWAPPER_ADDRESS, "getAmountOut(address,address,uint256)", "uint256", [ + ethereum.Value.fromAddress(Address.fromBytes(underlyingToken.id)), + ethereum.Value.fromAddress(WNATIVE_TOKEN_ADDRESS), + ethereum.Value.fromUnsignedBigInt(amountId), + ]), + ) + + for (let i = 0; i < rewardTokenAddresses.length; i++) { + const rewardTokenAddress = Address.fromBytes(rewardTokenAddresses[i]) + const rewardToken = getToken(rewardTokenAddress) + const amountIn = changeValueEncoding(ONE_BI, ZERO_BI, rewardToken.decimals).div(BEEFY_SWAPPER_VALUE_SCALER) + calls.push( + new Multicall3Params(BEEFY_SWAPPER_ADDRESS, "getAmountOut(address,address,uint256)", "uint256", [ + ethereum.Value.fromAddress(rewardTokenAddress), + ethereum.Value.fromAddress(WNATIVE_TOKEN_ADDRESS), + ethereum.Value.fromUnsignedBigInt(amountIn), + ]), + ) + } + + // map multicall indices to variables + const results = multicall(calls) + + let idx = 0 + const totalSupplyRes = results[idx++] + const balanceRes = results[idx++] + const priceFeedRes = results[idx++] + for (let i = 0; i < tokensToRefresh.length; i++) { + idx++ + } + const underlyingToNativeRes = results[idx++] + const rewardTokenOutputAmountsRes = new Array() + for (let i = 0; i < rewardTokenAddresses.length; i++) { + rewardTokenOutputAmountsRes.push(results[idx++]) + } + + // extract the data + let rewardPoolTotalSupply = ZERO_BI + if (!totalSupplyRes.reverted) { + rewardPoolTotalSupply = totalSupplyRes.value.toBigInt() + } else { + log.error("Failed to fetch totalSupply for Reward Pool {}", [rewardPool.id.toHexString()]) + } + + let underlyingTokenBalance = ZERO_BI + if (!balanceRes.reverted) { + underlyingTokenBalance = balanceRes.value.toBigInt() + } else { + log.error("Failed to fetch balance for Reward Pool {}", [rewardPool.id.toHexString()]) + } + + let underlyingToNativePrice = ZERO_BI + if (!underlyingToNativeRes.reverted) { + underlyingToNativePrice = underlyingToNativeRes.value.toBigInt().times(BEEFY_SWAPPER_VALUE_SCALER) + } else { + log.error("Failed to fetch underlyingToNativePrice for Reward Pool {}", [rewardPool.id.toHexString()]) + } + + // only some strategies have this + let rewardToNativePrices = new Array() + for (let i = 0; i < rewardTokenOutputAmountsRes.length; i++) { + const amountOutRes = rewardTokenOutputAmountsRes[i] + if (!amountOutRes.reverted) { + const amountOut = amountOutRes.value.toBigInt().times(BEEFY_SWAPPER_VALUE_SCALER) + rewardToNativePrices.push(amountOut) + } else { + rewardToNativePrices.push(ZERO_BI) + log.error("Failed to fetch rewardToNativePrices for Reward Pool {}", [rewardPool.id.toHexString()]) + } + } + + // and have a native price in USD + let nativeToUSDPrice = ZERO_BI + if (!priceFeedRes.reverted) { + if (PRICE_ORACLE_TYPE === "chainlink") { + const chainLinkAnswer = priceFeedRes.value.toTuple() + nativeToUSDPrice = changeValueEncoding( + chainLinkAnswer[1].toBigInt(), + CHAINLINK_NATIVE_PRICE_FEED_DECIMALS, + PRICE_STORE_DECIMALS_USD, + ) + } else if (PRICE_ORACLE_TYPE === "pyth") { + const pythAnswer = priceFeedRes.value.toTuple() + const value = pythAnswer[0].toBigInt() + const exponent = pythAnswer[2].toBigInt() + const decimals = exponent.neg() + nativeToUSDPrice = changeValueEncoding(value, decimals, PRICE_STORE_DECIMALS_USD) + } else { + log.error("Unsupported price oracle type {}", [PRICE_ORACLE_TYPE]) + throw new Error("Unsupported price oracle type") + } + } else { + log.error("Failed to fetch nativeToUSDPrice for Reward Pool {}", [rewardPool.id.toHexString()]) + } + + return new RewardPoolData( + rewardPoolTotalSupply, + underlyingTokenBalance, + underlyingToNativePrice, + rewardToNativePrices, + nativeToUSDPrice, + ) +} + +class RewardPoolData { + constructor( + public rewardPoolTotalSupply: BigInt, + public underlyingTokenBalance: BigInt, + public underlyingToNativePrice: BigInt, + public rewardToNativePrices: Array, + public nativeToUSDPrice: BigInt, + ) {} +} + +export function updateRewardPoolDataAndSnapshots( + rewardPool: RewardPool, + rewardPoolData: RewardPoolData, + nowTimestamp: BigInt, +): RewardPool { + // update reward pool data + rewardPool.rewardPoolSharesTotalSupply = rewardPoolData.rewardPoolTotalSupply + rewardPool.underlyingAmount = rewardPoolData.underlyingTokenBalance + rewardPool.underlyingToNativePrice = rewardPoolData.underlyingToNativePrice + rewardPool.rewardToNativePrices = rewardPoolData.rewardToNativePrices + rewardPool.nativeToUSDPrice = rewardPoolData.nativeToUSDPrice + rewardPool.save() + + // don't save a snapshot if we don't have a deposit yet + // or if the vault becomes empty + if (rewardPool.rewardPoolSharesTotalSupply.equals(ZERO_BI)) { + return rewardPool + } + + for (let i = 0; i < REWARD_POOL_SNAPSHOT_PERIODS.length; i++) { + const period = REWARD_POOL_SNAPSHOT_PERIODS[i] + const snapshot = getRewardPoolSnapshot(rewardPool, nowTimestamp, period) + snapshot.rewardPoolSharesTotalSupply = rewardPoolData.rewardPoolTotalSupply + snapshot.underlyingAmount = rewardPoolData.underlyingTokenBalance + snapshot.underlyingToNativePrice = rewardPoolData.underlyingToNativePrice + snapshot.rewardToNativePrices = rewardPoolData.rewardToNativePrices + snapshot.nativeToUSDPrice = rewardPoolData.nativeToUSDPrice + snapshot.save() + } + + return rewardPool +} diff --git a/src/reward-pool/util/snapshot.ts b/src/reward-pool/util/snapshot.ts new file mode 100644 index 0000000..2ae8f7b --- /dev/null +++ b/src/reward-pool/util/snapshot.ts @@ -0,0 +1,3 @@ +import { DAY, HOUR, WEEK } from "../../common/utils/time" + +export const REWARD_POOL_SNAPSHOT_PERIODS = [HOUR, DAY, WEEK] diff --git a/subgraph.template.yaml b/subgraph.template.yaml index c4fbef1..3c6f16c 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -26,7 +26,7 @@ dataSources: - ClmSnapshot - ClmManager - ClmManagerCollectionEvent - - ClmRewardPool + - RewardPool - ClmStrategy - ClockTick - Investor @@ -43,9 +43,9 @@ dataSources: - name: ClmStrategy file: ./abis/beefy/clm/ClmStrategy.json - name: RewardPoolFactory - file: ./abis/beefy/clm/RewardPoolFactory.json + file: ./abis/beefy/reward-pool/RewardPoolFactory.json - name: RewardPool - file: ./abis/beefy/clm/RewardPool.json + file: ./abis/beefy/reward-pool/RewardPool.json - name: ClassicVaultFactory file: ./abis/beefy/classic/ClassicVaultFactory.json @@ -95,7 +95,7 @@ dataSources: kind: ethereum/events apiVersion: 0.0.7 # 0xgraph's version language: wasm/assemblyscript - file: ./src/clm/mapping/reward-pool-factory.ts + file: ./src/reward-pool/mapping/reward-pool-factory.ts entities: *clmEntities abis: *abis eventHandlers: @@ -220,7 +220,7 @@ templates: handler: handleClmStrategyClaimedRewards - - name: ClmRewardPool + - name: RewardPool kind: ethereum/contract network: {{network}} source: @@ -229,7 +229,7 @@ templates: kind: ethereum/events apiVersion: 0.0.7 # 0xgraph's version language: wasm/assemblyscript - file: ./src/clm/mapping/reward-pool.ts + file: ./src/reward-pool/mapping/reward-pool.ts entities: *clmEntities abis: *abis eventHandlers: