Skip to content

Commit

Permalink
Merge pull request #342 from 00labs/pool-apy
Browse files Browse the repository at this point in the history
unify get pool apy
  • Loading branch information
mliu authored Oct 30, 2024
2 parents 714cec5 + fb3d0d2 commit 3ff25fb
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 204 deletions.
87 changes: 15 additions & 72 deletions packages/huma-shared/src/solana/utils/poolUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BN } from '@coral-xyz/anchor'

import { getPoolApyV2 } from '../../utils/apy'
import { SOLANA_BP_FACTOR } from '../const'

export const getSolanaPoolApy = (
Expand All @@ -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,
Expand Down
209 changes: 209 additions & 0 deletions packages/huma-shared/src/utils/apy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { BigNumber } from 'ethers'

export interface FlcConfig {
minLiquidity: string
riskYieldMultiplierInBps: number
flcIndex: number
maxLiquidity: string
}

export interface FlcConfigWithApy extends FlcConfig {
apy: number
firstLossCoverIndex: number
}

export interface PoolApy {
flcConfigsWithApy: FlcConfigWithApy[]
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: FlcConfig[] = [],
): 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: FlcConfig[] = [],
): 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,
}
}
9 changes: 5 additions & 4 deletions packages/huma-shared/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
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'
export * from './style'
export * from './time'
export * from './transaction'
export * from './web3'
export * from './notifi'
export * from './realWorldReceivable'
export * from './env'
Loading

0 comments on commit 3ff25fb

Please sign in to comment.