From feb30c5819e8d83fa9f2a2ea4824604281375d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pr=C3=A9vost?= <998369+prevostc@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:24:22 +0200 Subject: [PATCH] Add reward pool data to classic vaults --- package.json | 3 +- schema.graphql | 57 ++++++ src/classic/entity/classic.ts | 38 +++- src/classic/entity/position.ts | 1 + src/classic/interaction.ts | 141 +++++++++++-- src/classic/mapping/reward-pool.ts | 5 + src/clm/entity/clm.ts | 4 + src/clm/lifecycle.ts | 103 +--------- src/clm/mapping/reward-pool.ts | 6 +- src/reward-pool/lifecycle.ts | 186 ++++++++++++++++++ .../mapping/reward-pool-factory.ts | 0 src/reward-pool/mapping/reward-pool.ts | 1 + subgraph.template.yaml | 43 +++- 13 files changed, 467 insertions(+), 121 deletions(-) create mode 100644 src/classic/mapping/reward-pool.ts create mode 100644 src/reward-pool/lifecycle.ts rename src/{clm => reward-pool}/mapping/reward-pool-factory.ts (100%) create mode 100644 src/reward-pool/mapping/reward-pool.ts diff --git a/package.json b/package.json index 97143db..396271c 100644 --- a/package.json +++ b/package.json @@ -44,5 +44,6 @@ }, "lint-staged": { "*.*": "prettier --write" - } + }, + "packageManager": "yarn@1.22.22" } diff --git a/schema.graphql b/schema.graphql index 7b9f303..95c4aec 100644 --- a/schema.graphql +++ b/schema.graphql @@ -119,12 +119,20 @@ type Classic @entity { strategy: ClassicStrategy! "The vault boosts" boosts: [ClassicBoost!]! @derivedFrom(field: "classic") + """ + The reward pools of this vault. + """ + rewardPools: [ClassicRewardPool!]! @derivedFrom(field: "classic") "The current lifecycle status of the vault" lifecycle: ProductLifecycle! "The vault's share token" vaultSharesToken: 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 vault's underlying LP token" underlyingToken: Token! @@ -133,16 +141,24 @@ type Classic @entity { boostRewardTokens: [Token!]! "The boost token addresses of the vault. This is the source of truth for other tables reward ordering." boostRewardTokensOrder: [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 vault shares token in circulation. Express with `sharesToken.decimals` decimals." vaultSharesTotalSupply: BigInt! + "Total supply of the reward pool token. Express with `clm.rewardTokensOrder[idx].decimals` decimals." + rewardPoolsTotalSupply: [BigInt!]! "Latest LP token price in native we have seen. Expressed with 18 decimals." underlyingToNativePrice: BigInt! "Latest boost token prices in native we have seen. Ordered by classic.boostRewardTokensOrder. Expressed with 18 decimals." boostRewardToNativePrices: [BigInt!]! + "Latest reward token prices in native we have seen. Ordered by classic.rewardTokensOrder. Expressed with 18 decimals." + rewardToNativePrices: [BigInt!]! "Latest native token price we have seen. Expressed with 18 decimals." nativeToUSDPrice: BigInt! @@ -190,11 +206,15 @@ type ClassicSnapshot @entity { "The total supply of the vault shares token in circulation. Express with `sharesToken.decimals` decimals." vaultSharesTotalSupply: BigInt! + "Total supply of the reward pool token. Express with `clm.rewardTokensOrder[idx].decimals` decimals." + rewardPoolsTotalSupply: [BigInt!]! "Latest LP token price in native of this snapshot. Expressed with 18 decimals." underlyingToNativePrice: BigInt! "Latest boost token prices in native of this snapshot. Ordered by classic.boostRewardTokensOrder. Expressed with 18 decimals." boostRewardToNativePrices: [BigInt!]! + "Latest reward token prices in native of this snapshot. Ordered by classic.rewardTokensOrder. Expressed with 18 decimals." + rewardToNativePrices: [BigInt!]! "Latest native token price of this snapshot. Expressed with 18 decimals." nativeToUSDPrice: BigInt! @@ -253,6 +273,23 @@ type ClassicBoost @entity { isInitialized: Boolean! } +""" +Some Classic 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 Classic on new event +""" +type ClassicRewardPool @entity { + "The strategy address" + id: Bytes! + "The classic product the reward pool is for" + classic: Classic! + "The vault the reward pool is linked to" + vault: ClassicVault! + "The transaction that created the reward pool" + createdWith: Transaction! + "Technical field to remember if the strategy was already initialized" + isInitialized: Boolean! +} + """ Classic products are harvested by the strategy. This event is emitted when the strategy harvests the vault. """ @@ -281,11 +318,15 @@ type ClassicHarvestEvent @entity(immutable: true) { "Total amount of liquidity in the vault at time of harvest" vaultSharesTotalSupply: BigInt! + "Total amount of reward pool shares at time of harvest. Ordered by classic.rewardPoolTokensOrder." + rewardPoolsTotalSupply: [BigInt!]! "LP token price in native at the time of harvest. Expressed with 18 decimals." underlyingToNativePrice: BigInt! "Boost token prices in native at the time of harvest. Ordered by classic.boostRewardTokensOrder. Expressed with 18 decimals." boostRewardToNativePrices: [BigInt!]! + "Reward token prices in native at the time of harvest. Ordered by classic.rewardTokensOrder. Expressed with 18 decimals." + rewardToNativePrices: [BigInt!]! "Native token price at the time of harvest. Expressed with 18 decimals." nativeToUSDPrice: BigInt! } @@ -308,6 +349,8 @@ type ClassicPosition @entity { vaultBalance: BigInt! "The amount of vault shares the investor holds in a boost" boostBalance: BigInt! + "Amount of reward pool shares the investor holds. Ordered by clm.rewardPoolTokensOrder." + rewardPoolBalances: [BigInt!]! "Total amount of vault shares the investor holds. Should always equal vaultBalance + boostBalance. This is mostly used for filtering." totalBalance: BigInt! @@ -326,6 +369,12 @@ enum ClassicPositionInteractionType { BOOST_UNSTAKE "The investor claimed their rewards from the vault" BOOST_REWARD_CLAIM + "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 ClassicPositionInteraction @entity(immutable: true) { @@ -354,6 +403,8 @@ type ClassicPositionInteraction @entity(immutable: true) { vaultBalance: BigInt! "The amount of vault shares the investor holds in a boost at the time of the interaction" boostBalance: 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 vault shares the investor holds. Should always equal vaultBalance + boostBalance. This is mostly used for filtering." totalBalance: BigInt! @@ -363,11 +414,17 @@ type ClassicPositionInteraction @entity(immutable: true) { boostBalanceDelta: BigInt! "Amount of boost tokens change in the interaction. Ordered by classic.boostRewardTokensOrder." boostRewardBalancesDelta: [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!]! "LP token price in native at the time of the interaction. Expressed with 18 decimals." underlyingToNativePrice: BigInt! "Boost token prices in native at the time of the interaction. Ordered by classic.boostRewardTokensOrder. Expressed with 18 decimals." boostRewardToNativePrices: [BigInt!]! + "Reward token prices in native at the time of the interaction. 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! } diff --git a/src/classic/entity/classic.ts b/src/classic/entity/classic.ts index e3332af..945e028 100644 --- a/src/classic/entity/classic.ts +++ b/src/classic/entity/classic.ts @@ -1,5 +1,12 @@ import { BigInt, Bytes } from "@graphprotocol/graph-ts" -import { Classic, ClassicVault, ClassicStrategy, ClassicBoost, ClassicSnapshot } from "../../../generated/schema" +import { + Classic, + ClassicVault, + ClassicStrategy, + ClassicBoost, + ClassicSnapshot, + ClassicRewardPool, +} from "../../../generated/schema" import { ADDRESS_ZERO } from "../../common/utils/address" import { ZERO_BI } from "../../common/utils/decimal" import { getIntervalFromTimestamp } from "../../common/utils/time" @@ -11,6 +18,10 @@ export function isClassicInitialized(classic: Classic): boolean { return classic.lifecycle != PRODUCT_LIFECYCLE_INITIALIZING } +export function isClassicVaultAddress(vaultAddress: Bytes): boolean { + return ClassicVault.load(vaultAddress) != null +} + export function getClassic(vaultAddress: Bytes): Classic { let classic = Classic.load(vaultAddress) if (!classic) { @@ -26,11 +37,17 @@ export function getClassic(vaultAddress: Bytes): Classic { classic.underlyingToken = ADDRESS_ZERO classic.boostRewardTokens = [] classic.boostRewardTokensOrder = [] + classic.rewardPoolTokens = [] + classic.rewardPoolTokensOrder = [] + classic.rewardTokens = [] + classic.rewardTokensOrder = [] classic.vaultSharesTotalSupply = ZERO_BI + classic.rewardPoolsTotalSupply = [] classic.underlyingToNativePrice = ZERO_BI classic.boostRewardToNativePrices = [] + classic.rewardToNativePrices = [] classic.nativeToUSDPrice = ZERO_BI classic.underlyingAmount = ZERO_BI @@ -74,6 +91,22 @@ export function getClassicBoost(boostAddress: Bytes): ClassicBoost { return boost } +export function isClassicRewardPool(rewardPoolAddress: Bytes): boolean { + return ClassicRewardPool.load(rewardPoolAddress) != null +} + +export function getClassicRewardPool(rewardPoolAddress: Bytes): ClassicRewardPool { + let rewardPool = ClassicRewardPool.load(rewardPoolAddress) + if (!rewardPool) { + rewardPool = new ClassicRewardPool(rewardPoolAddress) + rewardPool.classic = ADDRESS_ZERO + rewardPool.vault = ADDRESS_ZERO + rewardPool.createdWith = ADDRESS_ZERO + rewardPool.isInitialized = false + } + return rewardPool +} + export function getClassicSnapshot(classic: Classic, timestamp: BigInt, period: BigInt): ClassicSnapshot { const interval = getIntervalFromTimestamp(timestamp, period) const snapshotId = classic.id.concat(getSnapshotIdSuffix(period, interval)) @@ -87,9 +120,11 @@ export function getClassicSnapshot(classic: Classic, timestamp: BigInt, period: snapshot.roundedTimestamp = interval snapshot.vaultSharesTotalSupply = ZERO_BI + snapshot.rewardPoolsTotalSupply = [] snapshot.underlyingToNativePrice = ZERO_BI snapshot.boostRewardToNativePrices = [] + snapshot.rewardToNativePrices = [] snapshot.nativeToUSDPrice = ZERO_BI snapshot.underlyingAmount = ZERO_BI @@ -101,6 +136,7 @@ export function getClassicSnapshot(classic: Classic, timestamp: BigInt, period: snapshot.vaultSharesTotalSupply = previousSnapshot.vaultSharesTotalSupply snapshot.underlyingToNativePrice = previousSnapshot.underlyingToNativePrice snapshot.boostRewardToNativePrices = previousSnapshot.boostRewardToNativePrices + snapshot.rewardToNativePrices = previousSnapshot.rewardToNativePrices snapshot.nativeToUSDPrice = previousSnapshot.nativeToUSDPrice snapshot.underlyingAmount = previousSnapshot.underlyingAmount } diff --git a/src/classic/entity/position.ts b/src/classic/entity/position.ts index 59552ba..1655852 100644 --- a/src/classic/entity/position.ts +++ b/src/classic/entity/position.ts @@ -19,6 +19,7 @@ export function getClassicPosition(classic: Classic, investor: Investor): Classi position.createdWith = ADDRESS_ZERO position.vaultBalance = ZERO_BI position.boostBalance = ZERO_BI + position.rewardPoolBalances = [] position.totalBalance = ZERO_BI } return position diff --git a/src/classic/interaction.ts b/src/classic/interaction.ts index f3612f4..41a89f4 100644 --- a/src/classic/interaction.ts +++ b/src/classic/interaction.ts @@ -5,13 +5,17 @@ import { Withdrawn as ClassicBoostWithdrawn, RewardPaid as ClassicBoostRewardPaid, } from "../../generated/templates/ClassicBoost/ClassicBoost" +import { + Transfer as RewardPoolTransferEvent, + RewardPaid as RewardPoolRewardPaidEvent, +} from "../../generated/templates/ClmRewardPool/RewardPool" import { getTransaction } from "../common/entity/transaction" import { getInvestor } from "../common/entity/investor" import { Classic, ClassicPositionInteraction } 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 { getClassic, getClassicBoost, isClassicInitialized } from "./entity/classic" +import { getClassic, getClassicBoost, getClassicRewardPool, isClassicInitialized } from "./entity/classic" import { getClassicPosition } from "./entity/position" import { fetchClassicData, updateClassicDataAndSnapshots } from "./utils/classic-data" @@ -30,11 +34,11 @@ export function handleClassicVaultTransfer(event: ClassicVaultTransfer): void { // 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(classic, event, event.params.from, event.params.value.neg(), ZERO_BI, []) + updateUserPosition(classic, event, event.params.from, event.params.value.neg(), ZERO_BI, [], [], []) } if (!event.params.to.equals(SHARE_TOKEN_MINT_ADDRESS) && !event.params.to.equals(BURN_ADDRESS)) { - updateUserPosition(classic, event, event.params.to, event.params.value, ZERO_BI, []) + updateUserPosition(classic, event, event.params.to, event.params.value, ZERO_BI, [], [], []) } } @@ -46,7 +50,7 @@ export function handleClassicBoostStaked(event: ClassicBoostStaked): void { const investorAddress = event.params.user const amount = event.params.amount - updateUserPosition(classic, event, investorAddress, ZERO_BI, amount, []) + updateUserPosition(classic, event, investorAddress, ZERO_BI, amount, [], [], []) } export function handleClassicBoostWithdrawn(event: ClassicBoostWithdrawn): void { @@ -57,7 +61,7 @@ export function handleClassicBoostWithdrawn(event: ClassicBoostWithdrawn): void const investorAddress = event.params.user const amount = event.params.amount - updateUserPosition(classic, event, investorAddress, ZERO_BI, amount.neg(), []) + updateUserPosition(classic, event, investorAddress, ZERO_BI, amount.neg(), [], [], []) } export function handleClassicBoostRewardPaid(event: ClassicBoostRewardPaid): void { @@ -79,7 +83,85 @@ export function handleClassicBoostRewardPaid(event: ClassicBoostRewardPaid): voi } } - updateUserPosition(classic, event, investorAddress, ZERO_BI, ZERO_BI, boostRewardBalancesDelta) + updateUserPosition(classic, event, investorAddress, ZERO_BI, ZERO_BI, boostRewardBalancesDelta, [], []) +} + +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 = getClassicRewardPool(event.address) + const classic = getClassic(rewardPool.classic) + const vaultAddress = classic.vault + + const rewardPoolAddresses = classic.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) + } + } + + // 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(vaultAddress) && + !isRewardPoolTo + ) { + updateUserPosition(classic, event, event.params.to, ZERO_BI, ZERO_BI, [], rewardPoolBalancesDelta, []) + } + + if ( + !event.params.from.equals(SHARE_TOKEN_MINT_ADDRESS) && + !event.params.from.equals(BURN_ADDRESS) && + !event.params.from.equals(vaultAddress) && + !isRewardPoolFrom + ) { + const negRewardPoolBalancesDelta = new Array() + for (let i = 0; i < rewardPoolBalancesDelta.length; i++) { + negRewardPoolBalancesDelta.push(rewardPoolBalancesDelta[i].neg()) + } + updateUserPosition(classic, event, event.params.from, ZERO_BI, ZERO_BI, [], negRewardPoolBalancesDelta, []) + } +} + +export function handleRewardPoolRewardPaid(event: RewardPoolRewardPaidEvent): void { + const rewardPool = getClassicRewardPool(event.address) + const classic = getClassic(rewardPool.classic) + + const rewardTokensAddresses = classic.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(classic, event, event.params.user, ZERO_BI, ZERO_BI, [], [], rewardBalancesDelta) } function updateUserPosition( @@ -89,6 +171,8 @@ function updateUserPosition( vaultBalanceDelta: BigInt, boostBalanceDelta: BigInt, boostRewardBalancesDelta: Array, + rewardPoolBalancesDelta: Array, + rewardBalancesDelta: Array, ): void { if (!isClassicInitialized(classic)) { return @@ -111,19 +195,42 @@ function updateUserPosition( /////// // investor position - if (position.vaultBalance.equals(ZERO_BI) && position.boostBalance.equals(ZERO_BI)) { + let hasPreviousRewardPoolBalance = false + for (let i = 0; i < rewardPoolBalancesDelta.length; i++) { + if (rewardPoolBalancesDelta[i].notEqual(ZERO_BI)) { + hasPreviousRewardPoolBalance = true + break + } + } + if (position.vaultBalance.equals(ZERO_BI) && position.boostBalance.equals(ZERO_BI) && !hasPreviousRewardPoolBalance) { position.createdWith = event.transaction.hash } position.vaultBalance = position.vaultBalance.plus(vaultBalanceDelta) position.boostBalance = position.boostBalance.plus(boostBalanceDelta) - position.totalBalance = position.vaultBalance.plus(position.boostBalance) + 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.vaultBalance.plus(position.boostBalance) + for (let i = 0; i < positionRewardPoolBalances.length; i++) { + totalBalance = totalBalance.plus(positionRewardPoolBalances[i]) + } + position.totalBalance = totalBalance position.save() /////// // interaction const isSharesTransfer = !vaultBalanceDelta.equals(ZERO_BI) const isBoostTransfer = !boostBalanceDelta.equals(ZERO_BI) - const isRewardTransfer = boostRewardBalancesDelta.some((delta) => !delta.equals(ZERO_BI)) + const isBoostRewardTransfer = boostRewardBalancesDelta.some((delta) => !delta.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 need to create two interactions let interactionId = investor.id.concat(getEventIdentifier(event)) @@ -131,8 +238,12 @@ function updateUserPosition( interactionId = interactionId.concat(Bytes.fromI32(0)) } else if (isBoostTransfer) { interactionId = interactionId.concat(Bytes.fromI32(1)) - } else if (isRewardTransfer) { + } else if (isBoostRewardTransfer) { interactionId = interactionId.concat(Bytes.fromI32(2)) + } else if (isRewardPoolTransfer) { + interactionId = interactionId.concat(Bytes.fromI32(3)) + } else if (isRewardClaim) { + interactionId = interactionId.concat(Bytes.fromI32(4)) } const interaction = new ClassicPositionInteraction(interactionId) interaction.classic = classic.id @@ -145,16 +256,24 @@ function updateUserPosition( interaction.type = vaultBalanceDelta.gt(ZERO_BI) ? "VAULT_DEPOSIT" : "VAULT_WITHDRAW" } else if (isBoostTransfer) { interaction.type = boostBalanceDelta.gt(ZERO_BI) ? "BOOST_STAKE" : "BOOST_UNSTAKE" - } else if (isRewardTransfer) { + } else if (isBoostRewardTransfer) { interaction.type = "BOOST_REWARD_CLAIM" + } 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.vaultBalance = position.vaultBalance interaction.boostBalance = position.boostBalance + interaction.rewardPoolBalances = position.rewardPoolBalances interaction.totalBalance = position.totalBalance interaction.vaultBalanceDelta = vaultBalanceDelta interaction.boostBalanceDelta = boostBalanceDelta interaction.boostRewardBalancesDelta = boostRewardBalancesDelta + interaction.rewardPoolBalancesDelta = rewardPoolBalancesDelta + interaction.rewardBalancesDelta = rewardBalancesDelta interaction.underlyingToNativePrice = classicData.underlyingToNativePrice interaction.boostRewardToNativePrices = classicData.boostRewardToNativePrices diff --git a/src/classic/mapping/reward-pool.ts b/src/classic/mapping/reward-pool.ts new file mode 100644 index 0000000..f90508c --- /dev/null +++ b/src/classic/mapping/reward-pool.ts @@ -0,0 +1,5 @@ +export { handleRewardPoolInitialized } from "../../reward-pool/lifecycle" +export { handleRewardPoolTransfer } from "../interaction" +export { handleRewardPoolRewardPaid } from "../interaction" +export { handleRewardPoolAddReward } from "../../reward-pool/lifecycle" +export { handleRewardPoolRemoveReward } from "../../reward-pool/lifecycle" diff --git a/src/clm/entity/clm.ts b/src/clm/entity/clm.ts index aa0c390..35fbf8a 100644 --- a/src/clm/entity/clm.ts +++ b/src/clm/entity/clm.ts @@ -12,6 +12,10 @@ export function isClmInitialized(clm: CLM): boolean { return clm.lifecycle != PRODUCT_LIFECYCLE_INITIALIZING } +export function isClmManagerAddress(managerAddress: Bytes): boolean { + return ClmManager.load(managerAddress) != null +} + export function getCLM(managerAddress: Bytes): CLM { let clm = CLM.load(managerAddress) if (!clm) { diff --git a/src/clm/lifecycle.ts b/src/clm/lifecycle.ts index 4850be9..605c481 100644 --- a/src/clm/lifecycle.ts +++ b/src/clm/lifecycle.ts @@ -4,13 +4,9 @@ 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/mapping/reward-pool.ts b/src/clm/mapping/reward-pool.ts index 0be4016..f90508c 100644 --- a/src/clm/mapping/reward-pool.ts +++ b/src/clm/mapping/reward-pool.ts @@ -1,5 +1,5 @@ -export { handleRewardPoolInitialized } from "../lifecycle" +export { handleRewardPoolInitialized } from "../../reward-pool/lifecycle" export { handleRewardPoolTransfer } from "../interaction" export { handleRewardPoolRewardPaid } from "../interaction" -export { handleRewardPoolAddReward } from "../lifecycle" -export { handleRewardPoolRemoveReward } from "../lifecycle" +export { handleRewardPoolAddReward } from "../../reward-pool/lifecycle" +export { handleRewardPoolRemoveReward } from "../../reward-pool/lifecycle" diff --git a/src/reward-pool/lifecycle.ts b/src/reward-pool/lifecycle.ts new file mode 100644 index 0000000..47ba82e --- /dev/null +++ b/src/reward-pool/lifecycle.ts @@ -0,0 +1,186 @@ +import { Bytes, log } from "@graphprotocol/graph-ts" +import { + RewardPool as RewardPoolTemplate, + ClmRewardPool as ClmRewardPoolTemplate, + ClassicRewardPool as ClassicRewardPoolTemplate, +} from "../../generated/templates" +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 { ZERO_BI } from "../common/utils/decimal" +import { getCLM, getClmRewardPool, isClmManagerAddress, isClmRewardPool } from "../clm/entity/clm" +import { getClassic, getClassicRewardPool, isClassicRewardPool, isClassicVaultAddress } from "../classic/entity/classic" +import { getNullToken } from "../common/entity/token" + +export function handleRewardPoolCreated(event: RewardPoolCreatedEvent): void { + const rewardPoolAddress = event.params.proxy + + // create both as we don't know which one it will be yet + const clmRewardPool = getClmRewardPool(rewardPoolAddress) + clmRewardPool.isInitialized = false + clmRewardPool.save() + + const classicRewardPool = getClassicRewardPool(rewardPoolAddress) + classicRewardPool.isInitialized = false + classicRewardPool.save() + + RewardPoolTemplate.create(rewardPoolAddress) +} + +export function handleRewardPoolInitialized(event: RewardPoolInitialized): void { + const rewardPoolAddress = event.address + const rewardPoolContract = ClmRewardPoolContract.bind(rewardPoolAddress) + const stakedTokenAddressRes = rewardPoolContract.try_stakedToken() + if (stakedTokenAddressRes.reverted) { + log.error("handleRewardPoolInitialized: Manager address is not available for reward pool {}", [ + rewardPoolAddress.toHexString(), + ]) + return + } + const stakedTokenAddress = stakedTokenAddressRes.value + + const tx = getTransaction(event.block, event.transaction) + tx.save() + + const rewardPoolToken = fetchAndSaveTokenData(rewardPoolAddress) + + if (isClmManagerAddress(stakedTokenAddress)) { + const rewardPool = getClmRewardPool(rewardPoolAddress) + rewardPool.isInitialized = true + rewardPool.clm = stakedTokenAddress + rewardPool.manager = stakedTokenAddress + rewardPool.createdWith = tx.id + rewardPool.save() + + const clm = getCLM(stakedTokenAddress) + 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() + + ClmRewardPoolTemplate.create(rewardPoolAddress) + + log.info("handleRewardPoolInitialized: Reward pool {} initialized for CLM {} on block {}", [ + rewardPool.id.toHexString(), + clm.id.toHexString(), + event.block.number.toString(), + ]) + } else if (isClassicVaultAddress(stakedTokenAddress)) { + const rewardPool = getClassicRewardPool(rewardPoolAddress) + rewardPool.isInitialized = true + rewardPool.classic = stakedTokenAddress + rewardPool.vault = stakedTokenAddress + rewardPool.createdWith = tx.id + rewardPool.save() + + const classic = getClassic(stakedTokenAddress) + const rewardPoolTokens = classic.rewardPoolTokens + const rewardPoolTokensOrder = classic.rewardPoolTokensOrder + rewardPoolTokens.push(rewardPoolToken.id) + rewardPoolTokensOrder.push(rewardPoolToken.id) + classic.rewardPoolTokens = rewardPoolTokens + classic.rewardPoolTokensOrder = rewardPoolTokensOrder + const rewardPoolsTotalSupply = classic.rewardPoolsTotalSupply + rewardPoolsTotalSupply.push(ZERO_BI) + classic.rewardPoolsTotalSupply = rewardPoolsTotalSupply + classic.save() + + ClassicRewardPoolTemplate.create(rewardPoolAddress) + + log.info("handleRewardPoolInitialized: Reward pool {} initialized for Classic {} on block {}", [ + rewardPool.id.toHexString(), + classic.id.toHexString(), + event.block.number.toString(), + ]) + } else { + log.error("handleRewardPoolInitialized: Staked token address {} is not a CLM or Classic vault", [ + stakedTokenAddress.toHexString(), + ]) + + return + } +} + +export function handleRewardPoolAddReward(event: RewardPoolAddRewardEvent): void { + const rewardPoolAddress = event.address + + if (isClmRewardPool(rewardPoolAddress)) { + 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() + } else if (isClassicRewardPool(rewardPoolAddress)) { + const rewardPool = getClassicRewardPool(rewardPoolAddress) + const classic = getClassic(rewardPool.classic) + + const rewardTokens = classic.rewardTokens + const rewardTokensOrder = classic.rewardTokensOrder + rewardTokens.push(event.params.reward) + rewardTokensOrder.push(event.params.reward) + classic.rewardTokens = rewardTokens + classic.rewardTokensOrder = rewardTokensOrder + classic.save() + } else { + log.error("handleRewardPoolAddReward: Reward pool address {} is not a CLM or Classic reward pool", [ + rewardPoolAddress.toHexString(), + ]) + } +} + +export function handleRewardPoolRemoveReward(event: RewardPoolRemoveRewardEvent): void { + const rewardPoolAddress = event.address + + if (isClmRewardPool(rewardPoolAddress)) { + 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() + } else if (isClassicRewardPool(rewardPoolAddress)) { + const rewardPool = getClassicRewardPool(rewardPoolAddress) + const classic = getClassic(rewardPool.classic) + const rewardTokenAddresses = classic.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]) + } + } + classic.rewardTokens = tokens + classic.rewardTokensOrder = tokens + classic.save() + } else { + log.error("handleRewardPoolRemoveReward: Reward pool address {} is not a CLM or Classic reward pool", [ + rewardPoolAddress.toHexString(), + ]) + } +} 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/reward-pool/mapping/reward-pool.ts b/src/reward-pool/mapping/reward-pool.ts new file mode 100644 index 0000000..e4d6385 --- /dev/null +++ b/src/reward-pool/mapping/reward-pool.ts @@ -0,0 +1 @@ +export { handleRewardPoolInitialized } from "../lifecycle" diff --git a/subgraph.template.yaml b/subgraph.template.yaml index c4fbef1..6186e49 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -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: @@ -219,8 +219,7 @@ templates: - event: ClaimedRewards(uint256) handler: handleClmStrategyClaimedRewards - - - name: ClmRewardPool + - name: RewardPool kind: ethereum/contract network: {{network}} source: @@ -229,12 +228,48 @@ 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: - event: Initialized(uint8) handler: handleRewardPoolInitialized + + - name: ClmRewardPool + kind: ethereum/contract + network: {{network}} + source: + abi: RewardPool + mapping: + kind: ethereum/events + apiVersion: 0.0.7 # 0xgraph's version + language: wasm/assemblyscript + file: ./src/clm/mapping/reward-pool.ts + entities: *clmEntities + abis: *abis + eventHandlers: + - event: Transfer(indexed address,indexed address,uint256) + handler: handleRewardPoolTransfer + - event: RewardPaid(indexed address,indexed address,uint256) + handler: handleRewardPoolRewardPaid + - event: AddReward(address) + handler: handleRewardPoolAddReward + - event: RemoveReward(address,address) + handler: handleRewardPoolRemoveReward + + - name: ClassicRewardPool + kind: ethereum/contract + network: {{network}} + source: + abi: RewardPool + mapping: + kind: ethereum/events + apiVersion: 0.0.7 # 0xgraph's version + language: wasm/assemblyscript + file: ./src/classic/mapping/reward-pool.ts + entities: *clmEntities + abis: *abis + eventHandlers: - event: Transfer(indexed address,indexed address,uint256) handler: handleRewardPoolTransfer - event: RewardPaid(indexed address,indexed address,uint256)