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 }