/ src / utils / hfUtils.ts
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  };