From fbb6f66ae7280a97ffd272ecc73360c778a44646 Mon Sep 17 00:00:00 2001 From: shan Date: Wed, 30 Oct 2024 16:01:31 +0800 Subject: [PATCH 1/2] unify get pool apy --- .../huma-shared/src/solana/utils/poolUtils.ts | 87 ++------ packages/huma-shared/src/utils/apy.ts | 209 ++++++++++++++++++ packages/huma-shared/src/utils/index.ts | 9 +- .../src/v2/utils/poolConfigContract.ts | 149 ++----------- .../src/solana/types/solanaPoolState.ts | 1 + 5 files changed, 251 insertions(+), 204 deletions(-) create mode 100644 packages/huma-shared/src/utils/apy.ts diff --git a/packages/huma-shared/src/solana/utils/poolUtils.ts b/packages/huma-shared/src/solana/utils/poolUtils.ts index b89183c0..25cf2ae8 100644 --- a/packages/huma-shared/src/solana/utils/poolUtils.ts +++ b/packages/huma-shared/src/solana/utils/poolUtils.ts @@ -1,5 +1,6 @@ import { BN } from '@coral-xyz/anchor' +import { getPoolApyV2 } from '../../utils/apy' import { SOLANA_BP_FACTOR } from '../const' export const getSolanaPoolApy = ( @@ -13,78 +14,20 @@ export const getSolanaPoolApy = ( defaultMaxSeniorJuniorRatio: number, fixedSeniorYieldInBps: number, tranchesRiskAdjustmentInBps: number, -): { - blendedApy: number - seniorTrancheApy: number - juniorTrancheApy: number -} => { - const BP_FACTOR_NUMBER = SOLANA_BP_FACTOR.toNumber() - const APY = yieldInBps / BP_FACTOR_NUMBER - - const totalDeployedAssets = seniorDeployedAssets.add(juniorDeployedAssets) - const seniorMaxAssets = liquidityCap - .sub(totalDeployedAssets) - .add(seniorDeployedAssets) - const currentMaxSeniorJuniorRatio = seniorMaxAssets - .div(juniorDeployedAssets) - .toNumber() - - let juniorAssets = liquidityCap.div(new BN(defaultMaxSeniorJuniorRatio + 1)) - let seniorAssets = liquidityCap.sub(juniorAssets) - if (currentMaxSeniorJuniorRatio < defaultMaxSeniorJuniorRatio) { - juniorAssets = juniorDeployedAssets - seniorAssets = seniorMaxAssets - } - - const totalProfit = liquidityCap - .mul(new BN(Math.round(APY * BP_FACTOR_NUMBER))) - .div(SOLANA_BP_FACTOR) - const postPoolProfitRatio = - (1 - protocolFeeInBps / BP_FACTOR_NUMBER) * - (1 - - rewardRateInBpsForPoolOwner / BP_FACTOR_NUMBER - - rewardRateInBpsForEA / BP_FACTOR_NUMBER) - const poolPostProfit = totalProfit - .mul(new BN(Math.round(postPoolProfitRatio * BP_FACTOR_NUMBER))) - .div(SOLANA_BP_FACTOR) - const blendedApy = - poolPostProfit.mul(SOLANA_BP_FACTOR).div(liquidityCap).toNumber() / - BP_FACTOR_NUMBER - - let seniorTrancheApy = 0 - let juniorProfit = new BN(0) - if (fixedSeniorYieldInBps > 0) { - seniorTrancheApy = fixedSeniorYieldInBps / BP_FACTOR_NUMBER - juniorProfit = poolPostProfit.sub( - seniorAssets - .mul(new BN(Math.round(seniorTrancheApy * BP_FACTOR_NUMBER))) - .div(SOLANA_BP_FACTOR), - ) - } else { - const riskAdjustment = tranchesRiskAdjustmentInBps / BP_FACTOR_NUMBER - const seniorProfit = seniorAssets - .mul( - new BN( - Math.round( - postPoolProfitRatio * (1 - riskAdjustment) * APY * BP_FACTOR_NUMBER, - ), - ), - ) - .div(SOLANA_BP_FACTOR) - seniorTrancheApy = postPoolProfitRatio * (1 - riskAdjustment) * APY - juniorProfit = poolPostProfit.sub(seniorProfit) - } - - const juniorTrancheApy = - juniorProfit.mul(SOLANA_BP_FACTOR).div(juniorAssets).toNumber() / - BP_FACTOR_NUMBER - - return { - blendedApy, - seniorTrancheApy, - juniorTrancheApy, - } -} +) => + getPoolApyV2( + protocolFeeInBps, + yieldInBps, + rewardRateInBpsForPoolOwner, + rewardRateInBpsForEA, + liquidityCap.toString(), + seniorDeployedAssets.toString(), + juniorDeployedAssets.toString(), + defaultMaxSeniorJuniorRatio, + fixedSeniorYieldInBps, + tranchesRiskAdjustmentInBps, + SOLANA_BP_FACTOR.toNumber(), + ) export const getSolanaUtilizationRate = ( seniorTrancheAssets: BN | undefined, diff --git a/packages/huma-shared/src/utils/apy.ts b/packages/huma-shared/src/utils/apy.ts new file mode 100644 index 00000000..aaab75a1 --- /dev/null +++ b/packages/huma-shared/src/utils/apy.ts @@ -0,0 +1,209 @@ +import { BigNumber } from 'ethers' + +export interface FclConfig { + minLiquidity: string + riskYieldMultiplierInBps: number + flcIndex: number + maxLiquidity: string +} + +export interface FclConfigWithApy extends FclConfig { + apy: number + firstLossCoverIndex: number +} + +export interface PoolApy { + flcConfigsWithApy: FclConfigWithApy[] + blendedApy: number + seniorTrancheApy: number + juniorTrancheApy: number +} + +const getFlcTotalAssetsAfterRisk = ( + flcConfigs: { + minLiquidity: string + riskYieldMultiplierInBps: number + }[], + BP_FACTOR: number, +): BigNumber => { + const flcTotalAssetsAfterRisk = flcConfigs.reduce( + (acc, flcConfig) => + acc.add( + BigNumber.from(flcConfig.minLiquidity) + .mul(flcConfig.riskYieldMultiplierInBps) + .div(BP_FACTOR), + ), + BigNumber.from(0), + ) + return flcTotalAssetsAfterRisk +} + +const getPoolApyV2Base = ( + protocolFeeInBps: number, + yieldInBps: number, + rewardRateInBpsForPoolOwner: number, + rewardRateInBpsForEA: number, + liquidityCap: string, + seniorDeployedAssets: string, + juniorDeployedAssets: string, + maxSeniorJuniorRatio: number, + fixedSeniorYieldInBps: number, + tranchesRiskAdjustmentInBps: number, + BP_FACTOR: number, + FirstLossCoverIndexes: string[] = [], + flcConfigs: FclConfig[] = [], +): PoolApy => { + const APY = yieldInBps / BP_FACTOR + + const liquidityCapBN = BigNumber.from(liquidityCap) + const seniorDeployedAssetsBN = BigNumber.from(seniorDeployedAssets) + const juniorDeployedAssetsBN = BigNumber.from(juniorDeployedAssets) + const totalDeployedAssetsBN = seniorDeployedAssetsBN.add( + juniorDeployedAssetsBN, + ) + const seniorMaxAssetsBN = liquidityCapBN + .sub(totalDeployedAssetsBN) + .add(seniorDeployedAssetsBN) + const currentMaxSeniorJuniorRatio = seniorMaxAssetsBN + .div(juniorDeployedAssetsBN) + .toNumber() + + let juniorAssetsBN = liquidityCapBN.div(maxSeniorJuniorRatio + 1) + let seniorAssetsBN = liquidityCapBN.sub(juniorAssetsBN) + if (currentMaxSeniorJuniorRatio < maxSeniorJuniorRatio) { + juniorAssetsBN = juniorDeployedAssetsBN + seniorAssetsBN = seniorMaxAssetsBN + } + + const totalProfitBN = liquidityCapBN + .mul(Math.round(APY * BP_FACTOR)) + .div(BP_FACTOR) + const postPoolProfitRatio = + (1 - protocolFeeInBps / BP_FACTOR) * + (1 - + rewardRateInBpsForPoolOwner / BP_FACTOR - + rewardRateInBpsForEA / BP_FACTOR) + const poolPostProfitBN = totalProfitBN + .mul(Math.round(postPoolProfitRatio * BP_FACTOR)) + .div(BP_FACTOR) + const blendedApy = + poolPostProfitBN.mul(BP_FACTOR).div(liquidityCap).toNumber() / BP_FACTOR + + let seniorTrancheApy = 0 + let seniorPostProfitBN = BigNumber.from(0) + if (fixedSeniorYieldInBps > 0) { + seniorTrancheApy = fixedSeniorYieldInBps / BP_FACTOR + seniorPostProfitBN = poolPostProfitBN.sub( + seniorAssetsBN + .mul(Math.round(seniorTrancheApy * BP_FACTOR)) + .div(BP_FACTOR), + ) + } else { + const riskAdjustment = tranchesRiskAdjustmentInBps / BP_FACTOR + const seniorProfitBN = seniorAssetsBN + .mul( + Math.round( + postPoolProfitRatio * (1 - riskAdjustment) * APY * BP_FACTOR, + ), + ) + .div(BP_FACTOR) + seniorTrancheApy = postPoolProfitRatio * (1 - riskAdjustment) * APY + seniorPostProfitBN = poolPostProfitBN.sub(seniorProfitBN) + } + + const flcTotalAssetsAfterRiskBN = getFlcTotalAssetsAfterRisk( + flcConfigs, + BP_FACTOR, + ) + const weightBN = juniorAssetsBN.add(flcTotalAssetsAfterRiskBN) + + const juniorProfitBN = juniorAssetsBN.mul(seniorPostProfitBN).div(weightBN) + const juniorTrancheApy = + juniorProfitBN.mul(BP_FACTOR).div(juniorAssetsBN).toNumber() / BP_FACTOR + const flcTotalProfitBN = flcTotalAssetsAfterRiskBN + .mul(seniorPostProfitBN) + .div(weightBN) + const flcConfigsWithApy = flcConfigs.map((flcConfig) => { + const minLiquidityBN = BigNumber.from(flcConfig.minLiquidity) + const flcAssetsAfterRiskBN = minLiquidityBN + .mul(flcConfig.riskYieldMultiplierInBps) + .div(BP_FACTOR) + + let apy = 0 + if (minLiquidityBN.gt(0) && flcAssetsAfterRiskBN.gt(0)) { + const flcProfitBN = flcAssetsAfterRiskBN + .mul(flcTotalProfitBN) + .div(flcTotalAssetsAfterRiskBN) + apy = + flcProfitBN.mul(BP_FACTOR).div(flcConfig.minLiquidity).toNumber() / + BP_FACTOR + } + + return { + ...flcConfig, + firstLossCoverIndex: Number(FirstLossCoverIndexes[flcConfig.flcIndex]), + apy, + } + }) + + return { + flcConfigsWithApy, + blendedApy, + seniorTrancheApy, + juniorTrancheApy, + } +} + +export const getPoolApyV2 = ( + protocolFeeInBps: number, + yieldInBps: number, + rewardRateInBpsForPoolOwner: number, + rewardRateInBpsForEA: number, + liquidityCap: string, + seniorDeployedAssets: string, + juniorDeployedAssets: string, + maxSeniorJuniorRatio: number, + fixedSeniorYieldInBps: number, + tranchesRiskAdjustmentInBps: number, + BP_FACTOR: number, + FirstLossCoverIndexes: string[] = [], + flcConfigs: FclConfig[] = [], +): PoolApy & { maxJuniorTrancheApy: number } => { + const realApyResult = getPoolApyV2Base( + protocolFeeInBps, + yieldInBps, + rewardRateInBpsForPoolOwner, + rewardRateInBpsForEA, + liquidityCap, + seniorDeployedAssets, + juniorDeployedAssets, + maxSeniorJuniorRatio, + fixedSeniorYieldInBps, + tranchesRiskAdjustmentInBps, + BP_FACTOR, + FirstLossCoverIndexes, + flcConfigs, + ) + + const liquidityCapBN = BigNumber.from(liquidityCap) + const juniorAssetsBN = liquidityCapBN.div(maxSeniorJuniorRatio + 1) + const seniorAssetsBN = liquidityCapBN.sub(juniorAssetsBN) + const maxJuniorApyResult = getPoolApyV2Base( + protocolFeeInBps, + yieldInBps, + rewardRateInBpsForPoolOwner, + rewardRateInBpsForEA, + liquidityCap, + seniorAssetsBN.toString(), + juniorAssetsBN.toString(), + maxSeniorJuniorRatio, + fixedSeniorYieldInBps, + tranchesRiskAdjustmentInBps, + BP_FACTOR, + ) + + return { + ...realApyResult, + maxJuniorTrancheApy: maxJuniorApyResult.juniorTrancheApy, + } +} diff --git a/packages/huma-shared/src/utils/index.ts b/packages/huma-shared/src/utils/index.ts index 35deb979..81032dd7 100644 --- a/packages/huma-shared/src/utils/index.ts +++ b/packages/huma-shared/src/utils/index.ts @@ -1,14 +1,18 @@ -export * from './JsonRpcConnector' +export * from './apy' export * from './chain' export * from './common' export * from './config' export * from './const' export * from './credit' +export * from './env' export * from './errors' +export * from './JsonRpcConnector' export * from './jsonRpcEndpoints' +export * from './notifi' export * from './number' export * from './pool' export * from './poolContract' +export * from './realWorldReceivable' export * from './request' export * from './scientificToDecimal' export * from './string' @@ -16,6 +20,3 @@ export * from './style' export * from './time' export * from './transaction' export * from './web3' -export * from './notifi' -export * from './realWorldReceivable' -export * from './env' diff --git a/packages/huma-shared/src/v2/utils/poolConfigContract.ts b/packages/huma-shared/src/v2/utils/poolConfigContract.ts index 8c83ac56..c61d1f50 100644 --- a/packages/huma-shared/src/v2/utils/poolConfigContract.ts +++ b/packages/huma-shared/src/v2/utils/poolConfigContract.ts @@ -1,26 +1,9 @@ import { BigNumber } from 'ethers' +import { getPoolApyV2 } from '../../utils/apy' import { FirstLossCoverIndex } from '../types' import { BP_FACTOR } from './const' -const getFlcTotalAssetsAfterRisk = ( - flcConfigs: { - minLiquidity: BigNumber - riskYieldMultiplierInBps: number - }[], -): BigNumber => { - const flcTotalAssetsAfterRisk = flcConfigs.reduce( - (acc, flcConfig) => - acc.add( - flcConfig.minLiquidity - .mul(flcConfig.riskYieldMultiplierInBps) - .div(BP_FACTOR), - ), - BigNumber.from(0), - ) - return flcTotalAssetsAfterRisk -} - export const getPoolOverviewV2 = ( protocolFeeInBps: number, yieldInBps: number, @@ -38,113 +21,23 @@ export const getPoolOverviewV2 = ( flcIndex: number maxLiquidity: BigNumber }[], -): { - flcConfigsWithApy: { - firstLossCoverIndex: number - maxLiquidity: BigNumber - minLiquidity: BigNumber - riskYieldMultiplierInBps: number - apy: number - }[] - blendedApy: number - seniorTrancheApy: number - juniorTrancheApy: number -} => { - const BP_FACTOR_NUMBER = BP_FACTOR.toNumber() - const APY = yieldInBps / BP_FACTOR_NUMBER - - const totalDeployedAssets = seniorDeployedAssets.add(juniorDeployedAssets) - const seniorMaxAssets = liquidityCap - .sub(totalDeployedAssets) - .add(seniorDeployedAssets) - const currentMaxSeniorJuniorRatio = seniorMaxAssets - .div(juniorDeployedAssets) - .toNumber() - - let juniorAssets = liquidityCap.div(defaultMaxSeniorJuniorRatio + 1) - let seniorAssets = liquidityCap.sub(juniorAssets) - if (currentMaxSeniorJuniorRatio < defaultMaxSeniorJuniorRatio) { - juniorAssets = juniorDeployedAssets - seniorAssets = seniorMaxAssets - } - - const totalProfit = liquidityCap - .mul(Math.round(APY * BP_FACTOR_NUMBER)) - .div(BP_FACTOR_NUMBER) - const postPoolProfitRatio = - (1 - protocolFeeInBps / BP_FACTOR_NUMBER) * - (1 - - rewardRateInBpsForPoolOwner / BP_FACTOR_NUMBER - - rewardRateInBpsForEA / BP_FACTOR_NUMBER) - const poolPostProfit = totalProfit - .mul(Math.round(postPoolProfitRatio * BP_FACTOR_NUMBER)) - .div(BP_FACTOR_NUMBER) - const blendedApy = - poolPostProfit.mul(BP_FACTOR_NUMBER).div(liquidityCap).toNumber() / - BP_FACTOR_NUMBER - - let seniorTrancheApy = 0 - let seniorPostProfit = BigNumber.from(0) - if (fixedSeniorYieldInBps > 0) { - seniorTrancheApy = fixedSeniorYieldInBps / BP_FACTOR_NUMBER - seniorPostProfit = poolPostProfit.sub( - seniorAssets - .mul(Math.round(seniorTrancheApy * BP_FACTOR_NUMBER)) - .div(BP_FACTOR_NUMBER), - ) - } else { - const riskAdjustment = tranchesRiskAdjustmentInBps / BP_FACTOR_NUMBER - const seniorProfit = seniorAssets - .mul( - Math.round( - postPoolProfitRatio * (1 - riskAdjustment) * APY * BP_FACTOR_NUMBER, - ), - ) - .div(BP_FACTOR_NUMBER) - seniorTrancheApy = postPoolProfitRatio * (1 - riskAdjustment) * APY - seniorPostProfit = poolPostProfit.sub(seniorProfit) - } - - const flcTotalAssetsAfterRisk = getFlcTotalAssetsAfterRisk(flcConfigs) - const weight = juniorAssets.add(flcTotalAssetsAfterRisk) - - const juniorProfit = juniorAssets.mul(seniorPostProfit).div(weight) - const juniorTrancheApy = - juniorProfit.mul(BP_FACTOR_NUMBER).div(juniorAssets).toNumber() / - BP_FACTOR_NUMBER - const flcTotalProfit = flcTotalAssetsAfterRisk - .mul(seniorPostProfit) - .div(weight) - const flcConfigsWithApy = flcConfigs.map((flcConfig) => { - const flcAssetsAfterRisk = flcConfig.minLiquidity - .mul(flcConfig.riskYieldMultiplierInBps) - .div(BP_FACTOR) - - let apy = 0 - if (flcConfig.minLiquidity.gt(0) && flcAssetsAfterRisk.gt(0)) { - const flcProfit = flcAssetsAfterRisk - .mul(flcTotalProfit) - .div(flcTotalAssetsAfterRisk) - apy = - flcProfit.mul(BP_FACTOR_NUMBER).div(flcConfig.minLiquidity).toNumber() / - BP_FACTOR_NUMBER - } - - return { - firstLossCoverIndex: Number( - Object.keys(FirstLossCoverIndex)[flcConfig.flcIndex], - ), - maxLiquidity: flcConfig.maxLiquidity, - minLiquidity: flcConfig.minLiquidity, - riskYieldMultiplierInBps: flcConfig.riskYieldMultiplierInBps, - apy, - } - }) - - return { - flcConfigsWithApy, - blendedApy, - seniorTrancheApy, - juniorTrancheApy, - } -} +) => + getPoolApyV2( + protocolFeeInBps, + yieldInBps, + rewardRateInBpsForPoolOwner, + rewardRateInBpsForEA, + liquidityCap.toString(), + seniorDeployedAssets.toString(), + juniorDeployedAssets.toString(), + defaultMaxSeniorJuniorRatio, + fixedSeniorYieldInBps, + tranchesRiskAdjustmentInBps, + BP_FACTOR.toNumber(), + Object.keys(FirstLossCoverIndex), + flcConfigs.map((flc) => ({ + ...flc, + maxLiquidity: flc.maxLiquidity.toString(), + minLiquidity: flc.minLiquidity.toString(), + })), + ) diff --git a/packages/huma-web-shared/src/solana/types/solanaPoolState.ts b/packages/huma-web-shared/src/solana/types/solanaPoolState.ts index 3805dd9f..eb5cfdf4 100644 --- a/packages/huma-web-shared/src/solana/types/solanaPoolState.ts +++ b/packages/huma-web-shared/src/solana/types/solanaPoolState.ts @@ -20,6 +20,7 @@ export type SolanaPoolState = { rangeApy?: string seniorTrancheApy?: number juniorTrancheApy?: number + maxJuniorTrancheApy?: number amountDefaulted?: number amountOriginated?: number amountRepaid?: number From fb3d0d241d351406196ae220627b4c1007233c88 Mon Sep 17 00:00:00 2001 From: Michael Liu Date: Wed, 30 Oct 2024 12:43:27 -0400 Subject: [PATCH 2/2] Fix name --- packages/huma-shared/src/utils/apy.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/huma-shared/src/utils/apy.ts b/packages/huma-shared/src/utils/apy.ts index aaab75a1..0459d6fd 100644 --- a/packages/huma-shared/src/utils/apy.ts +++ b/packages/huma-shared/src/utils/apy.ts @@ -1,19 +1,19 @@ import { BigNumber } from 'ethers' -export interface FclConfig { +export interface FlcConfig { minLiquidity: string riskYieldMultiplierInBps: number flcIndex: number maxLiquidity: string } -export interface FclConfigWithApy extends FclConfig { +export interface FlcConfigWithApy extends FlcConfig { apy: number firstLossCoverIndex: number } export interface PoolApy { - flcConfigsWithApy: FclConfigWithApy[] + flcConfigsWithApy: FlcConfigWithApy[] blendedApy: number seniorTrancheApy: number juniorTrancheApy: number @@ -51,7 +51,7 @@ const getPoolApyV2Base = ( tranchesRiskAdjustmentInBps: number, BP_FACTOR: number, FirstLossCoverIndexes: string[] = [], - flcConfigs: FclConfig[] = [], + flcConfigs: FlcConfig[] = [], ): PoolApy => { const APY = yieldInBps / BP_FACTOR @@ -167,7 +167,7 @@ export const getPoolApyV2 = ( tranchesRiskAdjustmentInBps: number, BP_FACTOR: number, FirstLossCoverIndexes: string[] = [], - flcConfigs: FclConfig[] = [], + flcConfigs: FlcConfig[] = [], ): PoolApy & { maxJuniorTrancheApy: number } => { const realApyResult = getPoolApyV2Base( protocolFeeInBps,