hfUtils.ts
1 import { 2 BigNumberValue, 3 calculateHealthFactorFromBalancesBigUnits, 4 ComputedUserReserve, 5 UserReserveData, 6 valueToBigNumber, 7 } from '@aave/math-utils'; 8 import { BigNumber } from 'bignumber.js'; 9 import { 10 ComputedReserveData, 11 ComputedUserReserveData, 12 ExtendedFormattedUser, 13 } from 'src/hooks/app-data-provider/useAppDataProvider'; 14 15 interface CalculateHFAfterSwapProps { 16 fromAmount: BigNumberValue; 17 fromAssetData: ComputedReserveData; 18 fromAssetUserData: ComputedUserReserve; 19 toAmountAfterSlippage: BigNumberValue; 20 toAssetData: ComputedReserveData; 21 user: ExtendedFormattedUser; 22 } 23 24 interface CalculateHFAfterSwapRepayProps { 25 amountToReceiveAfterSwap: BigNumberValue; 26 amountToSwap: BigNumberValue; 27 fromAssetData: ComputedReserveData; 28 toAssetData: ComputedReserveData; 29 user: ExtendedFormattedUser; 30 repayWithUserReserve?: UserReserveData; 31 debt: string; 32 } 33 34 interface CalculateHFAfterWithdrawProps { 35 user: ExtendedFormattedUser; 36 userReserve: ComputedUserReserveData; 37 poolReserve: ComputedReserveData; 38 withdrawAmount: string; 39 } 40 41 export function calculateHFAfterSwap({ 42 fromAmount, 43 fromAssetData, 44 fromAssetUserData, 45 toAmountAfterSlippage, 46 toAssetData, 47 user, 48 }: CalculateHFAfterSwapProps) { 49 const fromEmode = fromAssetData.eModes.find((elem) => elem.id === user.userEmodeCategoryId); 50 const toEMode = toAssetData.eModes.find((elem) => elem.id === user.userEmodeCategoryId); 51 const reserveLiquidationThreshold = 52 user.isInEmode && fromEmode 53 ? fromEmode.eMode.formattedLiquidationThreshold 54 : fromAssetData.formattedReserveLiquidationThreshold; 55 56 // hf indicating how the state would be if we withdrew this amount. 57 // this is needed because on contracts hf can't be < 1 so in the case 58 // that fromHF < 1 we need to do a flashloan to not go below 59 // it takes into account if in emode as threshold is different 60 let hfEffectOfFromAmount = '0'; 61 62 if ( 63 fromAssetUserData.usageAsCollateralEnabledOnUser && 64 fromAssetData.reserveLiquidationThreshold !== '0' 65 ) { 66 hfEffectOfFromAmount = calculateHealthFactorFromBalancesBigUnits({ 67 collateralBalanceMarketReferenceCurrency: valueToBigNumber(fromAmount).multipliedBy( 68 fromAssetData.formattedPriceInMarketReferenceCurrency 69 ), 70 borrowBalanceMarketReferenceCurrency: user.totalBorrowsMarketReferenceCurrency, 71 currentLiquidationThreshold: reserveLiquidationThreshold, 72 }).toString(); 73 } 74 75 // HF after swap (same as supply calcs as it needs to calculate as if we where supplying new reserve) 76 let hfEffectOfToAmount = '0'; 77 if ( 78 (!user.isInIsolationMode && !toAssetData.isIsolated) || 79 (user.isInIsolationMode && 80 user.isolatedReserve?.underlyingAsset === toAssetData.underlyingAsset) 81 ) { 82 hfEffectOfToAmount = calculateHealthFactorFromBalancesBigUnits({ 83 collateralBalanceMarketReferenceCurrency: valueToBigNumber( 84 toAmountAfterSlippage 85 ).multipliedBy(toAssetData.formattedPriceInMarketReferenceCurrency), 86 borrowBalanceMarketReferenceCurrency: user.totalBorrowsMarketReferenceCurrency, 87 currentLiquidationThreshold: 88 user.isInEmode && toEMode 89 ? toEMode.eMode.formattedLiquidationThreshold 90 : toAssetData.formattedReserveLiquidationThreshold, 91 }).toString(); 92 } 93 94 return { 95 hfEffectOfFromAmount, 96 hfAfterSwap: 97 user.healthFactor === '-1' 98 ? valueToBigNumber('-1') 99 : valueToBigNumber(user.healthFactor).plus(hfEffectOfToAmount).minus(hfEffectOfFromAmount), 100 }; 101 } 102 103 export const calculateHFAfterRepay = ({ 104 user, 105 amountToReceiveAfterSwap, 106 amountToSwap, 107 fromAssetData, 108 toAssetData, 109 repayWithUserReserve, 110 debt, 111 }: CalculateHFAfterSwapRepayProps) => { 112 const fromEmode = fromAssetData.eModes.find((elem) => elem.id === user.userEmodeCategoryId); 113 // it takes into account if in emode as threshold is different 114 const reserveLiquidationThreshold = 115 user.isInEmode && fromEmode 116 ? fromEmode.eMode.formattedLiquidationThreshold 117 : fromAssetData.formattedReserveLiquidationThreshold; 118 119 // hf indicating how the state would be if we withdrew this amount. 120 // this is needed because on contracts hf can't be < 1 so in the case 121 // that fromHF < 1 we need to do a flashloan to not go below 122 let hfInitialEffectOfFromAmount = '0'; 123 124 if ( 125 repayWithUserReserve?.usageAsCollateralEnabledOnUser && 126 fromAssetData.usageAsCollateralEnabled 127 ) { 128 hfInitialEffectOfFromAmount = calculateHealthFactorFromBalancesBigUnits({ 129 collateralBalanceMarketReferenceCurrency: valueToBigNumber(amountToSwap).multipliedBy( 130 fromAssetData.formattedPriceInMarketReferenceCurrency 131 ), 132 borrowBalanceMarketReferenceCurrency: user.totalBorrowsMarketReferenceCurrency, 133 currentLiquidationThreshold: reserveLiquidationThreshold, 134 }).toString(); 135 } 136 137 const fromAmountInMarketReferenceCurrency = valueToBigNumber( 138 BigNumber.min(amountToReceiveAfterSwap, debt) 139 ) 140 .multipliedBy(toAssetData.priceInUSD) 141 .toString(10); 142 let debtLeftInMarketReference = valueToBigNumber(user.totalBorrowsUSD).minus( 143 fromAmountInMarketReferenceCurrency 144 ); 145 146 debtLeftInMarketReference = BigNumber.max(debtLeftInMarketReference, valueToBigNumber('0')); 147 148 const hfAfterRepayBeforeWithdraw = calculateHealthFactorFromBalancesBigUnits({ 149 collateralBalanceMarketReferenceCurrency: user.totalCollateralUSD, 150 borrowBalanceMarketReferenceCurrency: debtLeftInMarketReference.toString(10), 151 currentLiquidationThreshold: user.currentLiquidationThreshold, 152 }); 153 154 const hfRealEffectOfFromAmount = 155 fromAssetData.reserveLiquidationThreshold !== '0' && 156 repayWithUserReserve?.usageAsCollateralEnabledOnUser 157 ? calculateHealthFactorFromBalancesBigUnits({ 158 collateralBalanceMarketReferenceCurrency: valueToBigNumber(amountToSwap).multipliedBy( 159 fromAssetData.priceInUSD 160 ), 161 borrowBalanceMarketReferenceCurrency: debtLeftInMarketReference.toString(10), 162 currentLiquidationThreshold: fromAssetData.formattedReserveLiquidationThreshold, 163 }).toString() 164 : '0'; 165 166 const hfAfterSwap = hfAfterRepayBeforeWithdraw.eq(-1) 167 ? hfAfterRepayBeforeWithdraw 168 : hfAfterRepayBeforeWithdraw.minus(hfRealEffectOfFromAmount); 169 170 return { 171 hfEffectOfFromAmount: valueToBigNumber(hfInitialEffectOfFromAmount), 172 hfAfterSwap: hfAfterSwap.isLessThan(0) && !hfAfterSwap.eq(-1) ? 0 : hfAfterSwap, 173 }; 174 }; 175 176 export const calculateHFAfterWithdraw = ({ 177 user, 178 userReserve, 179 poolReserve, 180 withdrawAmount, 181 }: CalculateHFAfterWithdrawProps) => { 182 let totalCollateralInETHAfterWithdraw = valueToBigNumber( 183 user.totalCollateralMarketReferenceCurrency 184 ); 185 let liquidationThresholdAfterWithdraw = user.currentLiquidationThreshold; 186 let healthFactorAfterWithdraw = valueToBigNumber(user.healthFactor); 187 188 const userEMode = poolReserve.eModes.find((elem) => elem.id === user.userEmodeCategoryId); 189 190 const reserveLiquidationThreshold = 191 user.isInEmode && userEMode 192 ? userEMode.eMode.formattedLiquidationThreshold 193 : poolReserve.formattedReserveLiquidationThreshold; 194 195 if ( 196 userReserve?.usageAsCollateralEnabledOnUser && 197 poolReserve.reserveLiquidationThreshold !== '0' 198 ) { 199 const amountToWithdrawInEth = valueToBigNumber(withdrawAmount).multipliedBy( 200 poolReserve.formattedPriceInMarketReferenceCurrency 201 ); 202 totalCollateralInETHAfterWithdraw = 203 totalCollateralInETHAfterWithdraw.minus(amountToWithdrawInEth); 204 205 liquidationThresholdAfterWithdraw = valueToBigNumber( 206 user.totalCollateralMarketReferenceCurrency 207 ) 208 .multipliedBy(valueToBigNumber(user.currentLiquidationThreshold)) 209 .minus(valueToBigNumber(amountToWithdrawInEth).multipliedBy(reserveLiquidationThreshold)) 210 .div(totalCollateralInETHAfterWithdraw) 211 .toFixed(4, BigNumber.ROUND_DOWN); 212 213 healthFactorAfterWithdraw = calculateHealthFactorFromBalancesBigUnits({ 214 collateralBalanceMarketReferenceCurrency: totalCollateralInETHAfterWithdraw, 215 borrowBalanceMarketReferenceCurrency: user.totalBorrowsMarketReferenceCurrency, 216 currentLiquidationThreshold: liquidationThresholdAfterWithdraw, 217 }); 218 } 219 220 return healthFactorAfterWithdraw; 221 }; 222 223 export const calculateHFAfterSupply = ( 224 user: ExtendedFormattedUser, 225 poolReserve: ComputedReserveData, 226 supplyAmountInEth: BigNumber 227 ) => { 228 let healthFactorAfterDeposit = user ? valueToBigNumber(user.healthFactor) : '-1'; 229 230 const totalCollateralMarketReferenceCurrencyAfter = user 231 ? valueToBigNumber(user.totalCollateralMarketReferenceCurrency).plus(supplyAmountInEth) 232 : '-1'; 233 234 const liquidationThresholdAfter = user 235 ? valueToBigNumber(user.totalCollateralMarketReferenceCurrency) 236 .multipliedBy(user.currentLiquidationThreshold) 237 .plus(supplyAmountInEth.multipliedBy(poolReserve.formattedReserveLiquidationThreshold)) 238 .dividedBy(totalCollateralMarketReferenceCurrencyAfter) 239 : '-1'; 240 241 if ( 242 user && 243 ((!user.isInIsolationMode && !poolReserve.isIsolated) || 244 (user.isInIsolationMode && 245 user.isolatedReserve?.underlyingAsset === poolReserve.underlyingAsset)) 246 ) { 247 healthFactorAfterDeposit = calculateHealthFactorFromBalancesBigUnits({ 248 collateralBalanceMarketReferenceCurrency: totalCollateralMarketReferenceCurrencyAfter, 249 borrowBalanceMarketReferenceCurrency: valueToBigNumber( 250 user.totalBorrowsMarketReferenceCurrency 251 ), 252 currentLiquidationThreshold: liquidationThresholdAfter, 253 }); 254 } 255 256 return healthFactorAfterDeposit; 257 };