Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add output token support #20

Merged
merged 1 commit into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ yarn test:lint # run prettier linter
- clmStrategy.vault()
- clmStrategy.price()
- clmStrategy.range()
- clmStrategy.output() // optional
- clmStrategy.balanceOfPool()
- clmStrategy.lpToken1ToNativePrice()
- clmStrategy.lpToken0ToNativePrice()
Expand Down
23 changes: 19 additions & 4 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,9 @@ type CLM @entity {
underlyingToken0: Token!
"The underlying tokens contained in the CLM. This is the second token."
underlyingToken1: Token!

"The output tokens of the CLM's strategy. These will be sent to the reward pool if the strategy does not automatically compound."
outputTokens: [Token!]!
"The reward tokens of the CLM's rewardpool. These will be rewarded to positions when the CLM earns non-compounding yield."
rewardTokens: [Token!]!

Expand All @@ -413,6 +416,8 @@ type CLM @entity {
token0ToNativePrice: BigInt!
"Latest token 1 price in native we have seen. Expressed with 18 decimals."
token1ToNativePrice: BigInt!
"Latest output token prices in native we have seen. Ordered by clm.outputTokens. Expressed with 18 decimals."
outputToNativePrices: [BigInt!]!
"Latest reward token prices in native we have seen. Ordered by clm.rewardTokens. Expressed with 18 decimals."
rewardToNativePrices: [BigInt!]!
"Latest native token price we have seen. Expressed with 18 decimals."
Expand Down Expand Up @@ -489,6 +494,8 @@ type ClmSnapshot @entity {
token0ToNativePrice: BigInt!
"Latest token 1 price in native of this snapshot. Expressed with 18 decimals."
token1ToNativePrice: BigInt!
"Latest output token prices in native of this snapshot. Ordered by clm.outputTokens. Expressed with 18 decimals."
outputToNativePrices: [BigInt!]!
"Latest reward token prices in native of this snapshot. Ordered by clm.rewardTokens. Expressed with 18 decimals."
rewardToNativePrices: [BigInt!]!
"Latest native token price of this snapshot. Expressed with 18 decimals."
Expand Down Expand Up @@ -540,6 +547,8 @@ type ClmStrategy @entity {
clm: CLM!
"The manager of the strategy"
manager: ClmManager!
"The output token sent to the reward pool. Set to NULL token if the strategy automatically compounds."
outputToken: Token!
"The transaction that created the strategy"
createdWith: Transaction!
"Technical field to remember if the strategy was already initialized"
Expand Down Expand Up @@ -593,8 +602,8 @@ type ClmHarvestEvent @entity(immutable: true) {
compoundedAmount0: BigInt!
"The amount of second underlying tokens compounded"
compoundedAmount1: BigInt!
"The amount of reward tokens collected. Ordered by clm.rewardTokens."
collectedRewards: [BigInt!]!
"The amount of output tokens collected. Ordered by clm.outputTokens."
collectedOutputAmounts: [BigInt!]!

"Total amount of liquidity in the manager at time of harvest"
managerTotalSupply: BigInt!
Expand All @@ -605,6 +614,8 @@ type ClmHarvestEvent @entity(immutable: true) {
token0ToNativePrice: BigInt!
"Token 1 price in native at the time of harvest. Expressed with 18 decimals."
token1ToNativePrice: BigInt!
"Output token prices in native at the time of harvest. Expressed with 18 decimals. Ordered by clm.outputTokens."
outputToNativePrices: [BigInt!]!
"Reward token prices in native at the time of harvest. Expressed with 18 decimals. Ordered by clm.rewardTokens."
rewardToNativePrices: [BigInt!]!
"Native token price at the time of harvest. Expressed with 18 decimals."
Expand Down Expand Up @@ -645,13 +656,15 @@ type ClmManagerCollectionEvent @entity(immutable: true) {
collectedAmount0: BigInt!
"Amount of collected fees in the second token"
collectedAmount1: BigInt!
"Amount of collected fees in the reward tokens. Ordered by clm.rewardTokens."
collectedRewardAmounts: [BigInt!]!
"Amount of collected fees in the output tokens. Ordered by clm.outputTokens."
collectedOutputAmounts: [BigInt!]!

"Token 0 price in native at the time of the collection. Expressed with 18 decimals."
token0ToNativePrice: BigInt!
"Token 1 price in native at the time of the collection. Expressed with 18 decimals."
token1ToNativePrice: BigInt!
"Output token prices in native at the time of the collection. Expressed with 18 decimals. Ordered by clm.outputTokens."
outputToNativePrices: [BigInt!]!
"Reward token prices in native at the time of the collection. Expressed with 18 decimals. Ordered by clm.rewardTokens."
rewardToNativePrices: [BigInt!]!
"Native token price at the time of the interaction. Expressed with 18 decimals."
Expand Down Expand Up @@ -744,6 +757,8 @@ type ClmPositionInteraction @entity(immutable: true) {
token0ToNativePrice: BigInt!
"Token 1 price in native at the time of the interaction. Expressed with 18 decimals."
token1ToNativePrice: BigInt!
"Output token prices in native at the time of the interaction. Expressed with 18 decimals. Ordered by clm.outputTokens."
outputToNativePrices: [BigInt!]!
"Reward token prices in native at the time of the interaction. Expressed with 18 decimals. Ordered by clm.rewardTokens."
rewardToNativePrices: [BigInt!]!
"Native token price at the time of the interaction. Expressed with 18 decimals."
Expand Down
46 changes: 38 additions & 8 deletions src/clm/compound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,29 @@ export function handleClmStrategyHarvestAmounts(event: CLMHarvestEvent): void {
}

export function handleClmStrategyHarvestRewards(event: CLMHarvestRewardsEvent): void {
// TODO: handle output tokens and event.params.fees or remove this handler
handleClmStrategyHarvest(event, ZERO_BI, ZERO_BI, [])
const amountClaimed = event.params.fees
const strategy = getClmStrategy(event.address)
const clm = getCLM(strategy.clm)
const outputToken = strategy.outputToken

const collectedOutputAmounts = new Array<BigInt>()
const outputTokens = clm.outputTokens
for (let i = 0; i < outputTokens.length; i++) {
if (outputTokens[i].equals(outputToken)) {
collectedOutputAmounts.push(amountClaimed)
} else {
collectedOutputAmounts.push(ZERO_BI)
}
}

handleClmStrategyHarvest(event, ZERO_BI, ZERO_BI, collectedOutputAmounts)
}

function handleClmStrategyHarvest(
event: ethereum.Event,
compoundedAmount0: BigInt,
compoundedAmount1: BigInt,
collectedRewards: Array<BigInt>,
collectedOutputAmounts: Array<BigInt>,
): void {
let strategy = getClmStrategy(event.address)
let clm = getCLM(strategy.clm)
Expand All @@ -52,11 +66,12 @@ function handleClmStrategyHarvest(
harvest.underlyingAmount1 = clmData.token1Balance
harvest.compoundedAmount0 = compoundedAmount0
harvest.compoundedAmount1 = compoundedAmount1
harvest.collectedRewards = collectedRewards
harvest.collectedOutputAmounts = collectedOutputAmounts
harvest.managerTotalSupply = clmData.managerTotalSupply
harvest.rewardPoolTotalSupply = clmData.rewardPoolTotalSupply
harvest.token0ToNativePrice = clmData.token0ToNativePrice
harvest.token1ToNativePrice = clmData.token1ToNativePrice
harvest.outputToNativePrices = clmData.outputToNativePrices
harvest.rewardToNativePrices = clmData.rewardToNativePrices
harvest.nativeToUSDPrice = clmData.nativeToUSDPrice
harvest.save()
Expand All @@ -71,15 +86,29 @@ export function handleClmStrategyClaimedFees(event: CLMClaimedFeesEvent): void {
)
}
export function handleClmStrategyClaimedRewards(event: CLMClaimedRewardsEvent): void {
// TODO: handle output tokens and event.params.fees or remove this handler
handleClmStrategyFees(event, ZERO_BI, ZERO_BI, [])
const amountClaimed = event.params.fees
const strategy = getClmStrategy(event.address)
const clm = getCLM(strategy.clm)
const outputToken = strategy.outputToken

const collectedOutputAmounts = new Array<BigInt>()
const outputTokens = clm.outputTokens
for (let i = 0; i < outputTokens.length; i++) {
if (outputTokens[i].equals(outputToken)) {
collectedOutputAmounts.push(amountClaimed)
} else {
collectedOutputAmounts.push(ZERO_BI)
}
}

handleClmStrategyFees(event, ZERO_BI, ZERO_BI, collectedOutputAmounts)
}

function handleClmStrategyFees(
event: ethereum.Event,
collectedAmount0: BigInt,
collectedAmount1: BigInt,
collectedRewardAmounts: Array<BigInt>,
collectedOutputAmounts: Array<BigInt>,
): void {
let strategy = getClmStrategy(event.address)
let clm = getCLM(strategy.clm)
Expand Down Expand Up @@ -108,9 +137,10 @@ function handleClmStrategyFees(
collect.underlyingAltAmount1 = clmData.token1PositionAltBalance
collect.collectedAmount0 = collectedAmount0
collect.collectedAmount1 = collectedAmount1
collect.collectedRewardAmounts = collectedRewardAmounts
collect.collectedOutputAmounts = collectedOutputAmounts
collect.token0ToNativePrice = clmData.token0ToNativePrice
collect.token1ToNativePrice = clmData.token1ToNativePrice
collect.outputToNativePrices = clmData.outputToNativePrices
collect.rewardToNativePrices = clmData.rewardToNativePrices
collect.nativeToUSDPrice = clmData.nativeToUSDPrice
collect.save()
Expand Down
5 changes: 5 additions & 0 deletions src/clm/entity/clm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export function getCLM(managerAddress: Bytes): CLM {

clm.underlyingToken0 = ADDRESS_ZERO
clm.underlyingToken1 = ADDRESS_ZERO
clm.outputTokens = []
clm.rewardTokens = []

clm.managerTotalSupply = ZERO_BI
clm.rewardPoolTotalSupply = ZERO_BI

clm.token0ToNativePrice = ZERO_BI
clm.token1ToNativePrice = ZERO_BI
clm.outputToNativePrices = []
clm.rewardToNativePrices = []
clm.nativeToUSDPrice = ZERO_BI

Expand Down Expand Up @@ -72,6 +74,7 @@ export function getClmStrategy(strategyAddress: Bytes): ClmStrategy {
strategy.clm = ADDRESS_ZERO
strategy.manager = ADDRESS_ZERO
strategy.createdWith = ADDRESS_ZERO
strategy.outputToken = getNullToken().id
strategy.isInitialized = false
}
return strategy
Expand Down Expand Up @@ -110,6 +113,7 @@ export function getClmSnapshot(clm: CLM, timestamp: BigInt, period: BigInt): Clm

snapshot.token0ToNativePrice = ZERO_BI
snapshot.token1ToNativePrice = ZERO_BI
snapshot.outputToNativePrices = []
snapshot.rewardToNativePrices = []
snapshot.nativeToUSDPrice = ZERO_BI

Expand All @@ -130,6 +134,7 @@ export function getClmSnapshot(clm: CLM, timestamp: BigInt, period: BigInt): Clm
snapshot.rewardPoolTotalSupply = previousSnapshot.rewardPoolTotalSupply
snapshot.token0ToNativePrice = previousSnapshot.token0ToNativePrice
snapshot.token1ToNativePrice = previousSnapshot.token1ToNativePrice
snapshot.outputToNativePrices = previousSnapshot.outputToNativePrices
snapshot.rewardToNativePrices = previousSnapshot.rewardToNativePrices
snapshot.nativeToUSDPrice = previousSnapshot.nativeToUSDPrice
snapshot.priceOfToken0InToken1 = previousSnapshot.priceOfToken0InToken1
Expand Down
1 change: 1 addition & 0 deletions src/clm/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ function updateUserPosition(
}
interaction.token0ToNativePrice = clmData.token0ToNativePrice
interaction.token1ToNativePrice = clmData.token1ToNativePrice
interaction.outputToNativePrices = clmData.outputToNativePrices
interaction.rewardToNativePrices = clmData.rewardToNativePrices
interaction.nativeToUSDPrice = clmData.nativeToUSDPrice
interaction.save()
Expand Down
24 changes: 24 additions & 0 deletions src/clm/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,30 @@ function fetchInitialCLMDataAndSave(clm: CLM): void {
const underlyingToken0 = fetchAndSaveTokenData(underlyingToken0Address)
const underlyingToken1 = fetchAndSaveTokenData(underlyingToken1Address)

const strategyAddress = Address.fromBytes(clm.strategy)
const strategyContract = ClmStrategyContract.bind(strategyAddress)
const outputTokenRes = strategyContract.try_output()
if (!outputTokenRes.reverted) {
const outputTokenAddress = outputTokenRes.value
const outputToken = fetchAndSaveTokenData(outputTokenAddress)

const currentOutputTokens = clm.outputTokens
let found = false
for (let i = 0; i < currentOutputTokens.length; i++) {
if (currentOutputTokens[i].equals(outputToken.id)) {
found = true
break
}
}

if (!found) {
currentOutputTokens.push(outputToken.id)
clm.outputTokens = currentOutputTokens
}
} else {
log.warning("fetchInitialCLMDataAndSave: Output token not found for CLM {}", [clm.id.toHexString()])
}

clm.managerToken = managerToken.id
clm.underlyingToken0 = underlyingToken0.id
clm.underlyingToken1 = underlyingToken1.id
Expand Down
57 changes: 53 additions & 4 deletions src/clm/utils/clm-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
PYTH_NATIVE_PRICE_ID,
PRICE_ORACLE_TYPE,
} from "../../config"
import { Multicall3Params, multicall } from "../../common/utils/multicall"
import { Multicall3Params, MulticallResult, multicall } from "../../common/utils/multicall"
import { getToken, isNullToken } from "../../common/entity/token"
import { CLM_SNAPSHOT_PERIODS } from "./snapshot"
import { getClmSnapshot } from "../entity/clm"
Expand Down Expand Up @@ -57,6 +57,7 @@ export function fetchCLMData(clm: CLM): CLMData {

const hasRewardPool = !isNullToken(rewardPoolToken)
const rewardTokens = clm.rewardTokens
let rewardCallStartIndex = calls.length
if (hasRewardPool) {
calls.push(new Multicall3Params(rewardPoolAddress, "totalSupply()", "uint256"))
calls.push(
Expand All @@ -65,6 +66,7 @@ export function fetchCLMData(clm: CLM): CLMData {
]),
)

rewardCallStartIndex = calls.length // ignore the first two calls, those just warm up the prices
for (let i = 0; i < rewardTokens.length; i++) {
const rewardTokenAddress = Address.fromBytes(rewardTokens[i])
const rewardToken = getToken(rewardTokenAddress)
Expand All @@ -84,6 +86,29 @@ export function fetchCLMData(clm: CLM): CLMData {
)
}
}
const rewardCallsEndIndex = calls.length

const outputCallsStartIndex = calls.length
const outputTokens = clm.outputTokens
for (let i = 0; i < outputTokens.length; i++) {
const outputTokenAddress = Address.fromBytes(outputTokens[i])

calls.push(
new Multicall3Params(BEEFY_ORACLE_ADDRESS, "getFreshPrice(address)", "(uint256,bool)", [
ethereum.Value.fromAddress(outputTokenAddress),
]),
)

const amountIn = changeValueEncoding(ONE_BI, ZERO_BI, token1.decimals).div(BEEFY_SWAPPER_VALUE_SCALER)
calls.push(
new Multicall3Params(BEEFY_SWAPPER_ADDRESS, "getAmountOut(address,address,uint256)", "uint256", [
ethereum.Value.fromAddress(outputTokenAddress),
ethereum.Value.fromAddress(WNATIVE_TOKEN_ADDRESS),
ethereum.Value.fromUnsignedBigInt(amountIn),
]),
)
}
const outputCallsEndIndex = calls.length

const results = multicall(calls)
const totalSupplyRes = results[0]
Expand All @@ -95,9 +120,16 @@ export function fetchCLMData(clm: CLM): CLMData {
const token1ToNativePriceRes = results[6]
const priceFeedRes = results[7]
const rewardPoolTotalSupplyRes = hasRewardPool ? results[8] : null
const rewardTokenOutputAmountsRes = hasRewardPool
? results.filter((_, i) => i >= 10).filter((_, i) => i % 2 == 1)
: []
const rewardTokenOutputAmountsRes = new Array<MulticallResult>()
if (hasRewardPool) {
for (let i = rewardCallStartIndex; i < rewardCallsEndIndex; i += 2) {
rewardTokenOutputAmountsRes.push(results[i + 1])
}
}
const outputTokenOutputAmountsRes = new Array<MulticallResult>()
for (let i = outputCallsStartIndex; i < outputCallsEndIndex; i += 2) {
outputTokenOutputAmountsRes.push(results[i + 1])
}

let managerTotalSupply = ZERO_BI
if (!totalSupplyRes.reverted) {
Expand Down Expand Up @@ -207,6 +239,7 @@ export function fetchCLMData(clm: CLM): CLMData {
} else {
log.error("Failed to fetch nativeToUSDPrice for CLM {}", [clm.id.toString()])
}

// only some clms have a reward pool token
let rewardPoolTotalSupply = ZERO_BI
if (rewardPoolTotalSupplyRes != null) {
Expand All @@ -217,6 +250,18 @@ export function fetchCLMData(clm: CLM): CLMData {
}
}

let outputToNativePrices = new Array<BigInt>()
for (let i = 0; i < outputTokenOutputAmountsRes.length; i++) {
const amountOutRes = outputTokenOutputAmountsRes[i]
if (!amountOutRes.reverted) {
const amountOut = amountOutRes.value.toBigInt().times(BEEFY_SWAPPER_VALUE_SCALER)
outputToNativePrices.push(amountOut)
} else {
outputToNativePrices.push(ZERO_BI)
log.error("Failed to fetch outputToNativePrices for CLM {}", [clm.id.toHexString()])
}
}

return new CLMData(
managerTotalSupply,
rewardPoolTotalSupply,
Expand All @@ -234,6 +279,7 @@ export function fetchCLMData(clm: CLM): CLMData {

token0ToNativePrice,
token1ToNativePrice,
outputToNativePrices,
rewardToNativePrices,
nativeToUSDPrice,
)
Expand All @@ -257,6 +303,7 @@ class CLMData {

public token0ToNativePrice: BigInt,
public token1ToNativePrice: BigInt,
public outputToNativePrices: Array<BigInt>,
public rewardToNativePrices: Array<BigInt>,
public nativeToUSDPrice: BigInt,
) {}
Expand All @@ -268,6 +315,7 @@ export function updateCLMDataAndSnapshots(clm: CLM, clmData: CLMData, nowTimesta
clm.rewardPoolTotalSupply = clmData.rewardPoolTotalSupply
clm.token0ToNativePrice = clmData.token0ToNativePrice
clm.token1ToNativePrice = clmData.token1ToNativePrice
clm.outputToNativePrices = clmData.outputToNativePrices
clm.rewardToNativePrices = clmData.rewardToNativePrices
clm.nativeToUSDPrice = clmData.nativeToUSDPrice
clm.priceOfToken0InToken1 = clmData.priceOfToken0InToken1
Expand All @@ -292,6 +340,7 @@ export function updateCLMDataAndSnapshots(clm: CLM, clmData: CLMData, nowTimesta
snapshot.rewardPoolTotalSupply = clm.rewardPoolTotalSupply
snapshot.token0ToNativePrice = clm.token0ToNativePrice
snapshot.token1ToNativePrice = clm.token1ToNativePrice
snapshot.outputToNativePrices = clm.outputToNativePrices
snapshot.rewardToNativePrices = clm.rewardToNativePrices
snapshot.nativeToUSDPrice = clm.nativeToUSDPrice
snapshot.priceOfToken0InToken1 = clm.priceOfToken0InToken1
Expand Down
Loading
Loading