From 38fade8d28071c3d92df97b9379269776d18c403 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:22:54 +0200 Subject: [PATCH] aprs: adding working supply --- .../ve-bal-gauge-apr.service.ts | 19 ++- .../pool/lib/staking/gauge-staking.service.ts | 123 +++++++++++++----- modules/pool/pool.gql | 1 + .../migration.sql | 2 + prisma/schema.prisma | 1 + 5 files changed, 110 insertions(+), 36 deletions(-) create mode 100644 prisma/migrations/20230911145905_add_working_supplies/migration.sql diff --git a/modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts b/modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts index a83d332ac..6b2695208 100644 --- a/modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts +++ b/modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts @@ -92,12 +92,25 @@ export class GaugeAprService implements PoolAprService { const aprItemId = `${pool.id}-${rewardTokenDefinition.symbol}-apr`; const aprRangeId = `${pool.id}-bal-apr-range`; + // We need gauge's workingSupply and the pool BPT price + const gaugeEmissionsUsd = rewardTokenValuePerYear; + + // Only 40% of LP token staked accrue emissions, totalSupply = workingSupply * 2.5 + const workingSupply = (parseFloat(preferredStaking.gauge.workingSupply) + 0.4) / 0.4; + const bptPrice = this.tokenService.getPriceForToken(tokenPrices, pool.address); + const workingSupplyUsd = workingSupply * bptPrice; + + let balApr = 0; + if (workingSupply > 0) { + balApr = gaugeEmissionsUsd / workingSupplyUsd; + } + const itemData = { id: aprItemId, chain: networkContext.chain, poolId: pool.id, title: `${rewardTokenDefinition.symbol} reward APR`, - apr: 0, + apr: balApr, type: PrismaPoolAprType.NATIVE_REWARD, group: null, }; @@ -106,8 +119,8 @@ export class GaugeAprService implements PoolAprService { id: aprRangeId, chain: networkContext.chain, aprItemId: aprItemId, - min: rewardApr, - max: rewardApr * this.MAX_VEBAL_BOOST, + min: balApr, + max: balApr * this.MAX_VEBAL_BOOST, }; operations.push( diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 3f3d27941..d2ea55107 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -1,7 +1,7 @@ import { PoolStakingService } from '../../pool-types'; import { prisma } from '../../../../prisma/prisma-client'; import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; -import { PrismaPoolStakingType } from '@prisma/client'; +import { Chain, PrismaPoolStakingType } from '@prisma/client'; import { networkContext } from '../../../network/network-context.service'; import { GaugeSubgraphService, LiquidityGaugeStatus } from '../../../subgraphs/gauge-subgraph/gauge-subgraph.service'; import { formatUnits } from 'ethers/lib/utils'; @@ -9,23 +9,27 @@ import { getContractAt } from '../../../web3/contract'; import childChainGaugeV2Abi from './abi/ChildChainGaugeV2.json'; import childChainGaugeV1Abi from './abi/ChildChainGaugeV1.json'; import moment from 'moment'; -import { formatFixed } from '@ethersproject/bignumber'; +import { BigNumber, formatFixed } from '@ethersproject/bignumber'; import { Multicaller3 } from '../../../web3/multicaller3'; import _ from 'lodash'; -interface ChildChainInfo { +interface GaugeRate { /** 1 for old gauges, 2 for gauges receiving cross chain BAL rewards */ version: number; /** BAL per second received by the gauge */ rate: string; + // Amount of tokens staked in the gauge + workingSupply: string; } export class GaugeStakingService implements PoolStakingService { private balAddress: string; + constructor(private readonly gaugeSubgraphService: GaugeSubgraphService, balAddress: string) { this.balAddress = balAddress.toLowerCase(); } - public async syncStakingForPools(): Promise { + + async syncStakingForPools(): Promise { const pools = await prisma.prismaPool.findMany({ where: { chain: networkContext.chain }, }); @@ -37,7 +41,11 @@ export class GaugeStakingService implements PoolStakingService { const allGaugeAddresses = subgraphPoolsWithGauges.map((pool) => pool.gaugesList).flat(); - const childChainGaugeInfo = await this.getChildChainGaugeInfo(allGaugeAddresses); + const childChainGaugeInfo = await ( + networkContext.chain === Chain.MAINNET + ? this.getMainnetGaugeRates(allGaugeAddresses) + : this.getChildChainGaugeInfo(allGaugeAddresses) + ); for (const gaugePool of subgraphPoolsWithGauges) { const pool = pools.find((pool) => pool.id === gaugePool.poolId); @@ -84,6 +92,7 @@ export class GaugeStakingService implements PoolStakingService { update: { status: gaugeStatus, version: gaugeVersion, + workingSupply: childChainGaugeInfo[gauge.id].workingSupply, }, }), ); @@ -180,40 +189,88 @@ export class GaugeStakingService implements PoolStakingService { await prismaBulkExecuteOperations(operations, true, undefined); } - async getChildChainGaugeInfo(gaugeAddresses: string[]): Promise<{ [gaugeAddress: string]: ChildChainInfo }> { + /** + * Get the inflation rate for all the gauges on child chains. + * + * @param gaugeAddresses + * @returns + */ + private async getChildChainGaugeInfo(gaugeAddresses: string[]): Promise<{ [gaugeAddress: string]: GaugeRate }> { const currentWeek = Math.floor(Date.now() / 1000 / 604800); - const childChainAbi = - networkContext.chain === 'MAINNET' - ? 'function inflation_rate() view returns (uint256)' - : 'function inflation_rate(uint256 week) view returns (uint256)'; - const multicall = new Multicaller3([childChainAbi]); - - let response: { [gaugeAddress: string]: ChildChainInfo } = {}; + const childChainAbi = [ + 'function inflation_rate(uint256 week) view returns (uint256)', + 'function working_supply() view returns (uint256)' + ]; + const multicall = new Multicaller3(childChainAbi); gaugeAddresses.forEach((address) => { - // Only L2 gauges have the inflation_rate with a week parameter - if (networkContext.chain === 'MAINNET') { - multicall.call(address, address, 'inflation_rate', [], true); - } else { - multicall.call(address, address, 'inflation_rate', [currentWeek], true); - } + multicall.call(`${address}.inflationRate`, address, 'inflation_rate', [currentWeek], true); + multicall.call(`${address}.workingSupply`, address, 'working_supply', [], true); }); - const childChainData = (await multicall.execute()) as Record; - - for (const childChainGauge in childChainData) { - if (childChainData[childChainGauge]) { - response[childChainGauge] = { - version: 2, - rate: formatUnits(childChainData[childChainGauge]!, 18), - }; - } else { - response[childChainGauge] = { - version: 1, - rate: '0.0', - }; + const results = (await multicall.execute()) as { + [address: string]: { + inflationRate: BigNumber | undefined, + workingSupply: BigNumber } - } + }; + + const response = Object.keys(results).reduce((acc, address) => { + const rate = results[address].inflationRate ? formatUnits(results[address].inflationRate!) : '0.0'; + const workingSupply = formatUnits(results[address].workingSupply); + const version = results[address].inflationRate ? 2 : 1; + acc[address] = { version, rate, workingSupply }; + return acc; + }, {} as { [gaugeAddress: string]: GaugeRate }); + + return response; + } + + /** + * Get the inflation rate for all the gauges on mainnet. + * Gauges on mainnet use the inflation rate from token admin contract + * and relative weight from the gauge contract. + * + * @param gaugeAddresses + * @returns + */ + private async getMainnetGaugeRates(gaugeAddresses: string[]): Promise<{ [gaugeAddress: string]: GaugeRate }> { + const version = 2; // On Mainnet BAL is always distributed directly to gauges + const { gaugeControllerAddress } = networkContext.data; + const abi = [ + 'function inflation_rate() view returns (uint256)', + 'function working_supply() view returns (uint256)', + 'function gauge_relative_weight(address) view returns (uint256)' + ]; + const multicall = new Multicaller3(abi); + + // On mainnet inflation rate is the same for all the gauges + multicall.call('inflation_rate', gaugeAddresses[0], 'inflation_rate', [], true); + + gaugeAddresses.forEach((address) => { + multicall.call(`${address}.weight`, gaugeControllerAddress!, 'gauge_relative_weight', [address], true); + multicall.call(`${address}.workingSupply`, address, 'working_supply', [], true); + }); + + const multicallResult = await multicall.execute() as { inflation_rate: BigNumber } & { [address: string]: { weight: BigNumber, workingSupply: BigNumber } }; + const { inflation_rate, ...gaugeData } = multicallResult; + const inflationRate = Number(formatUnits(inflation_rate!)); + + const weightedRates = _.mapValues(gaugeData, ({ weight }) => { + if (weight.eq(0)) return 0; + return inflationRate * Number(formatUnits(weight)); + }); + + const response = Object.keys(gaugeData).reduce((acc, address) => { + acc[address] = { + version, + rate: weightedRates[address] > 0 + ? weightedRates[address].toFixed(18) + : '0.0', + workingSupply: formatUnits(gaugeData[address].workingSupply) + }; + return acc; + }, {} as { [gaugeAddress: string]: GaugeRate }); return response; } diff --git a/modules/pool/pool.gql b/modules/pool/pool.gql index c168048ae..e1bcfe7dc 100644 --- a/modules/pool/pool.gql +++ b/modules/pool/pool.gql @@ -798,6 +798,7 @@ type GqlPoolStakingGauge { rewards: [GqlPoolStakingGaugeReward!]! status: GqlPoolStakingGaugeStatus! version: Int! + workingSupply: String! # There can be more than one gauge per pool, but only one preferred. For simplicity of handling, we focus on # the primary gauge. otherGauges: [GqlPoolStakingOtherGauge!] diff --git a/prisma/migrations/20230911145905_add_working_supplies/migration.sql b/prisma/migrations/20230911145905_add_working_supplies/migration.sql new file mode 100644 index 000000000..c922dca4b --- /dev/null +++ b/prisma/migrations/20230911145905_add_working_supplies/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "PrismaPoolStakingGauge" ADD COLUMN "workingSupply" TEXT NOT NULL DEFAULT '0.0'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 76e03f730..bace27203 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -470,6 +470,7 @@ model PrismaPoolStakingGauge { rewards PrismaPoolStakingGaugeReward[] status PrismaPoolStakingGaugeStatus @default(ACTIVE) version Int @default(1) + workingSupply String @default("0.0") } enum PrismaPoolStakingGaugeStatus {