Skip to content

Commit

Permalink
add v2 boost support to /boosts and /apy/boosts (#1522)
Browse files Browse the repository at this point in the history
  • Loading branch information
ReflectiveChimp authored Jul 26, 2024
1 parent eb05ac6 commit 44fc3bf
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 468 deletions.
515 changes: 84 additions & 431 deletions src/abis/IBeefyRewardPool.ts

Large diffs are not rendered by default.

65 changes: 45 additions & 20 deletions src/api/boosts/fetchBoostData.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { ChainId } from '../../../packages/address-book/src/address-book';
import BigNumber from 'bignumber.js';
import { ApiChain } from '../../utils/chain';
import { ApiChain, toAppChain } from '../../utils/chain';
import { fetchContract } from '../rpc/client';
import BeefyBoostAbi from '../../abis/BeefyBoost';
import { Boost } from './types';
import { IBeefyRewardPool } from '../../abis/IBeefyRewardPool';
import { Boost, BoostConfig } from './types';
import { bigintRange } from '../../utils/array';
import { bigintToNumber } from '../../utils/big-int';

export const getBoosts = async chain => {
const boostsEndpoint = `https://raw.githubusercontent.com/beefyfinance/beefy-v2/prod/src/config/boost/${chain}.json`;
type BoostConfigRaw = Omit<BoostConfig, 'version' | 'chain'> & {
version?: number;
};

export const getBoosts = async (chain: ApiChain): Promise<BoostConfig[]> => {
const boostsEndpoint = `https://raw.githubusercontent.com/beefyfinance/beefy-v2/prod/src/config/boost/${toAppChain(
chain
)}.json`;
const response = await fetch(boostsEndpoint);
if (response.status !== 200) {
throw new Error(
Expand All @@ -19,25 +27,42 @@ export const getBoosts = async chain => {
throw new Error(`Invalid boosts data for ${chain}`);
}

return (boosts as Boost[]).filter(b => !b.version || b.version === 1);
return (boosts as BoostConfigRaw[]).map(b => ({
...b,
version: b.version || 1,
chain,
}));
};

export const getBoostPeriodFinish = async (chain: ApiChain, boosts: any[]) => {
export const getBoostPeriodFinish = async (
chain: ApiChain,
boosts: BoostConfig[]
): Promise<Boost[]> => {
const chainId = ChainId[chain];
const periodFinishCalls = boosts.map(async (boost): Promise<number[]> => {
if (boost.version >= 2) {
const poolContract = fetchContract(boost.earnContractAddress, IBeefyRewardPool, chainId);
const numRewards = await poolContract.read.rewardsLength();
if (numRewards === 0n) {
return [];
}

const boostAddresses = boosts.map(v => v.earnContractAddress);
const periodFinishCalls = boostAddresses.map(boostAddress => {
const boostContract = fetchContract(boostAddress, BeefyBoostAbi, chainId);
return boostContract.read.periodFinish();
});

const res = await Promise.all(periodFinishCalls);
return await Promise.all(
bigintRange(numRewards).map(async (rewardId): Promise<number> => {
const rewardInfo = await poolContract.read.rewardInfo([rewardId]);
return bigintToNumber(rewardInfo[1]);
})
);
}

const periodFinishes = res.map(v => new BigNumber(v.toString()).toNumber());

for (let i = 0; i < periodFinishes.length; i++) {
boosts[i].periodFinish = periodFinishes[i];
}
const boostContract = fetchContract(boost.earnContractAddress, BeefyBoostAbi, chainId);
return [bigintToNumber(await boostContract.read.periodFinish())];
});

return boosts;
const periodFinishes = await Promise.all(periodFinishCalls);
return boosts.map((boost, i) => ({
...boost,
periodFinish: periodFinishes[i].length ? Math.max(...periodFinishes[i]) : 0,
periodFinishes: periodFinishes[i],
}));
};
10 changes: 4 additions & 6 deletions src/api/boosts/getBoosts.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MULTICHAIN_ENDPOINTS } from '../../constants';
import { getKey, setKey } from '../../utils/cache';
import { getBoostPeriodFinish, getBoosts } from './fetchBoostData';
import { Boost } from './types';
import { Boost, BoostConfig } from './types';
import { serviceEventBus } from '../../utils/ServiceEventBus';
import { isResultFulfilled, isResultRejected, withTimeout } from '../../utils/promise';
import { ApiChain, toAppChain } from '../../utils/chain';
import { ApiChain } from '../../utils/chain';

const REDIS_KEY = 'BOOSTS_BY_CHAIN';

Expand Down Expand Up @@ -67,10 +67,8 @@ function buildFromChains() {
}

async function updateChainBoosts(chain: ApiChain) {
let chainBoosts: Boost[] = await getBoosts(toAppChain(chain));
chainBoosts.forEach(boost => (boost.chain = chain));
chainBoosts = await getBoostPeriodFinish(chain, chainBoosts);
boostsByChain[chain] = chainBoosts;
const chainBoosts: BoostConfig[] = await getBoosts(chain);
boostsByChain[chain] = await getBoostPeriodFinish(chain, chainBoosts);
}

async function loadFromRedis() {
Expand Down
12 changes: 9 additions & 3 deletions src/api/boosts/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type Boost = {
import { ApiChain } from '../../utils/chain';

export type BoostConfig = {
id: string;
poolId: string;
name: string;
Expand All @@ -14,7 +16,11 @@ export type Boost = {
status: 'active' | 'prestake' | 'closed';
isMooStaked: boolean;
partners: string[];
chain: string;
version: number;
chain: ApiChain;
};

export type Boost = BoostConfig & {
periodFinish: number;
version?: number;
periodFinishes: number[];
};
2 changes: 1 addition & 1 deletion src/api/stats/common/getCowVaultApys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function getCowVaultApyBreakdown(
return {
vaultId: clm.vault.oracleId,
vault:
(clmBreakdown?.clmApr || 0) +
(clmBreakdown?.clmApr || 0) + // TODO clmApr already has fee removed
(rewardPoolBreakdown?.merklApr || 0) +
(rewardPoolBreakdown?.rewardPoolApr || 0),
compoundingsPerYear: DAILY_HPY,
Expand Down
4 changes: 2 additions & 2 deletions src/api/stats/getApys.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const { getModeApys } = require('./mode');
const { getMantaApys } = require('./manta');
const { getRealApys } = require('./real');
const { getKey, setKey } = require('../../utils/cache');
const { fetchBoostAprs } = require('./getBoostAprs');
const { fetchBoostAprs, BOOST_APR_EXPIRED } = require('./getBoostAprs');

const INIT_DELAY = process.env.INIT_DELAY || 30 * 1000;
const BOOST_APR_INIT_DELAY = 30 * 1000;
Expand Down Expand Up @@ -130,7 +130,7 @@ const updateBoostAprs = async () => {
};
//-1 will be returned when boost has ended and it will be removed from the api response
Object.keys(boostAprs)
.filter(boostId => boostAprs[boostId] === -1)
.filter(boostId => boostAprs[boostId] === BOOST_APR_EXPIRED)
.forEach(boostId => {
delete boostAprs[boostId];
});
Expand Down
78 changes: 73 additions & 5 deletions src/api/stats/getBoostAprs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,66 @@ import { ApiChain, toChainId } from '../../utils/chain';
import BeefyBoostAbi from '../../abis/BeefyBoost';
import { fetchContract } from '../rpc/client';
import { isFiniteNumber } from '../../utils/number';
import { partition } from 'lodash';
import { getBeefyRewardPoolV2Aprs } from './common/getBeefyRewardPoolV2Apr';
import { getAddress } from 'viem';
import { isDefined } from '../../utils/array';

const { getVaultByID } = require('../stats/getMultichainVaults');

const updateBoostAprsForChain = async (chain: ApiChain, boosts: Boost[]) => {
export const BOOST_APR_EXPIRED = -1;

const updateBoostV2AprsForChain = async (chain: ApiChain, boosts: Boost[]) => {
try {
const chainId = toChainId(chain);

//TODO: check boost update data frequency (/boosts already has periodFinish property) to see if periodFinish is still valid and rpc call can be avoided

const results = await getBeefyRewardPoolV2Aprs(
chainId,
boosts
.map(boost => {
const vault = getVaultByID(boost.poolId);
if (!vault) {
console.warn(
`updateBoostV2AprsForChain`,
chain,
`vault ${boost.poolId} not found for boost ${boost.id}`
);
return undefined;
}

return {
oracleId: boost.id,
address: getAddress(boost.earnContractAddress),
stakedToken: {
oracleId: vault.oracleId,
address: vault.earnContractAddress,
decimals: 18,
},
};
})
.filter(isDefined)
);

return results.reduce((aprs: Record<string, number>, result) => {
if (
result &&
result.totalApr !== undefined &&
result.rewardsApr &&
result.rewardsApr.length > 0
) {
aprs[result.oracleId] = result.totalApr;
}
return aprs;
}, Object.fromEntries(boosts.map(boost => [boost.id, BOOST_APR_EXPIRED])));
} catch (err) {
console.error('updateBoostV2AprsForChain', chain, err.message);
return {};
}
};

const updateBoostV1AprsForChain = async (chain: ApiChain, boosts: Boost[]) => {
const chainId = toChainId(chain);

//TODO: check boost update data frequency (/boosts already has periodFinish property) to see if periodFinish is still valid and rpc call can be avoided
Expand Down Expand Up @@ -50,14 +106,26 @@ const updateBoostAprsForChain = async (chain: ApiChain, boosts: Boost[]) => {

return boostAprs;
} catch (err) {
console.log(err.message);
console.error('updateBoostV1AprsForChain', chain, err.message);
return {};
}
};

const updateBoostAprsForChain = async (
chain: ApiChain,
boosts: Boost[]
): Promise<Record<string, number>> => {
const [boostsV2, boostsV1] = partition(boosts, boost => boost.version >= 2);
const [aprsV2, aprsV1] = await Promise.all([
updateBoostV2AprsForChain(chain, boostsV2),
updateBoostV1AprsForChain(chain, boostsV1),
]);

return { ...aprsV1, ...aprsV2 };
};

/**
* @param callReturnContext
* @returns -1 if boost has expired, null if error ocurred, apr number value if successful
* @returns -1 if boost has expired, null if error occurred, apr number value if successful
*/
const mapResponseToBoostApr = async (
boost: Boost,
Expand All @@ -69,7 +137,7 @@ const mapResponseToBoostApr = async (
const rewardRate = new BigNumber(rate.toString());
const periodFinish = new BigNumber(finish.toString());

if (periodFinish.times(1000).lte(new BigNumber(Date.now()))) return -1;
if (periodFinish.times(1000).lte(new BigNumber(Date.now()))) return BOOST_APR_EXPIRED;

try {
const vault: Vault = getVaultByID(boost.poolId);
Expand Down
18 changes: 18 additions & 0 deletions src/utils/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,21 @@ export function isDefined<T>(value: T): value is Exclude<T, undefined | null> {
export function toArray<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value];
}

export function numberRange(start: number, end?: number | undefined): number[] {
if (end === undefined) {
end = start;
start = 0;
}

return Array.from({ length: end - start }, (_, i) => i + start);
}

export function bigintRange(start: bigint, end?: bigint | undefined): bigint[] {
if (end === undefined) {
end = start;
start = BigInt(0);
}

return Array.from({ length: parseInt((end - start).toString(10)) }, (_, i) => start + BigInt(i));
}
7 changes: 7 additions & 0 deletions src/utils/big-int.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function bigintToNumber(value: bigint): number {
if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) {
return Number(value);
}

throw new Error(`BigInt ${value} is out of range for a Number`);
}

0 comments on commit 44fc3bf

Please sign in to comment.