/ src / utils / getMaxAmountAvailableToBorrow.ts
getMaxAmountAvailableToBorrow.ts
  1  import { FormatUserSummaryAndIncentivesResponse, valueToBigNumber } from '@aave/math-utils';
  2  import { BigNumber } from 'bignumber.js';
  3  import { ethers } from 'ethers';
  4  
  5  import {
  6    ComputedReserveData,
  7    ExtendedFormattedUser,
  8  } from '../hooks/app-data-provider/useAppDataProvider';
  9  import { roundToTokenDecimals } from './utils';
 10  
 11  // Subset of ComputedReserveData
 12  interface PoolReserveBorrowSubset {
 13    borrowCap: string;
 14    availableLiquidityUSD: string;
 15    totalDebt: string;
 16    isFrozen: boolean;
 17    decimals: number;
 18    formattedAvailableLiquidity: string;
 19    formattedPriceInMarketReferenceCurrency: string;
 20    borrowCapUSD: string;
 21  }
 22  
 23  /**
 24   * Calculates the maximum amount a user can borrow.
 25   * @param poolReserve
 26   * @param user
 27   */
 28  export function getMaxAmountAvailableToBorrow(
 29    poolReserve: PoolReserveBorrowSubset,
 30    user: FormatUserSummaryAndIncentivesResponse
 31  ): string {
 32    const availableInPoolUSD = poolReserve.availableLiquidityUSD;
 33    const availableForUserUSD = BigNumber.min(user.availableBorrowsUSD, availableInPoolUSD);
 34  
 35    const availableBorrowCap =
 36      poolReserve.borrowCap === '0'
 37        ? valueToBigNumber(ethers.constants.MaxUint256.toString())
 38        : valueToBigNumber(Number(poolReserve.borrowCap)).minus(
 39            valueToBigNumber(poolReserve.totalDebt)
 40          );
 41    const availableLiquidity = BigNumber.max(
 42      BigNumber.min(poolReserve.formattedAvailableLiquidity, availableBorrowCap),
 43      0
 44    );
 45  
 46    const availableForUserMarketReferenceCurrency = valueToBigNumber(
 47      user?.availableBorrowsMarketReferenceCurrency || 0
 48    ).div(poolReserve.formattedPriceInMarketReferenceCurrency);
 49  
 50    const maxUserAmountToBorrow = BigNumber.min(
 51      availableForUserMarketReferenceCurrency,
 52      availableLiquidity
 53    );
 54  
 55    const shouldAddMargin =
 56      /**
 57       * When the user is trying to do a max borrow
 58       */
 59      maxUserAmountToBorrow.gte(availableForUserMarketReferenceCurrency) ||
 60      /**
 61       * When a user has borrows we assume the debt is increasing faster then the supply.
 62       * That's a simplification that might not be true, but doesn't matter in most cases.
 63       */
 64      (user.totalBorrowsMarketReferenceCurrency !== '0' &&
 65        availableForUserUSD.lt(availableInPoolUSD)) ||
 66      /**
 67       * When the user could in theory borrow all, but the debt accrues the available decreases from block to block.
 68       */
 69      (availableForUserUSD.eq(availableInPoolUSD) && poolReserve.totalDebt !== '0') ||
 70      /**
 71       * When borrow cap could be reached and debt accumulates the debt would be surpassed.
 72       */
 73      (poolReserve.borrowCapUSD &&
 74        poolReserve.totalDebt !== '0' &&
 75        availableForUserUSD.gte(availableInPoolUSD)) ||
 76      /**
 77       * When the user would be able to borrow all the remaining ceiling we need to add a margin as existing debt.
 78       */
 79      (user.isInIsolationMode &&
 80        user.isolatedReserve?.isolationModeTotalDebt !== '0' &&
 81        // TODO: would be nice if userFormatter contained formatted reserve as this math is done twice now
 82        valueToBigNumber(user.isolatedReserve?.debtCeiling || '0')
 83          .minus(user.isolatedReserve?.isolationModeTotalDebt || '0')
 84          .shiftedBy(-(user.isolatedReserve?.debtCeilingDecimals || 0))
 85          .multipliedBy('0.99')
 86          .lt(user.availableBorrowsUSD));
 87  
 88    const amountWithMargin = shouldAddMargin
 89      ? maxUserAmountToBorrow.multipliedBy('0.99')
 90      : maxUserAmountToBorrow;
 91    return roundToTokenDecimals(amountWithMargin.toString(10), poolReserve.decimals);
 92  }
 93  
 94  /**
 95   * Calculates the maximum amount of GHO a user can mint
 96   * @param user
 97   */
 98  export function getMaxGhoMintAmount(
 99    user: FormatUserSummaryAndIncentivesResponse,
100    poolReserve: PoolReserveBorrowSubset
101  ) {
102    const userAvailableBorrows = valueToBigNumber(user?.availableBorrowsMarketReferenceCurrency || 0);
103  
104    const availableBorrowCap =
105      poolReserve.borrowCap === '0'
106        ? valueToBigNumber(ethers.constants.MaxUint256.toString())
107        : valueToBigNumber(Number(poolReserve.borrowCap)).minus(
108            valueToBigNumber(poolReserve.totalDebt)
109          );
110  
111    const maxAmountUserCanMint = BigNumber.max(
112      BigNumber.min(userAvailableBorrows, availableBorrowCap),
113      0
114    );
115  
116    const shouldAddMargin =
117      /**
118       * When a user has borrows we assume the debt is increasing faster then the supply.
119       * That's a simplification that might not be true, but doesn't matter in most cases.
120       */
121      user.totalBorrowsMarketReferenceCurrency !== '0' ||
122      /**
123       * When borrow cap could be reached and debt accumulates the debt would be surpassed.
124       */
125      (poolReserve.borrowCapUSD &&
126        poolReserve.totalDebt !== '0' &&
127        maxAmountUserCanMint.gte(availableBorrowCap)) ||
128      /**
129       * When the user would be able to borrow all the remaining ceiling we need to add a margin as existing debt.
130       */
131      (user.isInIsolationMode &&
132        user.isolatedReserve?.isolationModeTotalDebt !== '0' &&
133        // TODO: would be nice if userFormatter contained formatted reserve as this math is done twice now
134        valueToBigNumber(user.isolatedReserve?.debtCeiling || '0')
135          .minus(user.isolatedReserve?.isolationModeTotalDebt || '0')
136          .shiftedBy(-(user.isolatedReserve?.debtCeilingDecimals || 0))
137          .multipliedBy('0.99')
138          .lt(user.availableBorrowsUSD));
139  
140    const amountWithMargin = shouldAddMargin
141      ? maxAmountUserCanMint.multipliedBy('0.99')
142      : maxAmountUserCanMint;
143    return roundToTokenDecimals(amountWithMargin.toString(10), 18);
144  }
145  
146  export function assetCanBeBorrowedByUser(
147    {
148      borrowingEnabled,
149      isActive,
150      borrowableInIsolation,
151      eModes,
152      isFrozen,
153      isPaused,
154    }: ComputedReserveData,
155    user: ExtendedFormattedUser
156  ) {
157    if (!borrowingEnabled || !isActive || isFrozen || isPaused) return false;
158    if (user?.isInEmode) {
159      const reserveEmode = eModes.find((emode) => emode.id === user.userEmodeCategoryId);
160      if (!reserveEmode) return false;
161      return reserveEmode.borrowingEnabled;
162    }
163    if (user?.isInIsolationMode && !borrowableInIsolation) return false;
164    return true;
165  }