From 770d336bd57a8bfb8aee6edc607f493182978dee Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:27:38 +0200 Subject: [PATCH 01/24] 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 { From e5239863d1ad2e67c744e466b5b5a7ea9567781b Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:23:08 +0200 Subject: [PATCH 02/24] aprs: fixing bal emissions --- .../ve-bal-gauge-apr.service.ts | 5 +- modules/pool/lib/staking/bal-emissions.ts | 95 +++++++++++++++++++ .../pool/lib/staking/gauge-staking.service.ts | 11 +-- 3 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 modules/pool/lib/staking/bal-emissions.ts 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 6b2695208..f6e78431e 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,9 +92,6 @@ 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); @@ -102,7 +99,7 @@ export class GaugeAprService implements PoolAprService { let balApr = 0; if (workingSupply > 0) { - balApr = gaugeEmissionsUsd / workingSupplyUsd; + balApr = rewardTokenValuePerYear / workingSupplyUsd; } const itemData = { diff --git a/modules/pool/lib/staking/bal-emissions.ts b/modules/pool/lib/staking/bal-emissions.ts new file mode 100644 index 000000000..e61bc7c82 --- /dev/null +++ b/modules/pool/lib/staking/bal-emissions.ts @@ -0,0 +1,95 @@ +/** + * Weekly Bal emissions are fixed / year according to: + * https://docs.google.com/spreadsheets/d/1FY0gi596YWBOTeu_mrxhWcdF74SwKMNhmu0qJVgs0KI/edit#gid=0 + * + * Using regular numbers for simplicity assuming frontend use only. + * + * Calculation source + * https://github.com/balancer-labs/balancer-v2-monorepo/blob/master/pkg/liquidity-mining/contracts/BalancerTokenAdmin.sol + */ + +export const INITIAL_RATE = 145000; +export const START_EPOCH_TIME = 1648465251; +const RATE_REDUCTION_TIME = 365 * 86400; +const RATE_REDUCTION_COEFFICIENT = 2 ** (1 / 4); + +/** + * Weekly BAL emissions + * + * @param currentTimestamp used to get the epoch + * @returns BAL emitted in a week + */ +export const weekly = ( + currentTimestamp: number = Math.round(new Date().getTime() / 1000) +): number => { + const miningEpoch = Math.floor( + (currentTimestamp - START_EPOCH_TIME) / RATE_REDUCTION_TIME + ); + + const rate = INITIAL_RATE * RATE_REDUCTION_COEFFICIENT ** -miningEpoch; + + return rate; +}; + +/** + * Total BAL emitted in epoch (1 year) + * + * @param epoch starting from 0 for the first year of emissions + * @returns BAL emitted in epoch + */ +export const total = (epoch: number): number => { + const weeklyRate = INITIAL_RATE * RATE_REDUCTION_COEFFICIENT ** -epoch; + const dailyRate = weeklyRate / 7; + + return dailyRate * 365; +}; + +/** + * Total BAL emitted between two timestamps + * + * @param start starting timestamp + * @param end ending timestamp + * @returns BAL emitted in period + */ +export const between = (start: number, end: number): number => { + if (start < START_EPOCH_TIME) { + throw 'start timestamp before emission schedule deployment'; + } + if (end < start) { + throw 'cannot finish before starting'; + } + + let totalEmissions = 0; + + const startingEpoch = Math.floor( + (start - START_EPOCH_TIME) / RATE_REDUCTION_TIME + ); + const endingEpoch = Math.floor( + (end - START_EPOCH_TIME) / RATE_REDUCTION_TIME + ); + + for ( + let currentEpoch = startingEpoch; + currentEpoch <= endingEpoch; + currentEpoch++ + ) { + totalEmissions += total(currentEpoch); + } + + // Subtract what isn't emmited within the time range + const startingEpochEnd = + START_EPOCH_TIME + RATE_REDUCTION_TIME * (startingEpoch + 1); + const endingEpochStart = START_EPOCH_TIME + RATE_REDUCTION_TIME * endingEpoch; + + const secondsInStartingEpoch = startingEpochEnd - start; + const secondsInEndingEpoch = end - endingEpochStart; + + totalEmissions -= + (total(startingEpoch) * (RATE_REDUCTION_TIME - secondsInStartingEpoch)) / + RATE_REDUCTION_TIME; + totalEmissions -= + (total(endingEpoch) * (RATE_REDUCTION_TIME - secondsInEndingEpoch)) / + RATE_REDUCTION_TIME; + + return totalEmissions; +}; diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index d2ea55107..908b1b643 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -11,6 +11,7 @@ import childChainGaugeV1Abi from './abi/ChildChainGaugeV1.json'; import moment from 'moment'; import { BigNumber, formatFixed } from '@ethersproject/bignumber'; import { Multicaller3 } from '../../../web3/multicaller3'; +import * as emissions from './bal-emissions'; import _ from 'lodash'; interface GaugeRate { @@ -235,26 +236,22 @@ export class GaugeStakingService implements PoolStakingService { * @returns */ private async getMainnetGaugeRates(gaugeAddresses: string[]): Promise<{ [gaugeAddress: string]: GaugeRate }> { + const now = Math.round(new Date().getTime() / 1000); 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 gaugeData = await multicall.execute() as { [address: string]: { weight: BigNumber, workingSupply: BigNumber } }; + const inflationRate = emissions.weekly(now) / 604800; // BAL inflation rate per second const weightedRates = _.mapValues(gaugeData, ({ weight }) => { if (weight.eq(0)) return 0; From 3d1a16dd9438ce71d0022a10f589aed772ef0b35 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:27:01 +0200 Subject: [PATCH 03/24] fix: adding required parameter --- modules/vebal/prismaPoolStakingGauge.mock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/vebal/prismaPoolStakingGauge.mock.ts b/modules/vebal/prismaPoolStakingGauge.mock.ts index 756d726a6..019a10925 100644 --- a/modules/vebal/prismaPoolStakingGauge.mock.ts +++ b/modules/vebal/prismaPoolStakingGauge.mock.ts @@ -11,6 +11,7 @@ export function aPrismaPoolStakingGauge(...options: Partial Date: Mon, 18 Sep 2023 18:43:35 +0200 Subject: [PATCH 04/24] Staking rewards refactored --- .../pool/lib/staking/gauge-staking.service.ts | 411 +++++++++--------- 1 file changed, 194 insertions(+), 217 deletions(-) diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 908b1b643..b8c3a8f2b 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -1,15 +1,25 @@ +/** + * Supports calculation of BAL rewards sent to gauges. Balancer setup has 3 types of gauges: + * + * 1. Mainnet gauges with working supply and relative weight + * 2. Old L2 gauges with BAL rewards sent as a reward token + * 3. New L2 gauges (aka child chain gauges) with direct BAL rewards through a streamer. + * + * This service supports all 3 types of gauges and stores the BAL rewards in the DB as a reward token rate per second. + */ import { PoolStakingService } from '../../pool-types'; import { prisma } from '../../../../prisma/prisma-client'; import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; 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'; -import { getContractAt } from '../../../web3/contract'; +import type { LiquidityGauge, RewardToken } from '../../../subgraphs/gauge-subgraph/generated/gauge-subgraph-types'; +import gaugeControllerAbi from '../../../vebal/abi/GaugeController.json'; import childChainGaugeV2Abi from './abi/ChildChainGaugeV2.json'; import childChainGaugeV1Abi from './abi/ChildChainGaugeV1.json'; -import moment from 'moment'; -import { BigNumber, formatFixed } from '@ethersproject/bignumber'; +import { BigNumber } from '@ethersproject/bignumber'; +import { formatUnits } from '@ethersproject/units'; +import type { JsonFragment } from '@ethersproject/abi'; import { Multicaller3 } from '../../../web3/multicaller3'; import * as emissions from './bal-emissions'; import _ from 'lodash'; @@ -23,253 +33,220 @@ interface GaugeRate { workingSupply: string; } +interface GaugeRewardData { + [address: string]: { + rewardData: { + [address: string]: { + period_finish?: BigNumber, + rate?: BigNumber + } + } + } +} + +interface GaugeBalDistributionData { + [address: string]: { + rate?: BigNumber, + weight?: BigNumber, + workingSupply?: BigNumber + } +} + export class GaugeStakingService implements PoolStakingService { private balAddress: string; + private balMulticaller: Multicaller3; // Used to query BAL rate and gauge data + private rewardsMulticallerV1: Multicaller3; // Used to query rewards token data for v1 gauges + private rewardsMulticallerV2: Multicaller3; // Used to query rewards token data for v2 gauges constructor(private readonly gaugeSubgraphService: GaugeSubgraphService, balAddress: string) { this.balAddress = balAddress.toLowerCase(); + + this.balMulticaller = new Multicaller3([ + ...childChainGaugeV2Abi.filter((abi) => abi.name === 'working_supply'), + ...childChainGaugeV2Abi.filter((abi) => abi.name === 'inflation_rate'), + gaugeControllerAbi.find((abi) => abi.name === 'gauge_relative_weight') as JsonFragment, + ]); + + this.rewardsMulticallerV1 = new Multicaller3([ + ...childChainGaugeV1Abi.filter((abi) => abi.name === 'reward_data'), + ]); + + this.rewardsMulticallerV2 = new Multicaller3([ + ...childChainGaugeV2Abi.filter((abi) => abi.name === 'reward_data'), + ]); } async syncStakingForPools(): Promise { + // Getting data from the DB and subgraph const pools = await prisma.prismaPool.findMany({ where: { chain: networkContext.chain }, }); const poolIds = pools.map((pool) => pool.id); - const { pools: subgraphPoolsWithGauges } = await this.gaugeSubgraphService.getPoolsWithGauges(poolIds); - const operations: any[] = []; + const subgraphGauges = subgraphPoolsWithGauges + .map((pool) => pool.gauges) + .flat() + .filter((gauge): gauge is LiquidityGauge => !!gauge); + + const dbGauges = subgraphGauges + .map((gauge) => ({ + id: gauge.id, + poolId: gauge.poolId, + // we need to set the status based on the preferentialGauge entity on the gaugePool. If it's set there, it's preferential, otherwise it's active (or killed) + status: gauge.isKilled ? 'KILLED' : gauge.isPreferentialGauge ? 'PREFERRED' : 'ACTIVE' as LiquidityGaugeStatus, + version: gauge.streamer ? 2 : 1 as 1 | 2, + tokens: gauge.tokens || [], + })); + + // Get tokens used for all reward tokens including native BAL address, which might not be on the list of tokens stored in the gauge + const prismaTokens = await prisma.prismaToken.findMany({ + where: { + address: { + in: [ + this.balAddress, + ...subgraphGauges + .map((gauge) => gauge.tokens?.map((token) => token.id.split('-')[0].toLowerCase())) + .flat() + .filter((address): address is string => !!address) + ] + }, + chain: networkContext.chain, + }, + }); - const allGaugeAddresses = subgraphPoolsWithGauges.map((pool) => pool.gaugesList).flat(); + const onchainRates = await this.getOnchainRewardTokensData(dbGauges); - const childChainGaugeInfo = await ( - networkContext.chain === Chain.MAINNET - ? this.getMainnetGaugeRates(allGaugeAddresses) - : this.getChildChainGaugeInfo(allGaugeAddresses) - ); + // Prepare DB operations + const operations: any[] = []; - for (const gaugePool of subgraphPoolsWithGauges) { - const pool = pools.find((pool) => pool.id === gaugePool.poolId); - if (!pool) { + // DB operations for gauges + for (const gauge of dbGauges) { + // Skip gauges which pools aren't in the DB + if (!gauge.poolId || !poolIds.includes(gauge.poolId)) { continue; } - if (gaugePool.gauges) { - for (const gauge of gaugePool.gauges) { - // we need to set the status based on the preferentialGauge entity on the gaugePool. If it's set there, it's preferential, otherwise it's active (or killed) - let gaugeStatus: LiquidityGaugeStatus = 'PREFERRED'; - if (gauge.isKilled) { - gaugeStatus = 'KILLED'; - } else if (gaugePool.preferentialGauge?.id !== gauge.id) { - gaugeStatus = 'ACTIVE'; - } - - operations.push( - prisma.prismaPoolStaking.upsert({ - where: { id_chain: { id: gauge.id, chain: networkContext.chain } }, - create: { - id: gauge.id, - chain: networkContext.chain, - poolId: pool.id, - type: 'GAUGE', - address: gauge.id, - }, - update: {}, - }), - ); - - const gaugeVersion = childChainGaugeInfo[gauge.id] ? childChainGaugeInfo[gauge.id].version : 1; - - operations.push( - prisma.prismaPoolStakingGauge.upsert({ - where: { id_chain: { id: gauge.id, chain: networkContext.chain } }, - create: { - id: gauge.id, - stakingId: gauge.id, - gaugeAddress: gauge.id, - chain: networkContext.chain, - status: gaugeStatus, - version: gaugeVersion, - }, - update: { - status: gaugeStatus, - version: gaugeVersion, - workingSupply: childChainGaugeInfo[gauge.id].workingSupply, - }, - }), - ); - - // Add BAL as a reward token for the v2 gauge - // need to add '-0' to the ID because it get's split by that further down. - if (gaugeVersion === 2) { - if (gauge.tokens) { - gauge.tokens.push({ - id: `${this.balAddress}-0`, - decimals: 18, - symbol: 'BAL', - rate: childChainGaugeInfo[gauge.id].rate, - }); - } else { - gauge.tokens = [ - { - id: `${this.balAddress}-0`, - decimals: 18, - symbol: 'BAL', - rate: childChainGaugeInfo[gauge.id].rate, - }, - ]; - } - } - if (gauge.tokens) { - const rewardTokens = await prisma.prismaToken.findMany({ - where: { - address: { in: gauge.tokens.map((token) => token.id.split('-')[0].toLowerCase()) }, - chain: networkContext.chain, - }, - }); - for (let rewardToken of gauge.tokens) { - const tokenAddress = rewardToken.id.split('-')[0].toLowerCase(); - const token = rewardTokens.find((token) => token.address === tokenAddress); - if (!token) { - console.error( - `Could not find reward token (${tokenAddress}) in DB for gauge ${gauge.id} of pool ${pool.id}`, - ); - continue; - } - const id = `${gauge.id}-${tokenAddress}`; + operations.push( + prisma.prismaPoolStaking.upsert({ + where: { id_chain: { id: gauge.id, chain: networkContext.chain } }, + create: { + id: gauge.id, + chain: networkContext.chain, + poolId: gauge.poolId, + type: 'GAUGE', + address: gauge.id, + }, + update: {}, + }), + ); - let rewardRate = '0.0'; - let periodFinish: number; - - if (gaugeVersion === 1) { - const gaugeV1 = getContractAt(gauge.id, childChainGaugeV1Abi); - const rewardData = await gaugeV1.reward_data(tokenAddress); - - periodFinish = rewardData[2]; - if (periodFinish > moment().unix()) { - // period still running - rewardRate = formatFixed(rewardData[3], token.decimals); - } - } else { - // we can't get BAL rate from the reward data but got it from the inflation_rate call which set the rewardToken.rate - if (tokenAddress === this.balAddress) { - rewardRate = rewardToken.rate ? rewardToken.rate : '0.0'; - } else { - const gaugeV2 = getContractAt(gauge.id, childChainGaugeV2Abi); - const rewardData = await gaugeV2.reward_data(tokenAddress); - - periodFinish = parseFloat(formatUnits(rewardData[1], 0)); - if (periodFinish > moment().unix()) { - // period still running - rewardRate = formatFixed(rewardData[2], token.decimals); - } - } - } + operations.push( + prisma.prismaPoolStakingGauge.upsert({ + where: { id_chain: { id: gauge.id, chain: networkContext.chain } }, + create: { + id: gauge.id, + stakingId: gauge.id, + gaugeAddress: gauge.id, + chain: networkContext.chain, + status: gauge.status, + version: gauge.version, + }, + update: { + status: gauge.status, + version: gauge.version, + workingSupply: onchainRates.find(({ id }) => id === gauge.id)?.workingSupply, + }, + }), + ); + } - operations.push( - prisma.prismaPoolStakingGaugeReward.upsert({ - create: { - id, - chain: networkContext.chain, - gaugeId: gauge.id, - tokenAddress: tokenAddress, - rewardPerSecond: rewardRate, - }, - update: { - rewardPerSecond: rewardRate, - }, - where: { id_chain: { id, chain: networkContext.chain } }, - }), - ); - } - } - } + // DB operations for gauge reward tokens + for (const { id, rewardPerSecond } of onchainRates) { + const [tokenAddress, gaugeId] = id.toLowerCase().split('-'); + const token = prismaTokens.find((token) => token.address === tokenAddress); + if (!token) { + const poolId = subgraphGauges.find((gauge) => gauge.id === gaugeId)?.poolId; + console.error( + `Could not find reward token (${tokenAddress}) in DB for gauge ${gaugeId} of pool ${poolId}`, + ); + continue; } + + operations.push( + prisma.prismaPoolStakingGaugeReward.upsert({ + create: { + id, + chain: networkContext.chain, + gaugeId, + tokenAddress, + rewardPerSecond + }, + update: { + rewardPerSecond + }, + where: { id_chain: { id, chain: networkContext.chain } }, + }), + ); } await prismaBulkExecuteOperations(operations, true, undefined); } - /** - * Get the inflation rate for all the gauges on child chains. - * - * @param gaugeAddresses - * @returns - */ - private async getChildChainGaugeInfo(gaugeAddresses: string[]): Promise<{ [gaugeAddress: string]: GaugeRate }> { + private async getOnchainRewardTokensData(gauges: { id: string, version: 1 | 2, tokens: { id: string }[] }[]) { + // Get onchain data for BAL rewards const currentWeek = Math.floor(Date.now() / 1000 / 604800); - const childChainAbi = [ - 'function inflation_rate(uint256 week) view returns (uint256)', - 'function working_supply() view returns (uint256)' - ]; - const multicall = new Multicaller3(childChainAbi); - - gaugeAddresses.forEach((address) => { - multicall.call(`${address}.inflationRate`, address, 'inflation_rate', [currentWeek], true); - multicall.call(`${address}.workingSupply`, address, 'working_supply', [], true); - }); - - const results = (await multicall.execute()) as { - [address: string]: { - inflationRate: BigNumber | undefined, - workingSupply: BigNumber + for (const gauge of gauges) { + if (gauge.version === 2) { + this.balMulticaller.call(`${gauge.id}.rate`, gauge.id, 'inflation_rate', [currentWeek], true); + this.balMulticaller.call(`${gauge.id}.workingSupply`, gauge.id, 'working_supply', [], true); + } else if (networkContext.chain === Chain.MAINNET) { + this.balMulticaller.call(`${gauge.id}.weight`, networkContext.data.gaugeControllerAddress!, 'gauge_relative_weight', [gauge.id], true); + this.balMulticaller.call(`${gauge.id}.workingSupply`, gauge.id, 'working_supply', [], true); } - }; - - 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 now = Math.round(new Date().getTime() / 1000); - const version = 2; // On Mainnet BAL is always distributed directly to gauges - const { gaugeControllerAddress } = networkContext.data; - const abi = [ - 'function working_supply() view returns (uint256)', - 'function gauge_relative_weight(address) view returns (uint256)' - ]; - const multicall = new Multicaller3(abi); + } + const balData = await this.balMulticaller.execute() as GaugeBalDistributionData; - gaugeAddresses.forEach((address) => { - multicall.call(`${address}.weight`, gaugeControllerAddress!, 'gauge_relative_weight', [address], true); - multicall.call(`${address}.workingSupply`, address, 'working_supply', [], true); - }); - - const gaugeData = await multicall.execute() as { [address: string]: { weight: BigNumber, workingSupply: BigNumber } }; - const inflationRate = emissions.weekly(now) / 604800; // BAL inflation rate per second + // Get onchain data for reward tokens + for (const gauge of gauges) { + for (const token of gauge.tokens ?? []) { + const [address] = token.id.toLowerCase().split('-'); + if (gauge.version === 1) { + this.rewardsMulticallerV1.call(`${gauge.id}.rewardData.${address}`, gauge.id, 'reward_data', [address], true); + } else { + this.rewardsMulticallerV2.call(`${gauge.id}.rewardData.${address}`, gauge.id, 'reward_data', [address], true); + } + } + } + const rewardsDataV1 = await this.rewardsMulticallerV1.execute() as GaugeRewardData; + const rewardsDataV2 = await this.rewardsMulticallerV2.execute() as GaugeRewardData; + const rewardsData = { ...rewardsDataV1, ...rewardsDataV2 }; - const weightedRates = _.mapValues(gaugeData, ({ weight }) => { - if (weight.eq(0)) return 0; - return inflationRate * Number(formatUnits(weight)); - }); + const totalBalRate = emissions.weekly() / 604800; + const now = Math.floor(Date.now() / 1000); - 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 }); + // Format onchain rates for all the rewards + const onchainRates = [ + ...Object.keys(balData).map((gaugeAddress) => ({ + id: `${this.balAddress}-${gaugeAddress}`.toLowerCase(), + rewardPerSecond: balData[gaugeAddress]?.rate + ? formatUnits(balData[gaugeAddress].rate!) // L2 V2 case + : (parseFloat(formatUnits(balData[gaugeAddress].weight!)) * totalBalRate).toFixed(18), // mainnet case + workingSupply: balData[gaugeAddress]?.workingSupply ? formatUnits(balData[gaugeAddress].workingSupply!) : '0', + })), + ...Object.keys(rewardsData).map((gaugeAddress) => [ // L2 V1 case, includes tokens other than BAL + ...Object.keys(rewardsData[gaugeAddress].rewardData).map((address) => ({ + id: `${address}-${gaugeAddress}`.toLowerCase(), + rewardPerSecond: rewardsData[gaugeAddress].rewardData[address]?.period_finish + && rewardsData[gaugeAddress].rewardData[address]?.period_finish!.toNumber() > now + && formatUnits(rewardsData[gaugeAddress].rewardData[address].rate!) + || '0.0', + workingSupply: '0', + })) + ]).flat(), + ].filter(({ rewardPerSecond }) => parseFloat(rewardPerSecond) > 0) as { id: string, rewardPerSecond: string, workingSupply: string }[]; - return response; + return onchainRates; } public async reloadStakingForAllPools(stakingTypes: PrismaPoolStakingType[]): Promise { From 4eef3bcf2e4ce30319fc309c80c2696448b42f6b Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:11:49 +0200 Subject: [PATCH 05/24] fixing build issues and L2 versions --- .../pool/lib/staking/gauge-staking.service.ts | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index b8c3a8f2b..3c5c33e93 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -13,8 +13,8 @@ import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; import { Chain, PrismaPoolStakingType } from '@prisma/client'; import { networkContext } from '../../../network/network-context.service'; import { GaugeSubgraphService, LiquidityGaugeStatus } from '../../../subgraphs/gauge-subgraph/gauge-subgraph.service'; -import type { LiquidityGauge, RewardToken } from '../../../subgraphs/gauge-subgraph/generated/gauge-subgraph-types'; -import gaugeControllerAbi from '../../../vebal/abi/GaugeController.json'; +import type { LiquidityGauge } from '../../../subgraphs/gauge-subgraph/generated/gauge-subgraph-types'; +import gaugeControllerAbi from '../../../vebal/abi/gaugeController.json'; import childChainGaugeV2Abi from './abi/ChildChainGaugeV2.json'; import childChainGaugeV1Abi from './abi/ChildChainGaugeV1.json'; import { BigNumber } from '@ethersproject/bignumber'; @@ -64,8 +64,8 @@ export class GaugeStakingService implements PoolStakingService { this.balMulticaller = new Multicaller3([ ...childChainGaugeV2Abi.filter((abi) => abi.name === 'working_supply'), ...childChainGaugeV2Abi.filter((abi) => abi.name === 'inflation_rate'), - gaugeControllerAbi.find((abi) => abi.name === 'gauge_relative_weight') as JsonFragment, - ]); + gaugeControllerAbi.find((abi) => abi.name === 'gauge_relative_weight'), + ] as JsonFragment[]); this.rewardsMulticallerV1 = new Multicaller3([ ...childChainGaugeV1Abi.filter((abi) => abi.name === 'reward_data'), @@ -95,7 +95,7 @@ export class GaugeStakingService implements PoolStakingService { poolId: gauge.poolId, // we need to set the status based on the preferentialGauge entity on the gaugePool. If it's set there, it's preferential, otherwise it's active (or killed) status: gauge.isKilled ? 'KILLED' : gauge.isPreferentialGauge ? 'PREFERRED' : 'ACTIVE' as LiquidityGaugeStatus, - version: gauge.streamer ? 2 : 1 as 1 | 2, + version: gauge.streamer ? 1 : 2 as 1 | 2, tokens: gauge.tokens || [], })); @@ -151,11 +151,12 @@ export class GaugeStakingService implements PoolStakingService { chain: networkContext.chain, status: gauge.status, version: gauge.version, + workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id)?.workingSupply, }, update: { status: gauge.status, version: gauge.version, - workingSupply: onchainRates.find(({ id }) => id === gauge.id)?.workingSupply, + workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id)?.workingSupply, }, }), ); @@ -227,24 +228,34 @@ export class GaugeStakingService implements PoolStakingService { // Format onchain rates for all the rewards const onchainRates = [ - ...Object.keys(balData).map((gaugeAddress) => ({ - id: `${this.balAddress}-${gaugeAddress}`.toLowerCase(), - rewardPerSecond: balData[gaugeAddress]?.rate - ? formatUnits(balData[gaugeAddress].rate!) // L2 V2 case - : (parseFloat(formatUnits(balData[gaugeAddress].weight!)) * totalBalRate).toFixed(18), // mainnet case - workingSupply: balData[gaugeAddress]?.workingSupply ? formatUnits(balData[gaugeAddress].workingSupply!) : '0', - })), + ...Object.keys(balData).map((gaugeAddress) => { + const id = `${this.balAddress}-${gaugeAddress}`.toLowerCase(); + const { rate, weight, workingSupply } = balData[gaugeAddress]; + const rewardPerSecond = rate + ? formatUnits(rate) // L2 V2 case + : weight ? (parseFloat(formatUnits(weight!)) * totalBalRate).toFixed(18) // mainnet case + : '0'; + + return { + id, + rewardPerSecond, + workingSupply: workingSupply ? formatUnits(workingSupply) : '0', + } + }), ...Object.keys(rewardsData).map((gaugeAddress) => [ // L2 V1 case, includes tokens other than BAL - ...Object.keys(rewardsData[gaugeAddress].rewardData).map((address) => ({ - id: `${address}-${gaugeAddress}`.toLowerCase(), - rewardPerSecond: rewardsData[gaugeAddress].rewardData[address]?.period_finish - && rewardsData[gaugeAddress].rewardData[address]?.period_finish!.toNumber() > now - && formatUnits(rewardsData[gaugeAddress].rewardData[address].rate!) - || '0.0', - workingSupply: '0', - })) + ...Object.keys(rewardsData[gaugeAddress].rewardData).map((tokenAddress) => { + const id = `${tokenAddress}-${gaugeAddress}`.toLowerCase(); + const { rate, period_finish } = rewardsData[gaugeAddress].rewardData[tokenAddress]; + const rewardPerSecond = (period_finish && period_finish.toNumber() > now) ? formatUnits(rate!) : '0.0'; + + return { id, rewardPerSecond, workingSupply: '0' }; + }), ]).flat(), - ].filter(({ rewardPerSecond }) => parseFloat(rewardPerSecond) > 0) as { id: string, rewardPerSecond: string, workingSupply: string }[]; + ].filter(({ rewardPerSecond }) => parseFloat(rewardPerSecond) > 0) as { + id: string, + rewardPerSecond: string, + workingSupply: string + }[]; return onchainRates; } From b676423ecac5f439d3b829b19a163ead8d75cefe Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:43:59 +0200 Subject: [PATCH 06/24] bal staking rewards refactoring include working supply in Mainnet and V2 gauges APR calc --- modules/network/arbitrum.ts | 2 +- modules/network/avalanche.ts | 2 +- modules/network/base.ts | 2 +- modules/network/gnosis.ts | 2 +- modules/network/mainnet.ts | 2 +- modules/network/optimism.ts | 2 +- modules/network/polygon.ts | 2 +- modules/network/zkevm.ts | 2 +- .../ve-bal-gauge-apr.service.ts | 211 +++++++++--------- .../pool/lib/staking/gauge-staking.service.ts | 23 +- modules/pool/pool.prisma | 2 + .../migration.sql | 2 - .../migration.sql | 3 + prisma/prisma-client.ts | 13 ++ prisma/schema.prisma | 1 + 15 files changed, 156 insertions(+), 115 deletions(-) delete mode 100644 prisma/migrations/20230911145905_add_working_supplies/migration.sql create mode 100644 prisma/migrations/20230920134746_add_supplies_to_gauges/migration.sql diff --git a/modules/network/arbitrum.ts b/modules/network/arbitrum.ts index 04d0ad3ae..dfff5b7c7 100644 --- a/modules/network/arbitrum.ts +++ b/modules/network/arbitrum.ts @@ -194,7 +194,7 @@ export const arbitrumNetworkConfig: NetworkConfig = { new PhantomStableAprService(), new BoostedPoolAprService(), new SwapFeeAprService(arbitrumNetworkData.balancer.swapProtocolFeePercentage), - new GaugeAprService(gaugeSubgraphService, tokenService, [arbitrumNetworkData.bal!.address]), + new GaugeAprService(tokenService, [arbitrumNetworkData.bal!.address]), ], poolStakingServices: [new GaugeStakingService(gaugeSubgraphService, arbitrumNetworkData.bal!.address)], tokenPriceHandlers: [ diff --git a/modules/network/avalanche.ts b/modules/network/avalanche.ts index a4412e35f..13110719b 100644 --- a/modules/network/avalanche.ts +++ b/modules/network/avalanche.ts @@ -197,7 +197,7 @@ export const avalancheNetworkConfig: NetworkConfig = { new PhantomStableAprService(), new BoostedPoolAprService(), new SwapFeeAprService(avalancheNetworkData.balancer.swapProtocolFeePercentage), - new GaugeAprService(gaugeSubgraphService, tokenService, [avalancheNetworkData.bal!.address]), + new GaugeAprService(tokenService, [avalancheNetworkData.bal!.address]), ], poolStakingServices: [new GaugeStakingService(gaugeSubgraphService, avalancheNetworkData.bal!.address)], tokenPriceHandlers: [ diff --git a/modules/network/base.ts b/modules/network/base.ts index 5a51fd799..f16fa4414 100644 --- a/modules/network/base.ts +++ b/modules/network/base.ts @@ -122,7 +122,7 @@ export const baseNetworkConfig: NetworkConfig = { new IbTokensAprService(baseNetworkData.ibAprConfig), new BoostedPoolAprService(), new SwapFeeAprService(baseNetworkData.balancer.swapProtocolFeePercentage), - new GaugeAprService(gaugeSubgraphService, tokenService, [baseNetworkData.bal!.address]), + new GaugeAprService(tokenService, [baseNetworkData.bal!.address]), ], poolStakingServices: [new GaugeStakingService(gaugeSubgraphService, baseNetworkData.bal!.address)], tokenPriceHandlers: [ diff --git a/modules/network/gnosis.ts b/modules/network/gnosis.ts index fb807fff0..9d0fe7c41 100644 --- a/modules/network/gnosis.ts +++ b/modules/network/gnosis.ts @@ -147,7 +147,7 @@ export const gnosisNetworkConfig: NetworkConfig = { new PhantomStableAprService(), new BoostedPoolAprService(), new SwapFeeAprService(gnosisNetworkData.balancer.swapProtocolFeePercentage), - new GaugeAprService(gaugeSubgraphService, tokenService, [gnosisNetworkData.bal!.address]), + new GaugeAprService(tokenService, [gnosisNetworkData.bal!.address]), ], poolStakingServices: [new GaugeStakingService(gaugeSubgraphService, gnosisNetworkData.bal!.address)], tokenPriceHandlers: [ diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 440b9d9cc..1946a3d48 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -339,7 +339,7 @@ export const mainnetNetworkConfig: NetworkConfig = { new PhantomStableAprService(), new BoostedPoolAprService(), new SwapFeeAprService(mainnetNetworkData.balancer.swapProtocolFeePercentage), - new GaugeAprService(gaugeSubgraphService, tokenService, [mainnetNetworkData.bal!.address]), + new GaugeAprService(tokenService, [mainnetNetworkData.bal!.address]), ], poolStakingServices: [new GaugeStakingService(gaugeSubgraphService, mainnetNetworkData.bal!.address)], tokenPriceHandlers: [ diff --git a/modules/network/optimism.ts b/modules/network/optimism.ts index 8cb437ad8..0d39c0540 100644 --- a/modules/network/optimism.ts +++ b/modules/network/optimism.ts @@ -253,7 +253,7 @@ export const optimismNetworkConfig: NetworkConfig = { new PhantomStableAprService(), new BoostedPoolAprService(), new SwapFeeAprService(optimismNetworkData.balancer.swapProtocolFeePercentage), - new GaugeAprService(gaugeSubgraphService, tokenService, [ + new GaugeAprService(tokenService, [ optimismNetworkData.beets!.address, optimismNetworkData.bal!.address, ]), diff --git a/modules/network/polygon.ts b/modules/network/polygon.ts index 349462ad7..b85ac9857 100644 --- a/modules/network/polygon.ts +++ b/modules/network/polygon.ts @@ -251,7 +251,7 @@ export const polygonNetworkConfig: NetworkConfig = { new PhantomStableAprService(), new BoostedPoolAprService(), new SwapFeeAprService(polygonNetworkData.balancer.swapProtocolFeePercentage), - new GaugeAprService(gaugeSubgraphService, tokenService, [polygonNetworkData.bal!.address]), + new GaugeAprService(tokenService, [polygonNetworkData.bal!.address]), ], poolStakingServices: [new GaugeStakingService(gaugeSubgraphService, polygonNetworkData.bal!.address)], tokenPriceHandlers: [ diff --git a/modules/network/zkevm.ts b/modules/network/zkevm.ts index 08efcc313..0b5274b7d 100644 --- a/modules/network/zkevm.ts +++ b/modules/network/zkevm.ts @@ -159,7 +159,7 @@ export const zkevmNetworkConfig: NetworkConfig = { new PhantomStableAprService(), new BoostedPoolAprService(), new SwapFeeAprService(zkevmNetworkData.balancer.swapProtocolFeePercentage), - new GaugeAprService(gaugeSubgraphService, tokenService, [zkevmNetworkData.bal!.address]), + new GaugeAprService(tokenService, [zkevmNetworkData.bal!.address]), ], poolStakingServices: [new GaugeStakingService(gaugeSubgraphService, zkevmNetworkData.bal!.address)], tokenPriceHandlers: [ 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 f6e78431e..c09a0968d 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 @@ -1,18 +1,23 @@ +/** + * This service calculates the APR for a pool based on the gauge rewards + * + * Definitions: + * The “working supply” of the gauge - the effective total LP token amount after all deposits have been boosted. + * "Working balance" is 40% of a user balance in a gauge - used only for BAL rewards on v2 gauges on child gauges or on mainnet + */ import { PrismaPoolWithTokens } from '../../../../prisma/prisma-types'; import { PoolAprService } from '../../pool-types'; import { TokenService } from '../../../token/token.service'; import { secondsPerYear } from '../../../common/time'; -import { PrismaPoolAprItem, PrismaPoolAprRange, PrismaPoolAprType } from '@prisma/client'; +import { PrismaPoolAprItem, PrismaPoolAprRange, PrismaPoolAprType, PrismaPoolStaking, PrismaPoolStakingGauge, PrismaPoolStakingGaugeReward } from '@prisma/client'; import { prisma } from '../../../../prisma/prisma-client'; import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; import { networkContext } from '../../../network/network-context.service'; -import { GaugeSubgraphService } from '../../../subgraphs/gauge-subgraph/gauge-subgraph.service'; export class GaugeAprService implements PoolAprService { private readonly MAX_VEBAL_BOOST = 2.5; constructor( - private readonly gaugeSubgraphService: GaugeSubgraphService, private readonly tokenService: TokenService, private readonly primaryTokens: string[], ) {} @@ -23,145 +28,149 @@ export class GaugeAprService implements PoolAprService { public async updateAprForPools(pools: PrismaPoolWithTokens[]): Promise { const operations: any[] = []; - const gauges = await this.gaugeSubgraphService.getAllGaugesWithStatus(); - const tokenPrices = await this.tokenService.getTokenPrices(); - const poolsExpanded = await prisma.prismaPool.findMany({ - where: { chain: networkContext.chain, id: { in: pools.map((pool) => pool.id) } }, + // Get the data + const tokenPrices = await this.tokenService.getTokenPrices(); + const stakings = await prisma.prismaPoolStaking.findMany({ + where: { + poolId: { in: pools.map((pool) => pool.id) }, + type: 'GAUGE', + chain: networkContext.chain, + gauge: { + status: 'PREFERRED' + } + }, include: { - dynamicData: true, - staking: { + gauge: { include: { - gauge: { - include: { - rewards: true, - }, - }, + rewards: true, + } + }, + pool: { + include: { + dynamicData: true, }, }, }, }); - for (const pool of poolsExpanded) { - let gauge; - let preferredStaking; - for (const stake of pool.staking) { - if (stake.gauge?.status === 'PREFERRED') { - preferredStaking = stake; - gauge = gauges.find( - (subgraphGauge) => - subgraphGauge.address === stake.gauge?.gaugeAddress && stake.gauge?.status === 'PREFERRED', - ); - } - } - if (!gauge || !pool.dynamicData || !preferredStaking?.gauge) { + for (const stake of stakings) { + const { pool, gauge } = stake; + + if (!gauge || !pool.dynamicData || !gauge || !gauge.rewards) { continue; } + + // Get token rewards per year with data needed for the DB + const rewards = await Promise.allSettled( + gauge.rewards.map(async ({ tokenAddress, rewardPerSecond }) => { + const price = this.tokenService.getPriceForToken(tokenPrices, tokenAddress); + if (!price) { + return Promise.reject('Price not found'); + } + + let definition; + try { + definition = await prisma.prismaToken.findUniqueOrThrow({ + where: { address_chain: { address: tokenAddress, chain: networkContext.chain } }, + }); + } catch (e) { + //we don't have the reward token added as a token, only happens for testing tokens + return Promise.reject('Definition not found'); + } + + return { + address: tokenAddress, + symbol: definition.symbol, + rewardPerYear: parseFloat(rewardPerSecond) * secondsPerYear * price, + } + }) + ); + + // Calculate APRs const totalShares = parseFloat(pool.dynamicData.totalShares); + const bptPrice = pool.dynamicData.totalLiquidity / totalShares; const gaugeTvl = - totalShares > 0 ? (parseFloat(gauge.totalSupply) / totalShares) * pool.dynamicData.totalLiquidity : 0; - - let thirdPartyApr = 0; - - for (let rewardToken of preferredStaking.gauge.rewards) { - const tokenAddress = rewardToken.tokenAddress; - let rewardTokenDefinition; - try { - rewardTokenDefinition = await prisma.prismaToken.findUniqueOrThrow({ - where: { address_chain: { address: tokenAddress, chain: networkContext.chain } }, - }); - } catch (e) { - //we don't have the reward token added as a token, only happens for testing tokens - continue; - } - const tokenPrice = this.tokenService.getPriceForToken(tokenPrices, tokenAddress) || 0.1; - const rewardTokenPerYear = parseFloat(rewardToken.rewardPerSecond) * secondsPerYear; - const rewardTokenValuePerYear = tokenPrice * rewardTokenPerYear; - let rewardApr = gaugeTvl > 0 ? rewardTokenValuePerYear / gaugeTvl : 0; - - const isThirdPartyApr = !this.primaryTokens.includes(tokenAddress.toLowerCase()); - if (isThirdPartyApr) { - thirdPartyApr += rewardApr; + totalShares > 0 ? parseFloat(gauge.totalSupply) * bptPrice : 0; + const workingSupply = parseFloat(gauge.workingSupply); + const workingSupplyTvl = ((workingSupply + 0.4) / 0.4) * bptPrice; + + const aprItems = rewards.map((reward) => { + if (reward.status === 'rejected') { + return null; } - // apply vebal boost for BAL rewards on v2 gauges on child changes or on mainnet + const { address, symbol, rewardPerYear } = reward.value; + + const itemData: PrismaPoolAprItem = { + id: `${pool.id}-${symbol}-apr`, + chain: networkContext.chain, + poolId: pool.id, + title: `${symbol} reward APR`, + group: null, + apr: 0, + type: this.primaryTokens.includes(address.toLowerCase()) ? PrismaPoolAprType.NATIVE_REWARD : PrismaPoolAprType.THIRD_PARTY_REWARD, + }; + + // veBAL rewards have a range associated with the item if ( - rewardToken.tokenAddress.toLowerCase() === networkContext.data.bal!.address.toLowerCase() && - (preferredStaking.gauge.version === 2 || networkContext.chain === 'MAINNET') + address.toLowerCase() === networkContext.data.bal!.address.toLowerCase() && + (networkContext.chain === 'MAINNET' || gauge.version === 2) ) { - const aprItemId = `${pool.id}-${rewardTokenDefinition.symbol}-apr`; - const aprRangeId = `${pool.id}-bal-apr-range`; + let minApr = 0; - // 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 = rewardTokenValuePerYear / workingSupplyUsd; + minApr = rewardPerYear / workingSupplyTvl; } - const itemData = { - id: aprItemId, - chain: networkContext.chain, - poolId: pool.id, - title: `${rewardTokenDefinition.symbol} reward APR`, - apr: balApr, - type: PrismaPoolAprType.NATIVE_REWARD, - group: null, - }; + const aprRangeId = `${pool.id}-bal-apr-range`; + + itemData.apr = minApr; + // TODO: Is this needed? Are there any other use-cases besides veBAL? If not, maybe we can remove this and just use the itemData const rangeData = { id: aprRangeId, chain: networkContext.chain, - aprItemId: aprItemId, - min: balApr, - max: balApr * this.MAX_VEBAL_BOOST, + aprItemId: itemData.id, + min: minApr, + max: minApr * this.MAX_VEBAL_BOOST, }; - operations.push( - prisma.prismaPoolAprItem.upsert({ - where: { - id_chain: { - id: aprItemId, - chain: networkContext.chain, - }, - }, - update: itemData, - create: itemData, - }), - ); + return [itemData, rangeData]; + } else { + itemData.apr = gaugeTvl > 0 ? rewardPerYear / gaugeTvl : 0; + + return itemData; + } + }).flat().filter((apr): apr is PrismaPoolAprItem | PrismaPoolAprRange => apr !== null); + + // Prepare DB operations + for (const item of aprItems) { + if (item.id.includes('apr-range')) { operations.push( prisma.prismaPoolAprRange.upsert({ where: { - id_chain: { id: aprRangeId, chain: networkContext.chain }, + id_chain: { id: item.id, chain: networkContext.chain }, }, - update: rangeData, - create: rangeData, + update: item, + create: item as PrismaPoolAprRange, }), - ); + ); } else { - const item: PrismaPoolAprItem = { - id: `${pool.id}-${rewardTokenDefinition.symbol}-apr`, - chain: networkContext.chain, - poolId: pool.id, - title: `${rewardTokenDefinition.symbol} reward APR`, - apr: rewardApr, - type: isThirdPartyApr ? PrismaPoolAprType.THIRD_PARTY_REWARD : PrismaPoolAprType.NATIVE_REWARD, - group: null, - }; operations.push( prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: item.id, chain: networkContext.chain } }, + where: { + id_chain: { id: item.id, chain: networkContext.chain }, + }, update: item, - create: item, + create: item as PrismaPoolAprItem, }), ); } } } + await prismaBulkExecuteOperations(operations, true); } } diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 3c5c33e93..59d6b0275 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -30,6 +30,8 @@ interface GaugeRate { /** BAL per second received by the gauge */ rate: string; // Amount of tokens staked in the gauge + totalSupply: string; + // Effective total LP token amount after all deposits have been boosted workingSupply: string; } @@ -49,6 +51,7 @@ interface GaugeBalDistributionData { rate?: BigNumber, weight?: BigNumber, workingSupply?: BigNumber + totalSupply?: BigNumber } } @@ -62,6 +65,7 @@ export class GaugeStakingService implements PoolStakingService { this.balAddress = balAddress.toLowerCase(); this.balMulticaller = new Multicaller3([ + ...childChainGaugeV2Abi.filter((abi) => abi.name === 'totalSupply'), ...childChainGaugeV2Abi.filter((abi) => abi.name === 'working_supply'), ...childChainGaugeV2Abi.filter((abi) => abi.name === 'inflation_rate'), gaugeControllerAbi.find((abi) => abi.name === 'gauge_relative_weight'), @@ -152,11 +156,13 @@ export class GaugeStakingService implements PoolStakingService { status: gauge.status, version: gauge.version, workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id)?.workingSupply, + totalSupply: onchainRates.find(({ id }) => id.includes(gauge.id))?.totalSupply, }, update: { status: gauge.status, version: gauge.version, workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id)?.workingSupply, + totalSupply: onchainRates.find(({ id }) => id.includes(gauge.id))?.totalSupply, }, }), ); @@ -198,6 +204,7 @@ export class GaugeStakingService implements PoolStakingService { // Get onchain data for BAL rewards const currentWeek = Math.floor(Date.now() / 1000 / 604800); for (const gauge of gauges) { + this.balMulticaller.call(`${gauge.id}.totalSupply`, gauge.id, 'totalSupply', [], true); if (gauge.version === 2) { this.balMulticaller.call(`${gauge.id}.rate`, gauge.id, 'inflation_rate', [currentWeek], true); this.balMulticaller.call(`${gauge.id}.workingSupply`, gauge.id, 'working_supply', [], true); @@ -230,7 +237,7 @@ export class GaugeStakingService implements PoolStakingService { const onchainRates = [ ...Object.keys(balData).map((gaugeAddress) => { const id = `${this.balAddress}-${gaugeAddress}`.toLowerCase(); - const { rate, weight, workingSupply } = balData[gaugeAddress]; + const { rate, weight, workingSupply, totalSupply } = balData[gaugeAddress]; const rewardPerSecond = rate ? formatUnits(rate) // L2 V2 case : weight ? (parseFloat(formatUnits(weight!)) * totalBalRate).toFixed(18) // mainnet case @@ -240,6 +247,7 @@ export class GaugeStakingService implements PoolStakingService { id, rewardPerSecond, workingSupply: workingSupply ? formatUnits(workingSupply) : '0', + totalSupply: totalSupply ? formatUnits(totalSupply) : '0', } }), ...Object.keys(rewardsData).map((gaugeAddress) => [ // L2 V1 case, includes tokens other than BAL @@ -247,14 +255,21 @@ export class GaugeStakingService implements PoolStakingService { const id = `${tokenAddress}-${gaugeAddress}`.toLowerCase(); const { rate, period_finish } = rewardsData[gaugeAddress].rewardData[tokenAddress]; const rewardPerSecond = (period_finish && period_finish.toNumber() > now) ? formatUnits(rate!) : '0.0'; + const { totalSupply } = balData[gaugeAddress]; - return { id, rewardPerSecond, workingSupply: '0' }; + return { + id, + rewardPerSecond, + workingSupply: '0', + totalSupply: totalSupply ? formatUnits(totalSupply) : '0' + }; }), ]).flat(), ].filter(({ rewardPerSecond }) => parseFloat(rewardPerSecond) > 0) as { - id: string, - rewardPerSecond: string, + id: string + rewardPerSecond: string workingSupply: string + totalSupply: string }[]; return onchainRates; diff --git a/modules/pool/pool.prisma b/modules/pool/pool.prisma index 6fb00ce62..dacfc2c19 100644 --- a/modules/pool/pool.prisma +++ b/modules/pool/pool.prisma @@ -424,6 +424,8 @@ model PrismaPoolStakingGauge { rewards PrismaPoolStakingGaugeReward[] status PrismaPoolStakingGaugeStatus @default(ACTIVE) version Int @default(1) + workingSupply String @default("0.0") + totalSupply String @default("0.0") } enum PrismaPoolStakingGaugeStatus { diff --git a/prisma/migrations/20230911145905_add_working_supplies/migration.sql b/prisma/migrations/20230911145905_add_working_supplies/migration.sql deleted file mode 100644 index c922dca4b..000000000 --- a/prisma/migrations/20230911145905_add_working_supplies/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "PrismaPoolStakingGauge" ADD COLUMN "workingSupply" TEXT NOT NULL DEFAULT '0.0'; diff --git a/prisma/migrations/20230920134746_add_supplies_to_gauges/migration.sql b/prisma/migrations/20230920134746_add_supplies_to_gauges/migration.sql new file mode 100644 index 000000000..4d5c0f07a --- /dev/null +++ b/prisma/migrations/20230920134746_add_supplies_to_gauges/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "PrismaPoolStakingGauge" ADD COLUMN "totalSupply" TEXT NOT NULL DEFAULT '0.0', +ADD COLUMN "workingSupply" TEXT NOT NULL DEFAULT '0.0'; diff --git a/prisma/prisma-client.ts b/prisma/prisma-client.ts index 6c9790473..13652ad9f 100644 --- a/prisma/prisma-client.ts +++ b/prisma/prisma-client.ts @@ -2,6 +2,19 @@ import { PrismaClient } from '@prisma/client'; export let prisma = new PrismaClient(); +// Debugging query times +// export let prisma = new PrismaClient({ +// log: ['query'], +// }); + +// prisma.$use(async (params, next) => { +// const before = Date.now() +// const result = await next(params) +// const after = Date.now() +// console.log(`Query ${params.model}.${params.action} took ${after - before}ms`) +// return result +// }) + export function setPrisma(prismaClient: PrismaClient) { prisma = prismaClient; } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bace27203..56fece9a8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -471,6 +471,7 @@ model PrismaPoolStakingGauge { status PrismaPoolStakingGaugeStatus @default(ACTIVE) version Int @default(1) workingSupply String @default("0.0") + totalSupply String @default("0.0") } enum PrismaPoolStakingGaugeStatus { From b4bb4891ed0a3e1136e0fa6b364cabdd6a0461c9 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:45:03 +0200 Subject: [PATCH 07/24] fix: gauge mock --- modules/vebal/prismaPoolStakingGauge.mock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/vebal/prismaPoolStakingGauge.mock.ts b/modules/vebal/prismaPoolStakingGauge.mock.ts index 019a10925..9d472c4ee 100644 --- a/modules/vebal/prismaPoolStakingGauge.mock.ts +++ b/modules/vebal/prismaPoolStakingGauge.mock.ts @@ -12,6 +12,7 @@ export function aPrismaPoolStakingGauge(...options: Partial Date: Wed, 20 Sep 2023 17:42:06 +0200 Subject: [PATCH 08/24] tokenAdmin.getInflationRate --- modules/network/data/mainnet.ts | 316 ++++++++++++++ modules/network/mainnet.ts | 317 +------------- modules/network/network-config-types.ts | 1 + .../pool/lib/staking/gauge-staking.service.ts | 9 +- modules/vebal/abi/balancerTokenAdmin.json | 397 ++++++++++++++++++ modules/vebal/balancer-token-admin.service.ts | 14 + 6 files changed, 736 insertions(+), 318 deletions(-) create mode 100644 modules/network/data/mainnet.ts create mode 100644 modules/vebal/abi/balancerTokenAdmin.json create mode 100644 modules/vebal/balancer-token-admin.service.ts diff --git a/modules/network/data/mainnet.ts b/modules/network/data/mainnet.ts new file mode 100644 index 000000000..7ca73f8ce --- /dev/null +++ b/modules/network/data/mainnet.ts @@ -0,0 +1,316 @@ +import { BigNumber } from '@ethersproject/bignumber'; +import { NetworkData } from "../network-config-types"; +import { env } from '../../../app/env'; + +const underlyingTokens = { + USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7', + DAI: '0x6b175474e89094c44da98b954eedeac495271d0f', + wETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', +}; + +export const mainnetNetworkData: NetworkData = { + chain: { + slug: 'ethereum', + id: 1, + nativeAssetAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + wrappedNativeAssetAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + prismaId: 'MAINNET', + gqlId: 'MAINNET', + }, + subgraphs: { + startDate: '2019-04-20', + balancer: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', + beetsBar: 'https://', + blocks: 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks', + gauge: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + veBalLocks: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + userBalances: 'https://', + }, + eth: { + address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + addressFormatted: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + symbol: 'ETH', + name: 'Ether', + }, + weth: { + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + addressFormatted: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + }, + coingecko: { + nativeAssetId: 'ethereum', + platformId: 'ethereum', + excludedTokenAddresses: [], + }, + tokenPrices: { + maxHourlyPriceHistoryNumDays: 100, + }, + rpcUrl: env.INFURA_API_KEY ? `https://mainnet.infura.io/v3/${env.INFURA_API_KEY}` : 'https://eth.llamarpc.com', + rpcMaxBlockRange: 700, + protocolToken: 'bal', + bal: { + address: '0xba100000625a3754423978a60c9317c58a424e3d', + }, + veBal: { + address: '0xc128a9954e6c874ea3d62ce62b468ba073093f25', + delegationProxy: '0x0000000000000000000000000000000000000000', + }, + gaugeControllerAddress: '0xc128468b7ce63ea702c1f104d55a2566b13d3abd', + gaugeControllerHelperAddress: '0x8e5698dc4897dc12243c8642e77b4f21349db97c', + balancer: { + vault: '0xba12222222228d8ba445958a75a0704d566bf2c8', + tokenAdmin: '0xf302f9f50958c5593770fdf4d4812309ff77414f', + composableStablePoolFactories: [ + '0xf9ac7b9df2b3454e841110cce5550bd5ac6f875f', + '0x85a80afee867adf27b50bdb7b76da70f1e853062', + '0xdba127fbc23fb20f5929c546af220a991b5c6e01', + '0xfada0f4547ab2de89d1304a668c39b3e09aa7c76', + '0xdb8d758bcb971e482b2c45f7f8a7740283a1bd3a', + '0xba1b4a90bad57470a2cba762a32955dc491f76e0', + ], + weightedPoolV2Factories: [ + '0xcc508a455f5b0073973107db6a878ddbdab957bc', + '0x5dd94da3644ddd055fcf6b3e1aa310bb7801eb8b', + '0x897888115ada5773e02aa29f775430bfb5f34c51', + ], + swapProtocolFeePercentage: 0.5, + yieldProtocolFeePercentage: 0.5, + excludedPoolDataQueryPoolIds: ['0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e'], + }, + multicall: '0x5ba1e12693dc8f9c48aad8770482f4739beed696', + multicall3: '0xca11bde05977b3631167028862be2a173976ca11', + avgBlockSpeed: 10, + sor: { + main: { + url: 'https://uu6cfghhd5lqa7py3nojxkivd40zuugb.lambda-url.ca-central-1.on.aws/', + maxPools: 8, + forceRefresh: false, + gasPrice: BigNumber.from(10), + swapGas: BigNumber.from('1000000'), + }, + canary: { + url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', + maxPools: 8, + forceRefresh: false, + gasPrice: BigNumber.from(10), + swapGas: BigNumber.from('1000000'), + }, + }, + ibAprConfig: { + aave: { + v2: { + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2', + tokens: { + USDC: { + underlyingAssetAddress: underlyingTokens.USDC, + aTokenAddress: '0xbcca60bb61934080951369a648fb03df4f96263c', + wrappedTokens: { + waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', + }, + }, + USDT: { + underlyingAssetAddress: underlyingTokens.USDT, + aTokenAddress: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', + wrappedTokens: { + waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', + }, + }, + DAI: { + underlyingAssetAddress: underlyingTokens.DAI, + aTokenAddress: '0x028171bca77440897b824ca71d1c56cac55b68a3', + wrappedTokens: { + waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', + }, + }, + }, + }, + v3: { + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3', + tokens: { + USDC: { + underlyingAssetAddress: underlyingTokens.USDC, + aTokenAddress: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', + wrappedTokens: { + waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c', + stataEthUSDC: '0x02c2d189b45ce213a40097b62d311cf0dd16ec92', + }, + }, + USDT: { + underlyingAssetAddress: underlyingTokens.USDT, + aTokenAddress: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a', + wrappedTokens: { + waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455', + stataEthUSDT: '0x65799b9fd4206cdaa4a1db79254fcbc2fd2ffee6', + }, + }, + DAI: { + underlyingAssetAddress: underlyingTokens.DAI, + aTokenAddress: '0x018008bfb33d285247a21d44e50697654f754e63', + wrappedTokens: { + waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', + stataEthDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', + }, + }, + wETH: { + underlyingAssetAddress: underlyingTokens.wETH, + aTokenAddress: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8', + wrappedTokens: { + waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6', + stataEthWETH: '0x03928473f25bb2da6bc880b07ecbadc636822264', + }, + }, + }, + }, + }, + ankr: { + sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', + tokens: { + ankrETH: { + address: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb', + serviceName: 'eth', + isIbYield: true, + }, + }, + }, + euler: { + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/euler-xyz/euler-mainnet', + tokens: { + eUSDC: { address: '0xeb91861f8a4e1c12333f42dce8fb0ecdc28da716' }, + eDAI: { address: '0xe025e3ca2be02316033184551d4d3aa22024d9dc' }, + eUSDT: { address: '0x4d19f33948b99800b6113ff3e83bec9b537c85d2' }, + eFRAX: { address: '0x5484451a88a35cd0878a1be177435ca8a0e4054e' }, + }, + }, + gearbox: { + sourceUrl: 'https://mainnet.gearbox.foundation/api/pools', + tokens: { + dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, + dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, + }, + }, + idle: { + sourceUrl: 'https://api.idle.finance/junior-rates/', + authorizationHeader: + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', + tokens: { + idleDAI: { + address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', + wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', + }, + idleUSDC: { + address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', + wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', + }, + idleUSDT: { + address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', + wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', + }, + }, + }, + tessera: { + tokens: { + sAPE: { + tesseraPoolAddress: '0x5954ab967bc958940b7eb73ee84797dc8a2afbb9', + tokenAddress: '0x7966c5bae631294d7cffcea5430b78c2f76db6fa', + }, + }, + }, + tranchess: { + sourceUrl: 'https://tranchess.com/eth/api/v3/funds', + tokens: { + qETH: { + address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', + underlyingAssetName: 'WETH', + }, + }, + }, + defaultHandlers: { + vETH: { + tokenAddress: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', + sourceUrl: 'https://apy.liebi.com/veth', + path: 'veth', + }, + stETH: { + tokenAddress: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + wstETH: { + tokenAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + cbETH: { + tokenAddress: '0xbe9895146f7af43049ca1c1ae358b0541ea49704', + sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', + path: 'apy', + scale: 1, + isIbYield: true, + }, + sfrxETH: { + tokenAddress: '0xac3e018457b222d93114458476f3e3416abbe38f', + sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', + path: 'sfrxethApr', + isIbYield: true, + }, + StaFirETH: { + tokenAddress: '0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593', + sourceUrl: 'https://drop-api.stafi.io/reth/v1/poolData', + path: 'data.stakeApr', + isIbYield: true, + }, + rETH: { + tokenAddress: '0xae78736cd615f374d3085123a210448e74fc6393', + sourceUrl: 'https://rocketpool.net/api/mainnet/payload', + path: 'rethAPR', + isIbYield: true, + }, + USDR: { + tokenAddress: '0xaf0d9d65fc54de245cda37af3d18cbec860a4d4b', + sourceUrl: 'http://usdr-api.us-east-1.elasticbeanstalk.com/usdr/apy', + path: 'usdr', + isIbYield: true, + }, + swETH: { + tokenAddress: '0xf951e335afb289353dc249e82926178eac7ded78', + sourceUrl: 'https://v3.svc.swellnetwork.io/api/tokens/sweth/apr', + isIbYield: true, + }, + wjAURA: { + tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', + sourceUrl: 'https://data.jonesdao.io/api/v1/jones/apy-wjaura', + path: 'wjauraApy', + isIbYield: true, + }, + }, + }, + beefy: { + linearPools: [''], + }, + datastudio: { + main: { + user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', + sheetId: '11anHUEb9snGwvB-errb5HvO8TvoLTRJhkDdD80Gxw1Q', + databaseTabName: 'Database v2', + compositionTabName: 'Pool Composition v2', + emissionDataTabName: 'EmissionData', + }, + canary: { + user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', + sheetId: '1HnJOuRQXGy06tNgqjYMzQNIsaCSCC01Yxe_lZhXBDpY', + databaseTabName: 'Database v2', + compositionTabName: 'Pool Composition v2', + emissionDataTabName: 'EmissionData', + }, + }, + monitoring: { + main: { + alarmTopicArn: 'arn:aws:sns:ca-central-1:118697801881:api_alarms', + }, + canary: { + alarmTopicArn: 'arn:aws:sns:eu-central-1:118697801881:api_alarms', + }, + }, +}; diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 1946a3d48..22c36dd55 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -1,5 +1,5 @@ -import { BigNumber, ethers } from 'ethers'; -import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; +import { ethers } from 'ethers'; +import { DeploymentEnv, NetworkConfig } from './network-config-types'; import { tokenService } from '../token/token.service'; import { PhantomStableAprService } from '../pool/lib/apr-data-sources/phantom-stable-apr.service'; import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/boosted-pool-apr.service'; @@ -16,320 +16,9 @@ import { gaugeSubgraphService } from '../subgraphs/gauge-subgraph/gauge-subgraph import { coingeckoService } from '../coingecko/coingecko.service'; import { CoingeckoPriceHandlerService } from '../token/lib/token-price-handlers/coingecko-price-handler.service'; import { IbTokensAprService } from '../pool/lib/apr-data-sources/ib-tokens-apr.service'; +import { mainnetNetworkData } from './data/mainnet'; import { env } from '../../app/env'; -const underlyingTokens = { - USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7', - DAI: '0x6b175474e89094c44da98b954eedeac495271d0f', - wETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', -}; - -export const mainnetNetworkData: NetworkData = { - chain: { - slug: 'ethereum', - id: 1, - nativeAssetAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - wrappedNativeAssetAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - prismaId: 'MAINNET', - gqlId: 'MAINNET', - }, - subgraphs: { - startDate: '2019-04-20', - balancer: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', - beetsBar: 'https://', - blocks: 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks', - gauge: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', - veBalLocks: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', - userBalances: 'https://', - }, - eth: { - address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - addressFormatted: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - symbol: 'ETH', - name: 'Ether', - }, - weth: { - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - addressFormatted: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - }, - coingecko: { - nativeAssetId: 'ethereum', - platformId: 'ethereum', - excludedTokenAddresses: [], - }, - tokenPrices: { - maxHourlyPriceHistoryNumDays: 100, - }, - rpcUrl: env.INFURA_API_KEY ? `https://mainnet.infura.io/v3/${env.INFURA_API_KEY}` : 'https://eth.llamarpc.com', - rpcMaxBlockRange: 700, - protocolToken: 'bal', - bal: { - address: '0xba100000625a3754423978a60c9317c58a424e3d', - }, - veBal: { - address: '0xc128a9954e6c874ea3d62ce62b468ba073093f25', - delegationProxy: '0x0000000000000000000000000000000000000000', - }, - gaugeControllerAddress: '0xc128468b7ce63ea702c1f104d55a2566b13d3abd', - gaugeControllerHelperAddress: '0x8e5698dc4897dc12243c8642e77b4f21349db97c', - balancer: { - vault: '0xba12222222228d8ba445958a75a0704d566bf2c8', - composableStablePoolFactories: [ - '0xf9ac7b9df2b3454e841110cce5550bd5ac6f875f', - '0x85a80afee867adf27b50bdb7b76da70f1e853062', - '0xdba127fbc23fb20f5929c546af220a991b5c6e01', - '0xfada0f4547ab2de89d1304a668c39b3e09aa7c76', - '0xdb8d758bcb971e482b2c45f7f8a7740283a1bd3a', - '0xba1b4a90bad57470a2cba762a32955dc491f76e0', - ], - weightedPoolV2Factories: [ - '0xcc508a455f5b0073973107db6a878ddbdab957bc', - '0x5dd94da3644ddd055fcf6b3e1aa310bb7801eb8b', - '0x897888115ada5773e02aa29f775430bfb5f34c51', - ], - swapProtocolFeePercentage: 0.5, - yieldProtocolFeePercentage: 0.5, - excludedPoolDataQueryPoolIds: ['0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e'], - }, - multicall: '0x5ba1e12693dc8f9c48aad8770482f4739beed696', - multicall3: '0xca11bde05977b3631167028862be2a173976ca11', - avgBlockSpeed: 10, - sor: { - main: { - url: 'https://uu6cfghhd5lqa7py3nojxkivd40zuugb.lambda-url.ca-central-1.on.aws/', - maxPools: 8, - forceRefresh: false, - gasPrice: BigNumber.from(10), - swapGas: BigNumber.from('1000000'), - }, - canary: { - url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', - maxPools: 8, - forceRefresh: false, - gasPrice: BigNumber.from(10), - swapGas: BigNumber.from('1000000'), - }, - }, - ibAprConfig: { - aave: { - v2: { - subgraphUrl: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2', - tokens: { - USDC: { - underlyingAssetAddress: underlyingTokens.USDC, - aTokenAddress: '0xbcca60bb61934080951369a648fb03df4f96263c', - wrappedTokens: { - waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', - }, - }, - USDT: { - underlyingAssetAddress: underlyingTokens.USDT, - aTokenAddress: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', - wrappedTokens: { - waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', - }, - }, - DAI: { - underlyingAssetAddress: underlyingTokens.DAI, - aTokenAddress: '0x028171bca77440897b824ca71d1c56cac55b68a3', - wrappedTokens: { - waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', - }, - }, - }, - }, - v3: { - subgraphUrl: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3', - tokens: { - USDC: { - underlyingAssetAddress: underlyingTokens.USDC, - aTokenAddress: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', - wrappedTokens: { - waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c', - stataEthUSDC: '0x02c2d189b45ce213a40097b62d311cf0dd16ec92', - }, - }, - USDT: { - underlyingAssetAddress: underlyingTokens.USDT, - aTokenAddress: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a', - wrappedTokens: { - waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455', - stataEthUSDT: '0x65799b9fd4206cdaa4a1db79254fcbc2fd2ffee6', - }, - }, - DAI: { - underlyingAssetAddress: underlyingTokens.DAI, - aTokenAddress: '0x018008bfb33d285247a21d44e50697654f754e63', - wrappedTokens: { - waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', - stataEthDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', - }, - }, - wETH: { - underlyingAssetAddress: underlyingTokens.wETH, - aTokenAddress: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8', - wrappedTokens: { - waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6', - stataEthWETH: '0x03928473f25bb2da6bc880b07ecbadc636822264', - }, - }, - }, - }, - }, - ankr: { - sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', - tokens: { - ankrETH: { - address: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb', - serviceName: 'eth', - isIbYield: true, - }, - }, - }, - euler: { - subgraphUrl: 'https://api.thegraph.com/subgraphs/name/euler-xyz/euler-mainnet', - tokens: { - eUSDC: { address: '0xeb91861f8a4e1c12333f42dce8fb0ecdc28da716' }, - eDAI: { address: '0xe025e3ca2be02316033184551d4d3aa22024d9dc' }, - eUSDT: { address: '0x4d19f33948b99800b6113ff3e83bec9b537c85d2' }, - eFRAX: { address: '0x5484451a88a35cd0878a1be177435ca8a0e4054e' }, - }, - }, - gearbox: { - sourceUrl: 'https://mainnet.gearbox.foundation/api/pools', - tokens: { - dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, - dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, - }, - }, - idle: { - sourceUrl: 'https://api.idle.finance/junior-rates/', - authorizationHeader: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', - tokens: { - idleDAI: { - address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', - wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', - }, - idleUSDC: { - address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', - wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', - }, - idleUSDT: { - address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', - wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', - }, - }, - }, - tessera: { - tokens: { - sAPE: { - tesseraPoolAddress: '0x5954ab967bc958940b7eb73ee84797dc8a2afbb9', - tokenAddress: '0x7966c5bae631294d7cffcea5430b78c2f76db6fa', - }, - }, - }, - tranchess: { - sourceUrl: 'https://tranchess.com/eth/api/v3/funds', - tokens: { - qETH: { - address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', - underlyingAssetName: 'WETH', - }, - }, - }, - defaultHandlers: { - vETH: { - tokenAddress: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', - sourceUrl: 'https://apy.liebi.com/veth', - path: 'veth', - }, - stETH: { - tokenAddress: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - wstETH: { - tokenAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - cbETH: { - tokenAddress: '0xbe9895146f7af43049ca1c1ae358b0541ea49704', - sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', - path: 'apy', - scale: 1, - isIbYield: true, - }, - sfrxETH: { - tokenAddress: '0xac3e018457b222d93114458476f3e3416abbe38f', - sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', - path: 'sfrxethApr', - isIbYield: true, - }, - StaFirETH: { - tokenAddress: '0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593', - sourceUrl: 'https://drop-api.stafi.io/reth/v1/poolData', - path: 'data.stakeApr', - isIbYield: true, - }, - rETH: { - tokenAddress: '0xae78736cd615f374d3085123a210448e74fc6393', - sourceUrl: 'https://rocketpool.net/api/mainnet/payload', - path: 'rethAPR', - isIbYield: true, - }, - USDR: { - tokenAddress: '0xaf0d9d65fc54de245cda37af3d18cbec860a4d4b', - sourceUrl: 'http://usdr-api.us-east-1.elasticbeanstalk.com/usdr/apy', - path: 'usdr', - isIbYield: true, - }, - swETH: { - tokenAddress: '0xf951e335afb289353dc249e82926178eac7ded78', - sourceUrl: 'https://v3.svc.swellnetwork.io/api/tokens/sweth/apr', - isIbYield: true, - }, - wjAURA: { - tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', - sourceUrl: 'https://data.jonesdao.io/api/v1/jones/apy-wjaura', - path: 'wjauraApy', - isIbYield: true, - }, - }, - }, - beefy: { - linearPools: [''], - }, - datastudio: { - main: { - user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', - sheetId: '11anHUEb9snGwvB-errb5HvO8TvoLTRJhkDdD80Gxw1Q', - databaseTabName: 'Database v2', - compositionTabName: 'Pool Composition v2', - emissionDataTabName: 'EmissionData', - }, - canary: { - user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', - sheetId: '1HnJOuRQXGy06tNgqjYMzQNIsaCSCC01Yxe_lZhXBDpY', - databaseTabName: 'Database v2', - compositionTabName: 'Pool Composition v2', - emissionDataTabName: 'EmissionData', - }, - }, - monitoring: { - main: { - alarmTopicArn: 'arn:aws:sns:ca-central-1:118697801881:api_alarms', - }, - canary: { - alarmTopicArn: 'arn:aws:sns:eu-central-1:118697801881:api_alarms', - }, - }, -}; - export const mainnetNetworkConfig: NetworkConfig = { data: mainnetNetworkData, contentService: new GithubContentService(), diff --git a/modules/network/network-config-types.ts b/modules/network/network-config-types.ts index f8c742b00..29c27374b 100644 --- a/modules/network/network-config-types.ts +++ b/modules/network/network-config-types.ts @@ -93,6 +93,7 @@ export interface NetworkData { gaugeControllerHelperAddress?: string; balancer: { vault: string; + tokenAdmin?: string; weightedPoolV2Factories: string[]; composableStablePoolFactories: string[]; yieldProtocolFeePercentage: number; diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 59d6b0275..e751b1b8f 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -1,11 +1,12 @@ /** - * Supports calculation of BAL rewards sent to gauges. Balancer setup has 3 types of gauges: + * Supports calculation of BAL and token rewards sent to gauges. + * Balancer has 3 types of gauges: * * 1. Mainnet gauges with working supply and relative weight * 2. Old L2 gauges with BAL rewards sent as a reward token * 3. New L2 gauges (aka child chain gauges) with direct BAL rewards through a streamer. * - * This service supports all 3 types of gauges and stores the BAL rewards in the DB as a reward token rate per second. + * Reward data is fetched onchain and stored in the DB as a token rate per second. */ import { PoolStakingService } from '../../pool-types'; import { prisma } from '../../../../prisma/prisma-client'; @@ -21,7 +22,7 @@ import { BigNumber } from '@ethersproject/bignumber'; import { formatUnits } from '@ethersproject/units'; import type { JsonFragment } from '@ethersproject/abi'; import { Multicaller3 } from '../../../web3/multicaller3'; -import * as emissions from './bal-emissions'; +import { getInflationRate } from '../../../vebal/balancer-token-admin.service'; import _ from 'lodash'; interface GaugeRate { @@ -230,7 +231,7 @@ export class GaugeStakingService implements PoolStakingService { const rewardsDataV2 = await this.rewardsMulticallerV2.execute() as GaugeRewardData; const rewardsData = { ...rewardsDataV1, ...rewardsDataV2 }; - const totalBalRate = emissions.weekly() / 604800; + const totalBalRate = parseFloat(formatUnits(await getInflationRate())); const now = Math.floor(Date.now() / 1000); // Format onchain rates for all the rewards diff --git a/modules/vebal/abi/balancerTokenAdmin.json b/modules/vebal/abi/balancerTokenAdmin.json new file mode 100644 index 000000000..2935970ce --- /dev/null +++ b/modules/vebal/abi/balancerTokenAdmin.json @@ -0,0 +1,397 @@ +[ + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "contract IBalancerToken", + "name": "balancerToken", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "rate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "supply", + "type": "uint256" + } + ], + "name": "MiningParametersUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "INITIAL_RATE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RATE_DENOMINATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RATE_REDUCTION_COEFFICIENT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RATE_REDUCTION_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "activate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "available_supply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "futureEpochTimeWrite", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "future_epoch_time_write", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "getActionId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAuthorizer", + "outputs": [ + { + "internalType": "contract IAuthorizer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAvailableSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBalancerToken", + "outputs": [ + { + "internalType": "contract IBalancerToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFutureEpochTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInflationRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMiningEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStartEpochSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStartEpochTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVault", + "outputs": [ + { + "internalType": "contract IVault", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "end", + "type": "uint256" + } + ], + "name": "mintableInTimeframe", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "end", + "type": "uint256" + } + ], + "name": "mintable_in_timeframe", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "snapshot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startEpochTimeWrite", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "start_epoch_time_write", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "updateMiningParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "update_mining_parameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/modules/vebal/balancer-token-admin.service.ts b/modules/vebal/balancer-token-admin.service.ts new file mode 100644 index 000000000..1a7b68102 --- /dev/null +++ b/modules/vebal/balancer-token-admin.service.ts @@ -0,0 +1,14 @@ +import { Contract } from '@ethersproject/contracts'; +import { BigNumber } from '@ethersproject/bignumber'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { mainnetNetworkData } from '../network/data/mainnet'; +import abi from './abi/balancerTokenAdmin.json'; + +const { balancer: { tokenAdmin: address }, rpcUrl } = mainnetNetworkData; + +export const getInflationRate = async (): Promise => { + const provider = new JsonRpcProvider(rpcUrl); + const tokenAdmin = new Contract(address!, abi, provider); + const inflationRate = await tokenAdmin.getInflationRate(); + return inflationRate; +} From fc1b79fa303baab602d51dad3dfe234585ff430f Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 21 Sep 2023 11:40:44 +0200 Subject: [PATCH 09/24] handle edge cases around preferential gauges --- .../pool/lib/staking/gauge-staking.service.ts | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index e751b1b8f..41f99a51b 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -93,13 +93,52 @@ export class GaugeStakingService implements PoolStakingService { .map((pool) => pool.gauges) .flat() .filter((gauge): gauge is LiquidityGauge => !!gauge); - + + // Handle preferential gauges edge cases where the preferential gauge is not set, but pool has gauges, + // or there are multiple preferential gauges set for a pool + // My eyes hurt from looking at this code, but it works + const poolIdsWithPreferentialGauges = subgraphPoolsWithGauges + .filter((pool) => !!pool.preferentialGauge) + .map((pool) => pool.poolId); + const poolIdsWithoutPreferentialGauge = subgraphPoolsWithGauges + .filter((pool) => !poolIdsWithPreferentialGauges.includes(pool.poolId)) + .map((pool) => pool.poolId); + const gaugesForPoolsWithoutPreferentialGauge = subgraphGauges + .filter((gauge) => poolIdsWithoutPreferentialGauge.includes(gauge.poolId)); + const preferentialIdsForPoolsWithoutPreferentialGauge = _ + .uniqBy(gaugesForPoolsWithoutPreferentialGauge, (gauge) => gauge.poolId) + .map((gauge) => gauge.id); + // Remove duplicated preferential gauges for pools that have multiple preferential gauges + const poolsWithMultiplePreferentialGauges = _.countBy( + subgraphGauges.filter((gauge) => gauge.isPreferentialGauge), + (gauge) => gauge.poolId + ); + const poolIdsWithMultiplePreferentialGauges = Object.keys(poolsWithMultiplePreferentialGauges) + .filter((poolId) => poolsWithMultiplePreferentialGauges[poolId] > 1); + const singlePreferentialIds = subgraphGauges + .filter((gauge) => gauge.isPreferentialGauge && !poolIdsWithMultiplePreferentialGauges.includes(gauge.poolId!)) + .map((gauge) => gauge.id); + const preferentialGaugesForPoolsWithMultiplePreferentialGauges = subgraphGauges + .filter((gauge) => poolIdsWithMultiplePreferentialGauges.includes(gauge.poolId!)); + const preferentialIdsForPoolsWithMultiplePreferentialGauges = _ + .uniqBy(preferentialGaugesForPoolsWithMultiplePreferentialGauges, (gauge) => gauge.poolId) + .map((gauge) => gauge.id); + const preferentialIds = [ + ...singlePreferentialIds, + ...preferentialIdsForPoolsWithoutPreferentialGauge, + ...preferentialIdsForPoolsWithMultiplePreferentialGauges, + ]; + const dbGauges = subgraphGauges .map((gauge) => ({ id: gauge.id, - poolId: gauge.poolId, + poolId: gauge.poolId!, // we need to set the status based on the preferentialGauge entity on the gaugePool. If it's set there, it's preferential, otherwise it's active (or killed) - status: gauge.isKilled ? 'KILLED' : gauge.isPreferentialGauge ? 'PREFERRED' : 'ACTIVE' as LiquidityGaugeStatus, + status: gauge.isKilled + ? 'KILLED' + : preferentialIds.includes(gauge.id) + ? 'PREFERRED' + : 'ACTIVE' as LiquidityGaugeStatus, version: gauge.streamer ? 1 : 2 as 1 | 2, tokens: gauge.tokens || [], })); @@ -127,11 +166,6 @@ export class GaugeStakingService implements PoolStakingService { // DB operations for gauges for (const gauge of dbGauges) { - // Skip gauges which pools aren't in the DB - if (!gauge.poolId || !poolIds.includes(gauge.poolId)) { - continue; - } - operations.push( prisma.prismaPoolStaking.upsert({ where: { id_chain: { id: gauge.id, chain: networkContext.chain } }, From bcc2a9ae9d5db2e5acd8a2e8d16d9a10f0643381 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:10:06 +0200 Subject: [PATCH 10/24] cleanup preferential gauges case handling --- .../pool/lib/staking/gauge-staking.service.ts | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 41f99a51b..1d5b8c8f0 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -94,40 +94,9 @@ export class GaugeStakingService implements PoolStakingService { .flat() .filter((gauge): gauge is LiquidityGauge => !!gauge); - // Handle preferential gauges edge cases where the preferential gauge is not set, but pool has gauges, - // or there are multiple preferential gauges set for a pool - // My eyes hurt from looking at this code, but it works - const poolIdsWithPreferentialGauges = subgraphPoolsWithGauges - .filter((pool) => !!pool.preferentialGauge) - .map((pool) => pool.poolId); - const poolIdsWithoutPreferentialGauge = subgraphPoolsWithGauges - .filter((pool) => !poolIdsWithPreferentialGauges.includes(pool.poolId)) - .map((pool) => pool.poolId); - const gaugesForPoolsWithoutPreferentialGauge = subgraphGauges - .filter((gauge) => poolIdsWithoutPreferentialGauge.includes(gauge.poolId)); - const preferentialIdsForPoolsWithoutPreferentialGauge = _ - .uniqBy(gaugesForPoolsWithoutPreferentialGauge, (gauge) => gauge.poolId) - .map((gauge) => gauge.id); - // Remove duplicated preferential gauges for pools that have multiple preferential gauges - const poolsWithMultiplePreferentialGauges = _.countBy( - subgraphGauges.filter((gauge) => gauge.isPreferentialGauge), - (gauge) => gauge.poolId - ); - const poolIdsWithMultiplePreferentialGauges = Object.keys(poolsWithMultiplePreferentialGauges) - .filter((poolId) => poolsWithMultiplePreferentialGauges[poolId] > 1); - const singlePreferentialIds = subgraphGauges - .filter((gauge) => gauge.isPreferentialGauge && !poolIdsWithMultiplePreferentialGauges.includes(gauge.poolId!)) - .map((gauge) => gauge.id); - const preferentialGaugesForPoolsWithMultiplePreferentialGauges = subgraphGauges - .filter((gauge) => poolIdsWithMultiplePreferentialGauges.includes(gauge.poolId!)); - const preferentialIdsForPoolsWithMultiplePreferentialGauges = _ - .uniqBy(preferentialGaugesForPoolsWithMultiplePreferentialGauges, (gauge) => gauge.poolId) - .map((gauge) => gauge.id); - const preferentialIds = [ - ...singlePreferentialIds, - ...preferentialIdsForPoolsWithoutPreferentialGauge, - ...preferentialIdsForPoolsWithMultiplePreferentialGauges, - ]; + const uniquePreferentialIds = subgraphPoolsWithGauges + .filter((pool) => pool.preferentialGauge) + .map((pool) => pool.preferentialGauge!.id); const dbGauges = subgraphGauges .map((gauge) => ({ @@ -136,9 +105,9 @@ export class GaugeStakingService implements PoolStakingService { // we need to set the status based on the preferentialGauge entity on the gaugePool. If it's set there, it's preferential, otherwise it's active (or killed) status: gauge.isKilled ? 'KILLED' - : preferentialIds.includes(gauge.id) - ? 'PREFERRED' - : 'ACTIVE' as LiquidityGaugeStatus, + : !uniquePreferentialIds.includes(gauge.id) + ? 'ACTIVE' + : 'PREFERED' as LiquidityGaugeStatus, version: gauge.streamer ? 1 : 2 as 1 | 2, tokens: gauge.tokens || [], })); @@ -310,6 +279,45 @@ export class GaugeStakingService implements PoolStakingService { return onchainRates; } + // Handle preferential gauges edge cases where the preferential gauge is not set, but pool has gauges, + // or there are multiple preferential gauges set for a pool + private selectPreferentialIds(subgraphGauges: LiquidityGauge[]): string[] { + const poolIdsWithPreferentialGauges = subgraphGauges + .filter((gauge) => gauge.isPreferentialGauge) + .map((gauge) => gauge.poolId); + const poolIdsWithoutPreferentialGauge = subgraphGauges + .filter((gauge) => !poolIdsWithPreferentialGauges.includes(gauge.poolId)) + .map((gauge) => gauge.poolId); + const missingGauges = subgraphGauges + .filter((gauge) => poolIdsWithoutPreferentialGauge.includes(gauge.poolId)); + const missingIds = _ + .uniqBy(missingGauges, (gauge) => gauge.poolId) + .map((gauge) => gauge.id); + + // Remove duplicated preferential gauges for pools that have multiple preferential gauges + const preferentialGaugesCount = _.countBy( + subgraphGauges.filter((gauge) => gauge.isPreferentialGauge), + (gauge) => gauge.poolId + ); + const multiple = Object.keys(preferentialGaugesCount) + .filter((poolId) => preferentialGaugesCount[poolId] > 1); + const singleIds = subgraphGauges + .filter((gauge) => gauge.isPreferentialGauge && !multiple.includes(gauge.poolId!)) + .map((gauge) => gauge.id); + const multipleGauges = subgraphGauges + .filter((gauge) => multiple.includes(gauge.poolId!)); + const multipleIds = _ + .uniqBy(multipleGauges, (gauge) => gauge.poolId) + .map((gauge) => gauge.id); + const preferentialIds = [ + ...singleIds, + ...missingIds, + ...multipleIds, + ]; + + return preferentialIds; + } + public async reloadStakingForAllPools(stakingTypes: PrismaPoolStakingType[]): Promise { if (stakingTypes.includes('GAUGE')) { await prisma.prismaUserStakedBalance.deleteMany({ From 9196e890e0c16a437c6c886695e252c0e6d0fece Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:11:25 +0200 Subject: [PATCH 11/24] typo fix --- modules/pool/lib/staking/gauge-staking.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 1d5b8c8f0..34628fc2b 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -107,7 +107,7 @@ export class GaugeStakingService implements PoolStakingService { ? 'KILLED' : !uniquePreferentialIds.includes(gauge.id) ? 'ACTIVE' - : 'PREFERED' as LiquidityGaugeStatus, + : 'PREFERRED' as LiquidityGaugeStatus, version: gauge.streamer ? 1 : 2 as 1 | 2, tokens: gauge.tokens || [], })); From b0c637e8ecbd4c031e5df5cc6aa68c279c773f7a Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 21 Sep 2023 14:43:05 +0200 Subject: [PATCH 12/24] fix: handle mainnet --- modules/pool/lib/staking/gauge-staking.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 34628fc2b..ca31fd41d 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -108,7 +108,7 @@ export class GaugeStakingService implements PoolStakingService { : !uniquePreferentialIds.includes(gauge.id) ? 'ACTIVE' : 'PREFERRED' as LiquidityGaugeStatus, - version: gauge.streamer ? 1 : 2 as 1 | 2, + version: gauge.streamer || networkContext.chain == 'MAINNET' ? 1 : 2 as 1 | 2, tokens: gauge.tokens || [], })); From 4eefc984004d57cfff667a74db8fb01201e8806d Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 21 Sep 2023 16:02:10 +0200 Subject: [PATCH 13/24] cleanup --- modules/network/data/mainnet.ts | 316 ---------------- modules/network/mainnet.ts | 355 ++++++++++++++++-- .../ve-bal-gauge-apr.service.ts | 120 +++--- .../pool/lib/staking/gauge-staking.service.ts | 179 ++++----- modules/vebal/balancer-token-admin.service.ts | 9 +- 5 files changed, 481 insertions(+), 498 deletions(-) delete mode 100644 modules/network/data/mainnet.ts diff --git a/modules/network/data/mainnet.ts b/modules/network/data/mainnet.ts deleted file mode 100644 index 7ca73f8ce..000000000 --- a/modules/network/data/mainnet.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { BigNumber } from '@ethersproject/bignumber'; -import { NetworkData } from "../network-config-types"; -import { env } from '../../../app/env'; - -const underlyingTokens = { - USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7', - DAI: '0x6b175474e89094c44da98b954eedeac495271d0f', - wETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', -}; - -export const mainnetNetworkData: NetworkData = { - chain: { - slug: 'ethereum', - id: 1, - nativeAssetAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - wrappedNativeAssetAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - prismaId: 'MAINNET', - gqlId: 'MAINNET', - }, - subgraphs: { - startDate: '2019-04-20', - balancer: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', - beetsBar: 'https://', - blocks: 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks', - gauge: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', - veBalLocks: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', - userBalances: 'https://', - }, - eth: { - address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - addressFormatted: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - symbol: 'ETH', - name: 'Ether', - }, - weth: { - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - addressFormatted: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - }, - coingecko: { - nativeAssetId: 'ethereum', - platformId: 'ethereum', - excludedTokenAddresses: [], - }, - tokenPrices: { - maxHourlyPriceHistoryNumDays: 100, - }, - rpcUrl: env.INFURA_API_KEY ? `https://mainnet.infura.io/v3/${env.INFURA_API_KEY}` : 'https://eth.llamarpc.com', - rpcMaxBlockRange: 700, - protocolToken: 'bal', - bal: { - address: '0xba100000625a3754423978a60c9317c58a424e3d', - }, - veBal: { - address: '0xc128a9954e6c874ea3d62ce62b468ba073093f25', - delegationProxy: '0x0000000000000000000000000000000000000000', - }, - gaugeControllerAddress: '0xc128468b7ce63ea702c1f104d55a2566b13d3abd', - gaugeControllerHelperAddress: '0x8e5698dc4897dc12243c8642e77b4f21349db97c', - balancer: { - vault: '0xba12222222228d8ba445958a75a0704d566bf2c8', - tokenAdmin: '0xf302f9f50958c5593770fdf4d4812309ff77414f', - composableStablePoolFactories: [ - '0xf9ac7b9df2b3454e841110cce5550bd5ac6f875f', - '0x85a80afee867adf27b50bdb7b76da70f1e853062', - '0xdba127fbc23fb20f5929c546af220a991b5c6e01', - '0xfada0f4547ab2de89d1304a668c39b3e09aa7c76', - '0xdb8d758bcb971e482b2c45f7f8a7740283a1bd3a', - '0xba1b4a90bad57470a2cba762a32955dc491f76e0', - ], - weightedPoolV2Factories: [ - '0xcc508a455f5b0073973107db6a878ddbdab957bc', - '0x5dd94da3644ddd055fcf6b3e1aa310bb7801eb8b', - '0x897888115ada5773e02aa29f775430bfb5f34c51', - ], - swapProtocolFeePercentage: 0.5, - yieldProtocolFeePercentage: 0.5, - excludedPoolDataQueryPoolIds: ['0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e'], - }, - multicall: '0x5ba1e12693dc8f9c48aad8770482f4739beed696', - multicall3: '0xca11bde05977b3631167028862be2a173976ca11', - avgBlockSpeed: 10, - sor: { - main: { - url: 'https://uu6cfghhd5lqa7py3nojxkivd40zuugb.lambda-url.ca-central-1.on.aws/', - maxPools: 8, - forceRefresh: false, - gasPrice: BigNumber.from(10), - swapGas: BigNumber.from('1000000'), - }, - canary: { - url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', - maxPools: 8, - forceRefresh: false, - gasPrice: BigNumber.from(10), - swapGas: BigNumber.from('1000000'), - }, - }, - ibAprConfig: { - aave: { - v2: { - subgraphUrl: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2', - tokens: { - USDC: { - underlyingAssetAddress: underlyingTokens.USDC, - aTokenAddress: '0xbcca60bb61934080951369a648fb03df4f96263c', - wrappedTokens: { - waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', - }, - }, - USDT: { - underlyingAssetAddress: underlyingTokens.USDT, - aTokenAddress: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', - wrappedTokens: { - waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', - }, - }, - DAI: { - underlyingAssetAddress: underlyingTokens.DAI, - aTokenAddress: '0x028171bca77440897b824ca71d1c56cac55b68a3', - wrappedTokens: { - waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', - }, - }, - }, - }, - v3: { - subgraphUrl: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3', - tokens: { - USDC: { - underlyingAssetAddress: underlyingTokens.USDC, - aTokenAddress: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', - wrappedTokens: { - waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c', - stataEthUSDC: '0x02c2d189b45ce213a40097b62d311cf0dd16ec92', - }, - }, - USDT: { - underlyingAssetAddress: underlyingTokens.USDT, - aTokenAddress: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a', - wrappedTokens: { - waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455', - stataEthUSDT: '0x65799b9fd4206cdaa4a1db79254fcbc2fd2ffee6', - }, - }, - DAI: { - underlyingAssetAddress: underlyingTokens.DAI, - aTokenAddress: '0x018008bfb33d285247a21d44e50697654f754e63', - wrappedTokens: { - waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', - stataEthDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', - }, - }, - wETH: { - underlyingAssetAddress: underlyingTokens.wETH, - aTokenAddress: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8', - wrappedTokens: { - waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6', - stataEthWETH: '0x03928473f25bb2da6bc880b07ecbadc636822264', - }, - }, - }, - }, - }, - ankr: { - sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', - tokens: { - ankrETH: { - address: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb', - serviceName: 'eth', - isIbYield: true, - }, - }, - }, - euler: { - subgraphUrl: 'https://api.thegraph.com/subgraphs/name/euler-xyz/euler-mainnet', - tokens: { - eUSDC: { address: '0xeb91861f8a4e1c12333f42dce8fb0ecdc28da716' }, - eDAI: { address: '0xe025e3ca2be02316033184551d4d3aa22024d9dc' }, - eUSDT: { address: '0x4d19f33948b99800b6113ff3e83bec9b537c85d2' }, - eFRAX: { address: '0x5484451a88a35cd0878a1be177435ca8a0e4054e' }, - }, - }, - gearbox: { - sourceUrl: 'https://mainnet.gearbox.foundation/api/pools', - tokens: { - dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, - dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, - }, - }, - idle: { - sourceUrl: 'https://api.idle.finance/junior-rates/', - authorizationHeader: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', - tokens: { - idleDAI: { - address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', - wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', - }, - idleUSDC: { - address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', - wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', - }, - idleUSDT: { - address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', - wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', - }, - }, - }, - tessera: { - tokens: { - sAPE: { - tesseraPoolAddress: '0x5954ab967bc958940b7eb73ee84797dc8a2afbb9', - tokenAddress: '0x7966c5bae631294d7cffcea5430b78c2f76db6fa', - }, - }, - }, - tranchess: { - sourceUrl: 'https://tranchess.com/eth/api/v3/funds', - tokens: { - qETH: { - address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', - underlyingAssetName: 'WETH', - }, - }, - }, - defaultHandlers: { - vETH: { - tokenAddress: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', - sourceUrl: 'https://apy.liebi.com/veth', - path: 'veth', - }, - stETH: { - tokenAddress: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - wstETH: { - tokenAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - cbETH: { - tokenAddress: '0xbe9895146f7af43049ca1c1ae358b0541ea49704', - sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', - path: 'apy', - scale: 1, - isIbYield: true, - }, - sfrxETH: { - tokenAddress: '0xac3e018457b222d93114458476f3e3416abbe38f', - sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', - path: 'sfrxethApr', - isIbYield: true, - }, - StaFirETH: { - tokenAddress: '0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593', - sourceUrl: 'https://drop-api.stafi.io/reth/v1/poolData', - path: 'data.stakeApr', - isIbYield: true, - }, - rETH: { - tokenAddress: '0xae78736cd615f374d3085123a210448e74fc6393', - sourceUrl: 'https://rocketpool.net/api/mainnet/payload', - path: 'rethAPR', - isIbYield: true, - }, - USDR: { - tokenAddress: '0xaf0d9d65fc54de245cda37af3d18cbec860a4d4b', - sourceUrl: 'http://usdr-api.us-east-1.elasticbeanstalk.com/usdr/apy', - path: 'usdr', - isIbYield: true, - }, - swETH: { - tokenAddress: '0xf951e335afb289353dc249e82926178eac7ded78', - sourceUrl: 'https://v3.svc.swellnetwork.io/api/tokens/sweth/apr', - isIbYield: true, - }, - wjAURA: { - tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', - sourceUrl: 'https://data.jonesdao.io/api/v1/jones/apy-wjaura', - path: 'wjauraApy', - isIbYield: true, - }, - }, - }, - beefy: { - linearPools: [''], - }, - datastudio: { - main: { - user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', - sheetId: '11anHUEb9snGwvB-errb5HvO8TvoLTRJhkDdD80Gxw1Q', - databaseTabName: 'Database v2', - compositionTabName: 'Pool Composition v2', - emissionDataTabName: 'EmissionData', - }, - canary: { - user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', - sheetId: '1HnJOuRQXGy06tNgqjYMzQNIsaCSCC01Yxe_lZhXBDpY', - databaseTabName: 'Database v2', - compositionTabName: 'Pool Composition v2', - emissionDataTabName: 'EmissionData', - }, - }, - monitoring: { - main: { - alarmTopicArn: 'arn:aws:sns:ca-central-1:118697801881:api_alarms', - }, - canary: { - alarmTopicArn: 'arn:aws:sns:eu-central-1:118697801881:api_alarms', - }, - }, -}; diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 22c36dd55..3b07e86e2 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -1,5 +1,5 @@ -import { ethers } from 'ethers'; -import { DeploymentEnv, NetworkConfig } from './network-config-types'; +import { BigNumber, ethers } from 'ethers'; +import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; import { tokenService } from '../token/token.service'; import { PhantomStableAprService } from '../pool/lib/apr-data-sources/phantom-stable-apr.service'; import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/boosted-pool-apr.service'; @@ -16,9 +16,320 @@ import { gaugeSubgraphService } from '../subgraphs/gauge-subgraph/gauge-subgraph import { coingeckoService } from '../coingecko/coingecko.service'; import { CoingeckoPriceHandlerService } from '../token/lib/token-price-handlers/coingecko-price-handler.service'; import { IbTokensAprService } from '../pool/lib/apr-data-sources/ib-tokens-apr.service'; -import { mainnetNetworkData } from './data/mainnet'; import { env } from '../../app/env'; +const underlyingTokens = { + USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7', + DAI: '0x6b175474e89094c44da98b954eedeac495271d0f', + wETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', +}; + +export const mainnetNetworkData: NetworkData = { + chain: { + slug: 'ethereum', + id: 1, + nativeAssetAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + wrappedNativeAssetAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + prismaId: 'MAINNET', + gqlId: 'MAINNET', + }, + subgraphs: { + startDate: '2019-04-20', + balancer: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', + beetsBar: 'https://', + blocks: 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks', + gauge: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + veBalLocks: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + userBalances: 'https://', + }, + eth: { + address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + addressFormatted: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + symbol: 'ETH', + name: 'Ether', + }, + weth: { + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + addressFormatted: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + }, + coingecko: { + nativeAssetId: 'ethereum', + platformId: 'ethereum', + excludedTokenAddresses: [], + }, + tokenPrices: { + maxHourlyPriceHistoryNumDays: 100, + }, + rpcUrl: env.INFURA_API_KEY ? `https://mainnet.infura.io/v3/${env.INFURA_API_KEY}` : 'https://eth.llamarpc.com', + rpcMaxBlockRange: 700, + protocolToken: 'bal', + bal: { + address: '0xba100000625a3754423978a60c9317c58a424e3d', + }, + veBal: { + address: '0xc128a9954e6c874ea3d62ce62b468ba073093f25', + delegationProxy: '0x0000000000000000000000000000000000000000', + }, + gaugeControllerAddress: '0xc128468b7ce63ea702c1f104d55a2566b13d3abd', + gaugeControllerHelperAddress: '0x8e5698dc4897dc12243c8642e77b4f21349db97c', + balancer: { + vault: '0xba12222222228d8ba445958a75a0704d566bf2c8', + composableStablePoolFactories: [ + '0xf9ac7b9df2b3454e841110cce5550bd5ac6f875f', + '0x85a80afee867adf27b50bdb7b76da70f1e853062', + '0xdba127fbc23fb20f5929c546af220a991b5c6e01', + '0xfada0f4547ab2de89d1304a668c39b3e09aa7c76', + '0xdb8d758bcb971e482b2c45f7f8a7740283a1bd3a', + '0xba1b4a90bad57470a2cba762a32955dc491f76e0', + ], + weightedPoolV2Factories: [ + '0xcc508a455f5b0073973107db6a878ddbdab957bc', + '0x5dd94da3644ddd055fcf6b3e1aa310bb7801eb8b', + '0x897888115ada5773e02aa29f775430bfb5f34c51', + ], + swapProtocolFeePercentage: 0.5, + yieldProtocolFeePercentage: 0.5, + excludedPoolDataQueryPoolIds: ['0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e'], + }, + multicall: '0x5ba1e12693dc8f9c48aad8770482f4739beed696', + multicall3: '0xca11bde05977b3631167028862be2a173976ca11', + avgBlockSpeed: 10, + sor: { + main: { + url: 'https://uu6cfghhd5lqa7py3nojxkivd40zuugb.lambda-url.ca-central-1.on.aws/', + maxPools: 8, + forceRefresh: false, + gasPrice: BigNumber.from(10), + swapGas: BigNumber.from('1000000'), + }, + canary: { + url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', + maxPools: 8, + forceRefresh: false, + gasPrice: BigNumber.from(10), + swapGas: BigNumber.from('1000000'), + }, + }, + ibAprConfig: { + aave: { + v2: { + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2', + tokens: { + USDC: { + underlyingAssetAddress: underlyingTokens.USDC, + aTokenAddress: '0xbcca60bb61934080951369a648fb03df4f96263c', + wrappedTokens: { + waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', + }, + }, + USDT: { + underlyingAssetAddress: underlyingTokens.USDT, + aTokenAddress: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', + wrappedTokens: { + waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', + }, + }, + DAI: { + underlyingAssetAddress: underlyingTokens.DAI, + aTokenAddress: '0x028171bca77440897b824ca71d1c56cac55b68a3', + wrappedTokens: { + waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', + }, + }, + }, + }, + v3: { + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3', + tokens: { + USDC: { + underlyingAssetAddress: underlyingTokens.USDC, + aTokenAddress: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', + wrappedTokens: { + waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c', + stataEthUSDC: '0x02c2d189b45ce213a40097b62d311cf0dd16ec92', + }, + }, + USDT: { + underlyingAssetAddress: underlyingTokens.USDT, + aTokenAddress: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a', + wrappedTokens: { + waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455', + stataEthUSDT: '0x65799b9fd4206cdaa4a1db79254fcbc2fd2ffee6', + }, + }, + DAI: { + underlyingAssetAddress: underlyingTokens.DAI, + aTokenAddress: '0x018008bfb33d285247a21d44e50697654f754e63', + wrappedTokens: { + waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', + stataEthDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', + }, + }, + wETH: { + underlyingAssetAddress: underlyingTokens.wETH, + aTokenAddress: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8', + wrappedTokens: { + waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6', + stataEthWETH: '0x03928473f25bb2da6bc880b07ecbadc636822264', + }, + }, + }, + }, + }, + ankr: { + sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', + tokens: { + ankrETH: { + address: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb', + serviceName: 'eth', + isIbYield: true, + }, + }, + }, + euler: { + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/euler-xyz/euler-mainnet', + tokens: { + eUSDC: { address: '0xeb91861f8a4e1c12333f42dce8fb0ecdc28da716' }, + eDAI: { address: '0xe025e3ca2be02316033184551d4d3aa22024d9dc' }, + eUSDT: { address: '0x4d19f33948b99800b6113ff3e83bec9b537c85d2' }, + eFRAX: { address: '0x5484451a88a35cd0878a1be177435ca8a0e4054e' }, + }, + }, + gearbox: { + sourceUrl: 'https://mainnet.gearbox.foundation/api/pools', + tokens: { + dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, + dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, + }, + }, + idle: { + sourceUrl: 'https://api.idle.finance/junior-rates/', + authorizationHeader: + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', + tokens: { + idleDAI: { + address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', + wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', + }, + idleUSDC: { + address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', + wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', + }, + idleUSDT: { + address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', + wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', + }, + }, + }, + tessera: { + tokens: { + sAPE: { + tesseraPoolAddress: '0x5954ab967bc958940b7eb73ee84797dc8a2afbb9', + tokenAddress: '0x7966c5bae631294d7cffcea5430b78c2f76db6fa', + }, + }, + }, + tranchess: { + sourceUrl: 'https://tranchess.com/eth/api/v3/funds', + tokens: { + qETH: { + address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', + underlyingAssetName: 'WETH', + }, + }, + }, + defaultHandlers: { + vETH: { + tokenAddress: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', + sourceUrl: 'https://apy.liebi.com/veth', + path: 'veth', + }, + stETH: { + tokenAddress: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + wstETH: { + tokenAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + cbETH: { + tokenAddress: '0xbe9895146f7af43049ca1c1ae358b0541ea49704', + sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', + path: 'apy', + scale: 1, + isIbYield: true, + }, + sfrxETH: { + tokenAddress: '0xac3e018457b222d93114458476f3e3416abbe38f', + sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', + path: 'sfrxethApr', + isIbYield: true, + }, + StaFirETH: { + tokenAddress: '0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593', + sourceUrl: 'https://drop-api.stafi.io/reth/v1/poolData', + path: 'data.stakeApr', + isIbYield: true, + }, + rETH: { + tokenAddress: '0xae78736cd615f374d3085123a210448e74fc6393', + sourceUrl: 'https://rocketpool.net/api/mainnet/payload', + path: 'rethAPR', + isIbYield: true, + }, + USDR: { + tokenAddress: '0xaf0d9d65fc54de245cda37af3d18cbec860a4d4b', + sourceUrl: 'http://usdr-api.us-east-1.elasticbeanstalk.com/usdr/apy', + path: 'usdr', + isIbYield: true, + }, + swETH: { + tokenAddress: '0xf951e335afb289353dc249e82926178eac7ded78', + sourceUrl: 'https://v3.svc.swellnetwork.io/api/tokens/sweth/apr', + isIbYield: true, + }, + wjAURA: { + tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', + sourceUrl: 'https://data.jonesdao.io/api/v1/jones/apy-wjaura', + path: 'wjauraApy', + isIbYield: true, + }, + }, + }, + beefy: { + linearPools: [''], + }, + datastudio: { + main: { + user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', + sheetId: '11anHUEb9snGwvB-errb5HvO8TvoLTRJhkDdD80Gxw1Q', + databaseTabName: 'Database v2', + compositionTabName: 'Pool Composition v2', + emissionDataTabName: 'EmissionData', + }, + canary: { + user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', + sheetId: '1HnJOuRQXGy06tNgqjYMzQNIsaCSCC01Yxe_lZhXBDpY', + databaseTabName: 'Database v2', + compositionTabName: 'Pool Composition v2', + emissionDataTabName: 'EmissionData', + }, + }, + monitoring: { + main: { + alarmTopicArn: 'arn:aws:sns:ca-central-1:118697801881:api_alarms', + }, + canary: { + alarmTopicArn: 'arn:aws:sns:eu-central-1:118697801881:api_alarms', + }, + }, +}; + export const mainnetNetworkConfig: NetworkConfig = { data: mainnetNetworkData, contentService: new GithubContentService(), @@ -48,7 +359,7 @@ export const mainnetNetworkConfig: NetworkConfig = { workerJobs: [ { name: 'update-token-prices', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(4, 'minutes') : every(2, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(10, 'minutes') : every(2, 'minutes'), }, { name: 'update-liquidity-for-inactive-pools', @@ -58,27 +369,27 @@ export const mainnetNetworkConfig: NetworkConfig = { }, { name: 'update-liquidity-for-active-pools', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(4, 'minutes') : every(2, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(6, 'minutes') : every(2, 'minutes'), }, { name: 'update-pool-apr', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(4, 'minutes') : every(2, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(6, 'minutes') : every(2, 'minutes'), }, { name: 'load-on-chain-data-for-pools-with-active-updates', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(2, 'minutes') : every(1, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(4, 'minutes') : every(1, 'minutes'), }, { name: 'sync-new-pools-from-subgraph', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(4, 'minutes') : every(2, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(6, 'minutes') : every(2, 'minutes'), }, { name: 'sync-tokens-from-pool-tokens', - interval: every(5, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(10, 'minutes') : every(5, 'minutes'), }, { name: 'update-liquidity-24h-ago-for-all-pools', - interval: every(5, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(10, 'minutes') : every(5, 'minutes'), }, { name: 'cache-average-block-time', @@ -86,7 +397,7 @@ export const mainnetNetworkConfig: NetworkConfig = { }, { name: 'sync-staking-for-pools', - interval: every(5, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(10, 'minutes') : every(5, 'minutes'), }, { name: 'sync-latest-snapshots-for-all-pools', @@ -98,21 +409,17 @@ export const mainnetNetworkConfig: NetworkConfig = { }, { name: 'sync-changed-pools', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(40, 'seconds') : every(20, 'seconds'), - alarmEvaluationPeriod: 1, - alarmDatapointsToAlarm: 1, + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(2, 'minutes') : every(20, 'seconds'), + alarmEvaluationPeriod: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? 3 : 1, + alarmDatapointsToAlarm: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? 3 : 1, }, { name: 'user-sync-wallet-balances-for-all-pools', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(5, 'minutes') : every(2, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(30, 'minutes') : every(10, 'minutes'), }, { name: 'user-sync-staked-balances', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(5, 'minutes') : every(2, 'minutes'), - }, - { - name: 'sync-user-snapshots', - interval: every(1, 'hours'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(30, 'minutes') : every(10, 'minutes'), }, { name: 'sync-coingecko-coinids', @@ -130,20 +437,20 @@ export const mainnetNetworkConfig: NetworkConfig = { }, { name: 'sync-vebal-balances', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(2, 'minutes') : every(1, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(9, 'minutes') : every(3, 'minutes'), }, { name: 'sync-vebal-totalSupply', - interval: every(5, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(10, 'minutes') : every(5, 'minutes'), }, { name: 'sync-vebal-voting-gauges', - interval: every(5, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(20, 'minutes') : every(5, 'minutes'), }, // The following are multichain jobs and should only run once for all chains. { name: 'sync-global-coingecko-prices', - interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(4, 'minutes') : every(2, 'minutes'), + interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(10, 'minutes') : every(2, 'minutes'), }, ], }; 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 c09a0968d..e61309e96 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 @@ -1,6 +1,6 @@ /** * This service calculates the APR for a pool based on the gauge rewards - * + * * Definitions: * The “working supply” of the gauge - the effective total LP token amount after all deposits have been boosted. * "Working balance" is 40% of a user balance in a gauge - used only for BAL rewards on v2 gauges on child gauges or on mainnet @@ -9,7 +9,14 @@ import { PrismaPoolWithTokens } from '../../../../prisma/prisma-types'; import { PoolAprService } from '../../pool-types'; import { TokenService } from '../../../token/token.service'; import { secondsPerYear } from '../../../common/time'; -import { PrismaPoolAprItem, PrismaPoolAprRange, PrismaPoolAprType, PrismaPoolStaking, PrismaPoolStakingGauge, PrismaPoolStakingGaugeReward } from '@prisma/client'; +import { + PrismaPoolAprItem, + PrismaPoolAprRange, + PrismaPoolAprType, + PrismaPoolStaking, + PrismaPoolStakingGauge, + PrismaPoolStakingGaugeReward, +} from '@prisma/client'; import { prisma } from '../../../../prisma/prisma-client'; import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; import { networkContext } from '../../../network/network-context.service'; @@ -17,10 +24,7 @@ import { networkContext } from '../../../network/network-context.service'; export class GaugeAprService implements PoolAprService { private readonly MAX_VEBAL_BOOST = 2.5; - constructor( - private readonly tokenService: TokenService, - private readonly primaryTokens: string[], - ) {} + constructor(private readonly tokenService: TokenService, private readonly primaryTokens: string[]) {} public getAprServiceName(): string { return 'GaugeAprService'; @@ -36,15 +40,12 @@ export class GaugeAprService implements PoolAprService { poolId: { in: pools.map((pool) => pool.id) }, type: 'GAUGE', chain: networkContext.chain, - gauge: { - status: 'PREFERRED' - } }, include: { gauge: { include: { rewards: true, - } + }, }, pool: { include: { @@ -57,7 +58,7 @@ export class GaugeAprService implements PoolAprService { for (const stake of stakings) { const { pool, gauge } = stake; - if (!gauge || !pool.dynamicData || !gauge || !gauge.rewards) { + if (!gauge || !pool.dynamicData || !gauge.rewards) { continue; } @@ -83,67 +84,70 @@ export class GaugeAprService implements PoolAprService { address: tokenAddress, symbol: definition.symbol, rewardPerYear: parseFloat(rewardPerSecond) * secondsPerYear * price, - } - }) + }; + }), ); // Calculate APRs const totalShares = parseFloat(pool.dynamicData.totalShares); const bptPrice = pool.dynamicData.totalLiquidity / totalShares; - const gaugeTvl = - totalShares > 0 ? parseFloat(gauge.totalSupply) * bptPrice : 0; + const gaugeTvl = totalShares > 0 ? parseFloat(gauge.totalSupply) * bptPrice : 0; const workingSupply = parseFloat(gauge.workingSupply); - const workingSupplyTvl = ((workingSupply + 0.4) / 0.4) * bptPrice; + const workingSupplyTvl = ((workingSupply + 0.4) / 0.4) * bptPrice; - const aprItems = rewards.map((reward) => { - if (reward.status === 'rejected') { - return null; - } - - const { address, symbol, rewardPerYear } = reward.value; - - const itemData: PrismaPoolAprItem = { - id: `${pool.id}-${symbol}-apr`, - chain: networkContext.chain, - poolId: pool.id, - title: `${symbol} reward APR`, - group: null, - apr: 0, - type: this.primaryTokens.includes(address.toLowerCase()) ? PrismaPoolAprType.NATIVE_REWARD : PrismaPoolAprType.THIRD_PARTY_REWARD, - }; - - // veBAL rewards have a range associated with the item - if ( - address.toLowerCase() === networkContext.data.bal!.address.toLowerCase() && - (networkContext.chain === 'MAINNET' || gauge.version === 2) - ) { - let minApr = 0; - - if (workingSupply > 0) { - minApr = rewardPerYear / workingSupplyTvl; + const aprItems = rewards + .map((reward) => { + if (reward.status === 'rejected') { + return null; } - const aprRangeId = `${pool.id}-bal-apr-range`; - - itemData.apr = minApr; + const { address, symbol, rewardPerYear } = reward.value; - // TODO: Is this needed? Are there any other use-cases besides veBAL? If not, maybe we can remove this and just use the itemData - const rangeData = { - id: aprRangeId, + const itemData: PrismaPoolAprItem = { + id: `${pool.id}-${symbol}-apr`, chain: networkContext.chain, - aprItemId: itemData.id, - min: minApr, - max: minApr * this.MAX_VEBAL_BOOST, + poolId: pool.id, + title: `${symbol} reward APR`, + group: null, + apr: 0, + type: this.primaryTokens.includes(address.toLowerCase()) + ? PrismaPoolAprType.NATIVE_REWARD + : PrismaPoolAprType.THIRD_PARTY_REWARD, }; - return [itemData, rangeData]; - } else { - itemData.apr = gaugeTvl > 0 ? rewardPerYear / gaugeTvl : 0; + // veBAL rewards have a range associated with the item + if ( + address.toLowerCase() === networkContext.data.bal!.address.toLowerCase() && + (networkContext.chain === 'MAINNET' || gauge.version === 2) + ) { + let minApr = 0; - return itemData; - } - }).flat().filter((apr): apr is PrismaPoolAprItem | PrismaPoolAprRange => apr !== null); + if (workingSupply > 0) { + minApr = rewardPerYear / workingSupplyTvl; + } + + const aprRangeId = `${pool.id}-bal-apr-range`; + + itemData.apr = minApr; + + // TODO: Is this needed? Are there any other use-cases besides veBAL? If not, maybe we can remove this and just use the itemData + const rangeData = { + id: aprRangeId, + chain: networkContext.chain, + aprItemId: itemData.id, + min: minApr, + max: minApr * this.MAX_VEBAL_BOOST, + }; + return [itemData, rangeData]; + } else { + itemData.apr = gaugeTvl > 0 ? rewardPerYear / gaugeTvl : 0; + + return itemData; + } + }) + .flat() + .filter((apr): apr is PrismaPoolAprItem | PrismaPoolAprRange => apr !== null); // Prepare DB operations for (const item of aprItems) { @@ -156,7 +160,7 @@ export class GaugeAprService implements PoolAprService { update: item, create: item as PrismaPoolAprRange, }), - ); + ); } else { operations.push( prisma.prismaPoolAprItem.upsert({ diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index ca31fd41d..05a59c1eb 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -1,11 +1,11 @@ /** * Supports calculation of BAL and token rewards sent to gauges. * Balancer has 3 types of gauges: - * + * * 1. Mainnet gauges with working supply and relative weight * 2. Old L2 gauges with BAL rewards sent as a reward token * 3. New L2 gauges (aka child chain gauges) with direct BAL rewards through a streamer. - * + * * Reward data is fetched onchain and stored in the DB as a token rate per second. */ import { PoolStakingService } from '../../pool-types'; @@ -40,20 +40,20 @@ interface GaugeRewardData { [address: string]: { rewardData: { [address: string]: { - period_finish?: BigNumber, - rate?: BigNumber - } - } - } + period_finish?: BigNumber; + rate?: BigNumber; + }; + }; + }; } interface GaugeBalDistributionData { [address: string]: { - rate?: BigNumber, - weight?: BigNumber, - workingSupply?: BigNumber - totalSupply?: BigNumber - } + rate?: BigNumber; + weight?: BigNumber; + workingSupply?: BigNumber; + totalSupply?: BigNumber; + }; } export class GaugeStakingService implements PoolStakingService { @@ -98,19 +98,18 @@ export class GaugeStakingService implements PoolStakingService { .filter((pool) => pool.preferentialGauge) .map((pool) => pool.preferentialGauge!.id); - const dbGauges = subgraphGauges - .map((gauge) => ({ - id: gauge.id, - poolId: gauge.poolId!, - // we need to set the status based on the preferentialGauge entity on the gaugePool. If it's set there, it's preferential, otherwise it's active (or killed) - status: gauge.isKilled - ? 'KILLED' - : !uniquePreferentialIds.includes(gauge.id) - ? 'ACTIVE' - : 'PREFERRED' as LiquidityGaugeStatus, - version: gauge.streamer || networkContext.chain == 'MAINNET' ? 1 : 2 as 1 | 2, - tokens: gauge.tokens || [], - })); + const dbGauges = subgraphGauges.map((gauge) => ({ + id: gauge.id, + poolId: gauge.poolId!, + // we need to set the status based on the preferentialGauge entity on the gaugePool. If it's set there, it's preferential, otherwise it's active (or killed) + status: gauge.isKilled + ? 'KILLED' + : !uniquePreferentialIds.includes(gauge.id) + ? 'ACTIVE' + : ('PREFERRED' as LiquidityGaugeStatus), + version: gauge.streamer || networkContext.chain == 'MAINNET' ? 1 : (2 as 1 | 2), + tokens: gauge.tokens || [], + })); // Get tokens used for all reward tokens including native BAL address, which might not be on the list of tokens stored in the gauge const prismaTokens = await prisma.prismaToken.findMany({ @@ -121,8 +120,8 @@ export class GaugeStakingService implements PoolStakingService { ...subgraphGauges .map((gauge) => gauge.tokens?.map((token) => token.id.split('-')[0].toLowerCase())) .flat() - .filter((address): address is string => !!address) - ] + .filter((address): address is string => !!address), + ], }, chain: networkContext.chain, }, @@ -159,13 +158,15 @@ export class GaugeStakingService implements PoolStakingService { chain: networkContext.chain, status: gauge.status, version: gauge.version, - workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id)?.workingSupply, + workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id) + ?.workingSupply, totalSupply: onchainRates.find(({ id }) => id.includes(gauge.id))?.totalSupply, }, update: { status: gauge.status, version: gauge.version, - workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id)?.workingSupply, + workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id) + ?.workingSupply, totalSupply: onchainRates.find(({ id }) => id.includes(gauge.id))?.totalSupply, }, }), @@ -191,10 +192,10 @@ export class GaugeStakingService implements PoolStakingService { chain: networkContext.chain, gaugeId, tokenAddress, - rewardPerSecond + rewardPerSecond, }, update: { - rewardPerSecond + rewardPerSecond, }, where: { id_chain: { id, chain: networkContext.chain } }, }), @@ -204,7 +205,7 @@ export class GaugeStakingService implements PoolStakingService { await prismaBulkExecuteOperations(operations, true, undefined); } - private async getOnchainRewardTokensData(gauges: { id: string, version: 1 | 2, tokens: { id: string }[] }[]) { + private async getOnchainRewardTokensData(gauges: { id: string; version: 1 | 2; tokens: { id: string }[] }[]) { // Get onchain data for BAL rewards const currentWeek = Math.floor(Date.now() / 1000 / 604800); for (const gauge of gauges) { @@ -213,25 +214,43 @@ export class GaugeStakingService implements PoolStakingService { this.balMulticaller.call(`${gauge.id}.rate`, gauge.id, 'inflation_rate', [currentWeek], true); this.balMulticaller.call(`${gauge.id}.workingSupply`, gauge.id, 'working_supply', [], true); } else if (networkContext.chain === Chain.MAINNET) { - this.balMulticaller.call(`${gauge.id}.weight`, networkContext.data.gaugeControllerAddress!, 'gauge_relative_weight', [gauge.id], true); + this.balMulticaller.call( + `${gauge.id}.weight`, + networkContext.data.gaugeControllerAddress!, + 'gauge_relative_weight', + [gauge.id], + true, + ); this.balMulticaller.call(`${gauge.id}.workingSupply`, gauge.id, 'working_supply', [], true); } } - const balData = await this.balMulticaller.execute() as GaugeBalDistributionData; + const balData = (await this.balMulticaller.execute()) as GaugeBalDistributionData; // Get onchain data for reward tokens for (const gauge of gauges) { for (const token of gauge.tokens ?? []) { const [address] = token.id.toLowerCase().split('-'); if (gauge.version === 1) { - this.rewardsMulticallerV1.call(`${gauge.id}.rewardData.${address}`, gauge.id, 'reward_data', [address], true); + this.rewardsMulticallerV1.call( + `${gauge.id}.rewardData.${address}`, + gauge.id, + 'reward_data', + [address], + true, + ); } else { - this.rewardsMulticallerV2.call(`${gauge.id}.rewardData.${address}`, gauge.id, 'reward_data', [address], true); + this.rewardsMulticallerV2.call( + `${gauge.id}.rewardData.${address}`, + gauge.id, + 'reward_data', + [address], + true, + ); } } } - const rewardsDataV1 = await this.rewardsMulticallerV1.execute() as GaugeRewardData; - const rewardsDataV2 = await this.rewardsMulticallerV2.execute() as GaugeRewardData; + const rewardsDataV1 = (await this.rewardsMulticallerV1.execute()) as GaugeRewardData; + const rewardsDataV2 = (await this.rewardsMulticallerV2.execute()) as GaugeRewardData; const rewardsData = { ...rewardsDataV1, ...rewardsDataV2 }; const totalBalRate = parseFloat(formatUnits(await getInflationRate())); @@ -244,7 +263,8 @@ export class GaugeStakingService implements PoolStakingService { const { rate, weight, workingSupply, totalSupply } = balData[gaugeAddress]; const rewardPerSecond = rate ? formatUnits(rate) // L2 V2 case - : weight ? (parseFloat(formatUnits(weight!)) * totalBalRate).toFixed(18) // mainnet case + : weight + ? (parseFloat(formatUnits(weight!)) * totalBalRate).toFixed(18) // mainnet case : '0'; return { @@ -252,72 +272,37 @@ export class GaugeStakingService implements PoolStakingService { rewardPerSecond, workingSupply: workingSupply ? formatUnits(workingSupply) : '0', totalSupply: totalSupply ? formatUnits(totalSupply) : '0', - } + }; }), - ...Object.keys(rewardsData).map((gaugeAddress) => [ // L2 V1 case, includes tokens other than BAL - ...Object.keys(rewardsData[gaugeAddress].rewardData).map((tokenAddress) => { - const id = `${tokenAddress}-${gaugeAddress}`.toLowerCase(); - const { rate, period_finish } = rewardsData[gaugeAddress].rewardData[tokenAddress]; - const rewardPerSecond = (period_finish && period_finish.toNumber() > now) ? formatUnits(rate!) : '0.0'; - const { totalSupply } = balData[gaugeAddress]; + ...Object.keys(rewardsData) + .map((gaugeAddress) => [ + // L2 V1 case, includes tokens other than BAL + ...Object.keys(rewardsData[gaugeAddress].rewardData).map((tokenAddress) => { + const id = `${tokenAddress}-${gaugeAddress}`.toLowerCase(); + const { rate, period_finish } = rewardsData[gaugeAddress].rewardData[tokenAddress]; + const rewardPerSecond = + period_finish && period_finish.toNumber() > now ? formatUnits(rate!) : '0.0'; + const { totalSupply } = balData[gaugeAddress]; - return { - id, - rewardPerSecond, - workingSupply: '0', - totalSupply: totalSupply ? formatUnits(totalSupply) : '0' - }; - }), - ]).flat(), + return { + id, + rewardPerSecond, + workingSupply: '0', + totalSupply: totalSupply ? formatUnits(totalSupply) : '0', + }; + }), + ]) + .flat(), ].filter(({ rewardPerSecond }) => parseFloat(rewardPerSecond) > 0) as { - id: string - rewardPerSecond: string - workingSupply: string - totalSupply: string + id: string; + rewardPerSecond: string; + workingSupply: string; + totalSupply: string; }[]; return onchainRates; } - // Handle preferential gauges edge cases where the preferential gauge is not set, but pool has gauges, - // or there are multiple preferential gauges set for a pool - private selectPreferentialIds(subgraphGauges: LiquidityGauge[]): string[] { - const poolIdsWithPreferentialGauges = subgraphGauges - .filter((gauge) => gauge.isPreferentialGauge) - .map((gauge) => gauge.poolId); - const poolIdsWithoutPreferentialGauge = subgraphGauges - .filter((gauge) => !poolIdsWithPreferentialGauges.includes(gauge.poolId)) - .map((gauge) => gauge.poolId); - const missingGauges = subgraphGauges - .filter((gauge) => poolIdsWithoutPreferentialGauge.includes(gauge.poolId)); - const missingIds = _ - .uniqBy(missingGauges, (gauge) => gauge.poolId) - .map((gauge) => gauge.id); - - // Remove duplicated preferential gauges for pools that have multiple preferential gauges - const preferentialGaugesCount = _.countBy( - subgraphGauges.filter((gauge) => gauge.isPreferentialGauge), - (gauge) => gauge.poolId - ); - const multiple = Object.keys(preferentialGaugesCount) - .filter((poolId) => preferentialGaugesCount[poolId] > 1); - const singleIds = subgraphGauges - .filter((gauge) => gauge.isPreferentialGauge && !multiple.includes(gauge.poolId!)) - .map((gauge) => gauge.id); - const multipleGauges = subgraphGauges - .filter((gauge) => multiple.includes(gauge.poolId!)); - const multipleIds = _ - .uniqBy(multipleGauges, (gauge) => gauge.poolId) - .map((gauge) => gauge.id); - const preferentialIds = [ - ...singleIds, - ...missingIds, - ...multipleIds, - ]; - - return preferentialIds; - } - public async reloadStakingForAllPools(stakingTypes: PrismaPoolStakingType[]): Promise { if (stakingTypes.includes('GAUGE')) { await prisma.prismaUserStakedBalance.deleteMany({ diff --git a/modules/vebal/balancer-token-admin.service.ts b/modules/vebal/balancer-token-admin.service.ts index 1a7b68102..b691b14af 100644 --- a/modules/vebal/balancer-token-admin.service.ts +++ b/modules/vebal/balancer-token-admin.service.ts @@ -1,14 +1,17 @@ import { Contract } from '@ethersproject/contracts'; import { BigNumber } from '@ethersproject/bignumber'; import { JsonRpcProvider } from '@ethersproject/providers'; -import { mainnetNetworkData } from '../network/data/mainnet'; +import { mainnetNetworkData } from '../network/mainnet'; import abi from './abi/balancerTokenAdmin.json'; -const { balancer: { tokenAdmin: address }, rpcUrl } = mainnetNetworkData; +const { + balancer: { tokenAdmin: address }, + rpcUrl, +} = mainnetNetworkData; export const getInflationRate = async (): Promise => { const provider = new JsonRpcProvider(rpcUrl); const tokenAdmin = new Contract(address!, abi, provider); const inflationRate = await tokenAdmin.getInflationRate(); return inflationRate; -} +}; From bebd85fe730c36d2a9648d97613c496ceb8527b6 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 21 Sep 2023 16:36:08 +0200 Subject: [PATCH 14/24] change tokenadmin --- modules/vebal/balancer-token-admin.service.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/modules/vebal/balancer-token-admin.service.ts b/modules/vebal/balancer-token-admin.service.ts index b691b14af..9fb51ad96 100644 --- a/modules/vebal/balancer-token-admin.service.ts +++ b/modules/vebal/balancer-token-admin.service.ts @@ -1,17 +1,14 @@ import { Contract } from '@ethersproject/contracts'; import { BigNumber } from '@ethersproject/bignumber'; -import { JsonRpcProvider } from '@ethersproject/providers'; -import { mainnetNetworkData } from '../network/mainnet'; import abi from './abi/balancerTokenAdmin.json'; +import { networkContext } from '../network/network-context.service'; -const { - balancer: { tokenAdmin: address }, - rpcUrl, -} = mainnetNetworkData; - -export const getInflationRate = async (): Promise => { - const provider = new JsonRpcProvider(rpcUrl); - const tokenAdmin = new Contract(address!, abi, provider); - const inflationRate = await tokenAdmin.getInflationRate(); - return inflationRate; -}; +export async function getInflationRate(): Promise { + if (networkContext.isMainnet) { + const tokenAdmin = new Contract(networkContext.data.balancer.tokenAdmin!, abi, networkContext.provider); + const inflationRate = await tokenAdmin.getInflationRate(); + return inflationRate; + } else { + return BigNumber.from(0); + } +} From 0bec7bbe5fa9c2b85ea0c1f4af2285c82e130ee3 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 21 Sep 2023 17:04:05 +0200 Subject: [PATCH 15/24] minor fix --- .../lib/apr-data-sources/ve-bal-gauge-apr.service.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) 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 e61309e96..468f6ad5f 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 @@ -120,17 +120,10 @@ export class GaugeAprService implements PoolAprService { address.toLowerCase() === networkContext.data.bal!.address.toLowerCase() && (networkContext.chain === 'MAINNET' || gauge.version === 2) ) { - let minApr = 0; - - if (workingSupply > 0) { - minApr = rewardPerYear / workingSupplyTvl; - } + const minApr = rewardPerYear / workingSupplyTvl; const aprRangeId = `${pool.id}-bal-apr-range`; - itemData.apr = minApr; - - // TODO: Is this needed? Are there any other use-cases besides veBAL? If not, maybe we can remove this and just use the itemData const rangeData = { id: aprRangeId, chain: networkContext.chain, From 6e2f9e3edb8024b6693f8f19220c93b260d55ae5 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 21 Sep 2023 20:51:40 +0200 Subject: [PATCH 16/24] fix ID typo --- modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 468f6ad5f..fdccb0002 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 @@ -122,7 +122,7 @@ export class GaugeAprService implements PoolAprService { ) { const minApr = rewardPerYear / workingSupplyTvl; - const aprRangeId = `${pool.id}-bal-apr-range`; + const aprRangeId = `${pool.id}-BAL-apr-range`; const rangeData = { id: aprRangeId, From bff125f6d2b73c8679b443fb8815570444badef5 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 21 Sep 2023 21:06:11 +0200 Subject: [PATCH 17/24] switch reward id, don't filter gauegs --- modules/pool/lib/staking/gauge-staking.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 05a59c1eb..1d6e2e150 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -175,7 +175,7 @@ export class GaugeStakingService implements PoolStakingService { // DB operations for gauge reward tokens for (const { id, rewardPerSecond } of onchainRates) { - const [tokenAddress, gaugeId] = id.toLowerCase().split('-'); + const [gaugeId, tokenAddress] = id.toLowerCase().split('-'); const token = prismaTokens.find((token) => token.address === tokenAddress); if (!token) { const poolId = subgraphGauges.find((gauge) => gauge.id === gaugeId)?.poolId; @@ -259,7 +259,7 @@ export class GaugeStakingService implements PoolStakingService { // Format onchain rates for all the rewards const onchainRates = [ ...Object.keys(balData).map((gaugeAddress) => { - const id = `${this.balAddress}-${gaugeAddress}`.toLowerCase(); + const id = `${gaugeAddress}-${this.balAddress}-`.toLowerCase(); const { rate, weight, workingSupply, totalSupply } = balData[gaugeAddress]; const rewardPerSecond = rate ? formatUnits(rate) // L2 V2 case @@ -278,7 +278,7 @@ export class GaugeStakingService implements PoolStakingService { .map((gaugeAddress) => [ // L2 V1 case, includes tokens other than BAL ...Object.keys(rewardsData[gaugeAddress].rewardData).map((tokenAddress) => { - const id = `${tokenAddress}-${gaugeAddress}`.toLowerCase(); + const id = `${gaugeAddress}-${tokenAddress}`.toLowerCase(); const { rate, period_finish } = rewardsData[gaugeAddress].rewardData[tokenAddress]; const rewardPerSecond = period_finish && period_finish.toNumber() > now ? formatUnits(rate!) : '0.0'; @@ -293,7 +293,7 @@ export class GaugeStakingService implements PoolStakingService { }), ]) .flat(), - ].filter(({ rewardPerSecond }) => parseFloat(rewardPerSecond) > 0) as { + ] as { id: string; rewardPerSecond: string; workingSupply: string; From c0e1ab05759626edc6b746225d145dfb7db60461 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 21 Sep 2023 21:06:23 +0200 Subject: [PATCH 18/24] add tokenadmin --- modules/network/mainnet.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 6e7419eb3..3c68f6044 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -94,6 +94,7 @@ export const mainnetNetworkData: NetworkData = { swapProtocolFeePercentage: 0.5, yieldProtocolFeePercentage: 0.5, excludedPoolDataQueryPoolIds: ['0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e'], + tokenAdmin: '0xf302f9f50958c5593770fdf4d4812309ff77414f', }, multicall: '0x5ba1e12693dc8f9c48aad8770482f4739beed696', multicall3: '0xca11bde05977b3631167028862be2a173976ca11', From 952060bd5eb0fc80684102542eef54c0fbb84b22 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 21 Sep 2023 21:06:51 +0200 Subject: [PATCH 19/24] adding tokenAdmin address --- modules/network/mainnet.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 6e7419eb3..d13c9306a 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -78,6 +78,7 @@ export const mainnetNetworkData: NetworkData = { gaugeControllerHelperAddress: '0x8e5698dc4897dc12243c8642e77b4f21349db97c', balancer: { vault: '0xba12222222228d8ba445958a75a0704d566bf2c8', + tokenAdmin: '0xf302f9f50958c5593770fdf4d4812309ff77414f', composableStablePoolFactories: [ '0xf9ac7b9df2b3454e841110cce5550bd5ac6f875f', '0x85a80afee867adf27b50bdb7b76da70f1e853062', From 0c812c6b3eb03264c85f2c7caadecb95fed9eb75 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 21 Sep 2023 21:07:04 +0200 Subject: [PATCH 20/24] check apr input --- .../apr-data-sources/ve-bal-gauge-apr.service.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) 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 468f6ad5f..3ebd5d25e 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 @@ -9,14 +9,7 @@ import { PrismaPoolWithTokens } from '../../../../prisma/prisma-types'; import { PoolAprService } from '../../pool-types'; import { TokenService } from '../../../token/token.service'; import { secondsPerYear } from '../../../common/time'; -import { - PrismaPoolAprItem, - PrismaPoolAprRange, - PrismaPoolAprType, - PrismaPoolStaking, - PrismaPoolStakingGauge, - PrismaPoolStakingGaugeReward, -} from '@prisma/client'; +import { PrismaPoolAprItem, PrismaPoolAprRange, PrismaPoolAprType } from '@prisma/client'; import { prisma } from '../../../../prisma/prisma-client'; import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; import { networkContext } from '../../../network/network-context.service'; @@ -58,7 +51,7 @@ export class GaugeAprService implements PoolAprService { for (const stake of stakings) { const { pool, gauge } = stake; - if (!gauge || !pool.dynamicData || !gauge.rewards) { + if (!gauge || !gauge.rewards || !pool.dynamicData || pool.dynamicData.totalShares === '0') { continue; } @@ -120,7 +113,10 @@ export class GaugeAprService implements PoolAprService { address.toLowerCase() === networkContext.data.bal!.address.toLowerCase() && (networkContext.chain === 'MAINNET' || gauge.version === 2) ) { - const minApr = rewardPerYear / workingSupplyTvl; + let minApr = 0; + if (rewardPerYear > 0 && workingSupplyTvl > 0) { + minApr = rewardPerYear / workingSupplyTvl; + } const aprRangeId = `${pool.id}-bal-apr-range`; From 16219b6f3f42a22a9e6b910ff9b30fcfc6785d00 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 21 Sep 2023 21:15:04 +0200 Subject: [PATCH 21/24] fix --- modules/network/mainnet.ts | 1 - modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 38257f66e..d13c9306a 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -95,7 +95,6 @@ export const mainnetNetworkData: NetworkData = { swapProtocolFeePercentage: 0.5, yieldProtocolFeePercentage: 0.5, excludedPoolDataQueryPoolIds: ['0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e'], - tokenAdmin: '0xf302f9f50958c5593770fdf4d4812309ff77414f', }, multicall: '0x5ba1e12693dc8f9c48aad8770482f4739beed696', multicall3: '0xca11bde05977b3631167028862be2a173976ca11', 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 3465fa5c1..02fd729b9 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 @@ -118,7 +118,7 @@ export class GaugeAprService implements PoolAprService { minApr = rewardPerYear / workingSupplyTvl; } - const aprRangeId = `${pool.id}-BAL-apr-range`; + const aprRangeId = `${pool.id}-${symbol}-apr-range`; const rangeData = { id: aprRangeId, From d2de85d8644a90c61f9d9dadb8a3f8a417be1a00 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Fri, 22 Sep 2023 18:16:39 +0200 Subject: [PATCH 22/24] fixes --- .../apr-data-sources/ve-bal-gauge-apr.service.ts | 8 +++++--- modules/pool/lib/staking/gauge-staking.service.ts | 14 +++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) 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 02fd729b9..4c5468269 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 @@ -23,7 +23,7 @@ export class GaugeAprService implements PoolAprService { return 'GaugeAprService'; } - public async updateAprForPools(pools: PrismaPoolWithTokens[]): Promise { + public async updateAprForPools(pools: { id: string }[]): Promise { const operations: any[] = []; // Get the data @@ -114,11 +114,13 @@ export class GaugeAprService implements PoolAprService { (networkContext.chain === 'MAINNET' || gauge.version === 2) ) { let minApr = 0; - if (rewardPerYear > 0 && workingSupplyTvl > 0) { + if (networkContext.chain === 'MAINNET' && workingSupplyTvl > 0) { minApr = rewardPerYear / workingSupplyTvl; + } else if (gaugeTvl > 0) { + minApr = rewardPerYear / gaugeTvl; } - const aprRangeId = `${pool.id}-${symbol}-apr-range`; + const aprRangeId = `${itemData.id}-range`; const rangeData = { id: aprRangeId, diff --git a/modules/pool/lib/staking/gauge-staking.service.ts b/modules/pool/lib/staking/gauge-staking.service.ts index 1d6e2e150..cf5fefb85 100644 --- a/modules/pool/lib/staking/gauge-staking.service.ts +++ b/modules/pool/lib/staking/gauge-staking.service.ts @@ -81,12 +81,12 @@ export class GaugeStakingService implements PoolStakingService { ]); } - async syncStakingForPools(): Promise { + async syncStakingForPools(pools?: { id: string }[]): Promise { // Getting data from the DB and subgraph - const pools = await prisma.prismaPool.findMany({ + const poolIds = (pools ?? await prisma.prismaPool.findMany({ + select: { id: true }, where: { chain: networkContext.chain }, - }); - const poolIds = pools.map((pool) => pool.id); + })).map((pool) => pool.id); const { pools: subgraphPoolsWithGauges } = await this.gaugeSubgraphService.getPoolsWithGauges(poolIds); const subgraphGauges = subgraphPoolsWithGauges @@ -158,14 +158,14 @@ export class GaugeStakingService implements PoolStakingService { chain: networkContext.chain, status: gauge.status, version: gauge.version, - workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id) + workingSupply: onchainRates.find(({ id }) => `${gauge.id}-${this.balAddress}` === id) ?.workingSupply, totalSupply: onchainRates.find(({ id }) => id.includes(gauge.id))?.totalSupply, }, update: { status: gauge.status, version: gauge.version, - workingSupply: onchainRates.find(({ id }) => `${this.balAddress}-${gauge.id}` === id) + workingSupply: onchainRates.find(({ id }) => `${gauge.id}-${this.balAddress}` === id) ?.workingSupply, totalSupply: onchainRates.find(({ id }) => id.includes(gauge.id))?.totalSupply, }, @@ -259,7 +259,7 @@ export class GaugeStakingService implements PoolStakingService { // Format onchain rates for all the rewards const onchainRates = [ ...Object.keys(balData).map((gaugeAddress) => { - const id = `${gaugeAddress}-${this.balAddress}-`.toLowerCase(); + const id = `${gaugeAddress}-${this.balAddress}`.toLowerCase(); const { rate, weight, workingSupply, totalSupply } = balData[gaugeAddress]; const rewardPerSecond = rate ? formatUnits(rate) // L2 V2 case From de96f4fc9a0fd4e5ba1b267e7436359f58f89aac Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Fri, 22 Sep 2023 18:49:36 +0200 Subject: [PATCH 23/24] APR only for the preferred gauge --- modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4c5468269..1479dc208 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 @@ -51,7 +51,7 @@ export class GaugeAprService implements PoolAprService { for (const stake of stakings) { const { pool, gauge } = stake; - if (!gauge || !gauge.rewards || !pool.dynamicData || pool.dynamicData.totalShares === '0') { + if (!gauge || gauge.status !== 'PREFERRED' || !gauge.rewards || !pool.dynamicData || pool.dynamicData.totalShares === '0') { continue; } From 22cde19734bf840247a5a8335001a0dcd7170fd3 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:09:27 +0200 Subject: [PATCH 24/24] setting apr IDs per gauge --- modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 1479dc208..4ad705154 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 @@ -51,7 +51,7 @@ export class GaugeAprService implements PoolAprService { for (const stake of stakings) { const { pool, gauge } = stake; - if (!gauge || gauge.status !== 'PREFERRED' || !gauge.rewards || !pool.dynamicData || pool.dynamicData.totalShares === '0') { + if (!gauge || !gauge.rewards || !pool.dynamicData || pool.dynamicData.totalShares === '0') { continue; } @@ -97,7 +97,7 @@ export class GaugeAprService implements PoolAprService { const { address, symbol, rewardPerYear } = reward.value; const itemData: PrismaPoolAprItem = { - id: `${pool.id}-${symbol}-apr`, + id: `${gauge.id}-${symbol}-apr`, chain: networkContext.chain, poolId: pool.id, title: `${symbol} reward APR`,