Skip to content

Commit

Permalink
feat: start formatting data into main pool (2)
Browse files Browse the repository at this point in the history
  • Loading branch information
therealemjy committed Sep 6, 2023
1 parent abbf0aa commit f65f069
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
areTokensEqual,
calculateApy,
convertDollarsToCents,
convertFactorFromSmartContract,
convertWeiToTokens,
formatTokenPrices,
getVTokenByAddress,
Expand All @@ -21,7 +22,6 @@ import { MAINNET_TOKENS } from 'constants/tokens';
import { logError } from 'context/ErrorLogger';

import { GetTokenBalancesOutput } from '../../getTokenBalances';
import convertFactorFromSmartContract from './convertFactorFromSmartContract';
import formatDistributions from './formatDistributions';
import formatRewardTokenDataMapping from './formatRewardTokenDataMapping';

Expand Down Expand Up @@ -113,7 +113,7 @@ const formatToPools = ({
areAddressesEqual(userBalances[0], vTokenAddress),
);

const tokenPriceCents = new BigNumber(convertDollarsToCents(tokenPriceDollars));
const tokenPriceCents = convertDollarsToCents(tokenPriceDollars);

// Extract supplierCount and borrowerCount from subgraph result
const subgraphPoolMarket = subgraphPool?.markets.find(market =>
Expand Down
176 changes: 169 additions & 7 deletions src/clients/api/queries/getMainPool/formatToPool/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
import BigNumber from 'bignumber.js';
import { ContractTypeByName } from 'packages/contracts';
import { Asset, Pool } from 'types';
import { Asset, Pool, VToken } from 'types';
import {
areAddressesEqual,
convertDollarsToCents,
convertFactorFromSmartContract,
convertPriceMantissaToDollars,
convertWeiToTokens,
getTokenByAddress,
} from 'utilities';

import { getIsolatedPoolParticipantsCount } from 'clients/subgraph';
import { COMPOUND_MANTISSA } from 'constants/compoundMantissa';
import MAX_UINT256 from 'constants/maxUint256';
import { logError } from 'context/ErrorLogger';

import { UnderlyingTokenPriceMantissas } from '../types';
const BSC_MAINNET_VCAN_MAIN_POOL_ADDRESS = '0xeBD0070237a0713E8D94fEf1B728d3d993d290ef';

export interface FormatToPoolInput {
name: string;
description: string;
comptrollerContractAddress: string;
vTokenAddresses: string[];
vTokenMetadataResults: Awaited<
ReturnType<ContractTypeByName<'venusLens'>['callStatic']['vTokenMetadataAll']>
>;
currentBlockNumber: number;
underlyingTokenPriceMantissas: UnderlyingTokenPriceMantissas;
underlyingTokenPriceResults: PromiseSettledResult<
Awaited<ReturnType<ContractTypeByName<'resilientOracle'>['getPrice']>>
>[];
borrowCapsResults: PromiseSettledResult<
Awaited<ReturnType<ContractTypeByName<'mainPoolComptroller'>['borrowCaps']>>
>[];
supplyCapsResults: PromiseSettledResult<
Awaited<ReturnType<ContractTypeByName<'mainPoolComptroller'>['supplyCaps']>>
>[];
xvsPriceMantissa: BigNumber;
assetsInResult?: string[];
userVTokenBalancesResults?: Awaited<
ReturnType<ContractTypeByName<'venusLens'>['callStatic']['vTokenBalancesAll']>
Expand All @@ -28,18 +47,161 @@ const formatToPool = ({
name,
description,
comptrollerContractAddress,
vTokenAddresses,
vTokenMetadataResults,
underlyingTokenPriceResults,
borrowCapsResults,
supplyCapsResults,
xvsPriceMantissa,
currentBlockNumber,
underlyingTokenPriceMantissas,
assetsInResult,
userVTokenBalancesResults,
vaiRepayAmountResult,
mainParticipantsCountResult,
}: FormatToPoolInput) => {
// TODO: shape from data
const assets: Asset[] = [];

vTokenMetadataResults.forEach((vTokenMetaData, index) => {
// Temporary workaround to filter out vCAN
if (areAddressesEqual(vTokenMetaData.vToken, BSC_MAINNET_VCAN_MAIN_POOL_ADDRESS)) {
// TODO: remove once a more generic solution has been integrated on the contract side
return;
}

const underlyingToken = getTokenByAddress(vTokenMetaData.underlyingAssetAddress);

if (!underlyingToken) {
logError(`Record missing for token: ${vTokenMetaData.underlyingAssetAddress}`);
return;
}

const vToken: VToken = {
decimals: 8,
address: vTokenMetaData.vToken,
symbol: `v${underlyingToken.symbol}`,
underlyingToken,
};

const underlyingTokenPriceResult = underlyingTokenPriceResults[index];

const underlyingTokenPriceMantissa =
underlyingTokenPriceResult.status === 'fulfilled'
? new BigNumber(underlyingTokenPriceResult.value.toString())
: undefined;

if (!underlyingTokenPriceMantissa) {
logError(
`Price could not be fetched for token: ${underlyingToken.symbol} ${underlyingToken.address}`,
);
return;
}

const borrowCapsResult = borrowCapsResults[index];
const borrowCapsMantissa =
borrowCapsResult.status === 'fulfilled'
? new BigNumber(borrowCapsResult.value.toString())
: undefined;

if (!borrowCapsMantissa) {
logError(`Borrow cap could not be fetched for vToken: ${vToken.symbol} ${vToken.address}`);
return;
}

const supplyCapsResult = supplyCapsResults[index];
const supplyCapsMantissa =
supplyCapsResult.status === 'fulfilled'
? new BigNumber(supplyCapsResult.value.toString())
: undefined;

if (!supplyCapsMantissa) {
logError(`Supply cap could not be fetched for vToken: ${vToken.symbol} ${vToken.address}`);
return;
}

const tokenPriceDollars = convertPriceMantissaToDollars({
priceMantissa: underlyingTokenPriceMantissa,
token: underlyingToken,
});

const tokenPriceCents = convertDollarsToCents(tokenPriceDollars);

const unformattedBorrowCapTokens = convertWeiToTokens({
valueWei: borrowCapsMantissa,
token: vToken.underlyingToken,
});

const borrowCapTokens = unformattedBorrowCapTokens.isEqualTo(0)
? undefined
: unformattedBorrowCapTokens;

const unformattedSupplyCapTokens = convertWeiToTokens({
valueWei: supplyCapsMantissa,
token: vToken.underlyingToken,
});

const supplyCapTokens = unformattedSupplyCapTokens
.multipliedBy(COMPOUND_MANTISSA)
.isEqualTo(MAX_UINT256)
? undefined
: unformattedSupplyCapTokens;

const reserveFactor = convertFactorFromSmartContract({
factor: new BigNumber(vTokenMetaData.reserveFactorMantissa.toString()),
});

const collateralFactor = convertFactorFromSmartContract({
factor: new BigNumber(vTokenMetaData.collateralFactorMantissa.toString()),
});

const cashTokens = convertWeiToTokens({
valueWei: new BigNumber(vTokenMetaData.totalCash.toString()),
token: vToken.underlyingToken,
});

const liquidityCents = cashTokens.multipliedBy(tokenPriceCents);

const reserveTokens = convertWeiToTokens({
valueWei: new BigNumber(vTokenMetaData.totalReserves.toString()),
token: vToken.underlyingToken,
});

const asset: Asset = {
vToken,
tokenPriceCents,
reserveFactor,
collateralFactor,
liquidityCents,
reserveTokens,
cashTokens,
borrowCapTokens,
supplyCapTokens,

exchangeRateVTokens: new BigNumber(0),
supplierCount: 0,
borrowerCount: 0,
borrowApyPercentage: new BigNumber(0),
supplyApyPercentage: new BigNumber(0),
supplyBalanceTokens: new BigNumber(0),
supplyBalanceCents: new BigNumber(0),
borrowBalanceTokens: new BigNumber(0),
borrowBalanceCents: new BigNumber(0),
supplyPercentageRatePerBlock: new BigNumber(0),
borrowPercentageRatePerBlock: new BigNumber(0),
supplyDistributions: [],
borrowDistributions: [],
// User-specific props
userSupplyBalanceTokens: new BigNumber(0),
userSupplyBalanceCents: new BigNumber(0),
userBorrowBalanceTokens: new BigNumber(0),
userBorrowBalanceCents: new BigNumber(0),
userWalletBalanceTokens: new BigNumber(0),
userWalletBalanceCents: new BigNumber(0),
userPercentOfLimit: 0,
isCollateralOfUser: false,
};

assets.push(asset);
});

const pool: Pool = {
comptrollerAddress: comptrollerContractAddress,
name,
Expand Down
94 changes: 53 additions & 41 deletions src/clients/api/queries/getMainPool/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import BigNumber from 'bignumber.js';

import { getIsolatedPoolParticipantsCount } from 'clients/subgraph';
import { TOKENS } from 'constants/tokens';
import { logError } from 'context/ErrorLogger';

import formatToPool from './formatToPool';
import { GetMainPoolInput, GetMainPoolOutput, UnderlyingTokenPriceMantissas } from './types';
import { GetMainPoolInput, GetMainPoolOutput } from './types';

export type { GetMainPoolInput, GetMainPoolOutput } from './types';

Expand All @@ -30,31 +31,32 @@ const getMainPool = async ({
resilientOracleContract,
provider,
}: GetMainPoolInput): Promise<GetMainPoolOutput> => {
const promises = [
const [
marketsResult,
currentBlockNumberResult,
mainParticipantsCountResult,
xvsPriceMantissaResult,
assetsInResult,
// eslint-disable-next-line @typescript-eslint/naming-convention
_accrueVaiInterestResult,
vaiRepayAmountResult,
] = await Promise.allSettled([
// Fetch all markets
mainPoolComptrollerContract.getAllMarkets(),
// Fetch current block number
provider.getBlockNumber(),
// Fetch borrower and supplier counts of each asset
safelyGetMainPoolParticipantsCount(),
// Fetch XVS price
resilientOracleContract.getPrice(TOKENS.xvs.address),
// Account related calls
accountAddress ? mainPoolComptrollerContract.getAssetsIn(accountAddress) : undefined,
// Call (statically) accrueVAIInterest to calculate past accrued interests before fetching all
// interests. Since wagmi will batch these requests, the call to accrueVAIInterest and
// interests. Since multicall will batch these requests, the call to accrueVAIInterest and
// getVAIRepayAmount will happen in the same request (thus making the accrual possible)
accountAddress ? vaiControllerContract.callStatic.accrueVAIInterest() : undefined,
accountAddress ? vaiControllerContract.getVAIRepayAmount(accountAddress) : undefined,
] as const;

const [
marketsResult,
currentBlockNumberResult,
mainParticipantsCountResult,
assetsInResult,
// eslint-disable-next-line @typescript-eslint/naming-convention
_accrueVaiInterestResult,
vaiRepayAmountResult,
] = await Promise.allSettled(promises);
]);

if (marketsResult.status === 'rejected') {
throw new Error(marketsResult.reason);
Expand All @@ -64,46 +66,56 @@ const getMainPool = async ({
throw new Error(currentBlockNumberResult.reason);
}

if (xvsPriceMantissaResult.status === 'rejected') {
throw new Error(xvsPriceMantissaResult.reason);
}

const vTokenAddresses = marketsResult.value;

const [vTokenMetadataResults, userVTokenBalancesResults, ...underlyingTokenPricesResults] =
await Promise.allSettled([
// Fetch vToken data
venusLensContract.callStatic.vTokenMetadataAll(vTokenAddresses),
// Fetch use vToken balances
accountAddress
? venusLensContract.callStatic.vTokenBalancesAll(vTokenAddresses, accountAddress)
: undefined,
// Fetch underlying token prices
...vTokenAddresses.map(vTokenAddress =>
resilientOracleContract.getUnderlyingPrice(vTokenAddress),
),
]);
// Fetch underlying token prices
const underlyingTokenPricePromises = Promise.allSettled(
vTokenAddresses.map(vTokenAddress => resilientOracleContract.getUnderlyingPrice(vTokenAddress)),
);

// Fetch vToken borrow caps
const borrowCapsPromises = Promise.allSettled(
vTokenAddresses.map(vTokenAddress => mainPoolComptrollerContract.borrowCaps(vTokenAddress)),
);

// Fetch vToken supply caps
const supplyCapsPromises = Promise.allSettled(
vTokenAddresses.map(vTokenAddress => mainPoolComptrollerContract.supplyCaps(vTokenAddress)),
);

// Fetch vToken meta data and user balance
const vTokenMetaDataPromises = Promise.allSettled([
// Fetch vToken data
venusLensContract.callStatic.vTokenMetadataAll(vTokenAddresses),
// Fetch use vToken balances
accountAddress
? venusLensContract.callStatic.vTokenBalancesAll(vTokenAddresses, accountAddress)
: undefined,
]);

const underlyingTokenPriceResults = await underlyingTokenPricePromises;
const borrowCapsResults = await borrowCapsPromises;
const supplyCapsResults = await supplyCapsPromises;
const [vTokenMetadataResults, userVTokenBalancesResults] = await vTokenMetaDataPromises;

if (vTokenMetadataResults.status === 'rejected') {
throw new Error(vTokenMetadataResults.reason);
}

const underlyingTokenPriceMantissas =
underlyingTokenPricesResults.reduce<UnderlyingTokenPriceMantissas>(
(underlyingTokenPriceMantissasAcc, underlyingTokenPricesResult, index) => ({
...underlyingTokenPriceMantissasAcc,
[vTokenAddresses[index]]:
underlyingTokenPricesResult.status === 'fulfilled'
? new BigNumber(underlyingTokenPricesResult.value.toString())
: undefined,
}),
{},
);

const pool = formatToPool({
name,
description,
comptrollerContractAddress: mainPoolComptrollerContract.address,
vTokenAddresses,
vTokenMetadataResults: vTokenMetadataResults.value,
underlyingTokenPriceResults,
borrowCapsResults,
supplyCapsResults,
currentBlockNumber: currentBlockNumberResult.value,
underlyingTokenPriceMantissas,
xvsPriceMantissa: new BigNumber(xvsPriceMantissaResult.value.toString()),
assetsInResult: assetsInResult.status === 'fulfilled' ? assetsInResult.value : undefined,
userVTokenBalancesResults:
userVTokenBalancesResults.status === 'fulfilled'
Expand Down
5 changes: 0 additions & 5 deletions src/clients/api/queries/getMainPool/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import BigNumber from 'bignumber.js';
import { ContractTypeByName } from 'packages/contracts';
import { Pool } from 'types';

import { type Provider } from 'clients/web3';

export interface UnderlyingTokenPriceMantissas {
[vTokenAddress: string]: BigNumber | undefined;
}

export interface GetMainPoolInput {
name: string;
description: string;
Expand Down
Loading

0 comments on commit f65f069

Please sign in to comment.