// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.10; import {GPv2SafeERC20} from '../../../dependencies/gnosis/contracts/GPv2SafeERC20.sol'; import {SafeCast} from 'openzeppelin-contracts/contracts/utils/math/SafeCast.sol'; import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol'; import {IAToken} from '../../../interfaces/IAToken.sol'; import {IPool} from '../../../interfaces/IPool.sol'; import {TokenMath} from '../../libraries/helpers/TokenMath.sol'; import {UserConfiguration} from '../configuration/UserConfiguration.sol'; import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol'; import {DataTypes} from '../types/DataTypes.sol'; import {ValidationLogic} from './ValidationLogic.sol'; import {ReserveLogic} from './ReserveLogic.sol'; /** * @title BorrowLogic library * @author Aave * @notice Implements the base logic for all the actions related to borrowing */ library BorrowLogic { using TokenMath for uint256; using ReserveLogic for DataTypes.ReserveCache; using ReserveLogic for DataTypes.ReserveData; using GPv2SafeERC20 for IERC20; using UserConfiguration for DataTypes.UserConfigurationMap; using ReserveConfiguration for DataTypes.ReserveConfigurationMap; using SafeCast for uint256; /** * @notice Implements the borrow feature. Borrowing allows users that provided collateral to draw liquidity from the * Aave protocol proportionally to their collateralization power. * @dev Emits the `Borrow()` event * @param reservesData The state of all the reserves * @param reservesList The addresses of all the active reserves * @param eModeCategories The configuration of all the efficiency mode categories * @param userConfig The user configuration mapping that tracks the supplied/borrowed assets * @param params The additional parameters needed to execute the borrow function */ function executeBorrow( mapping(address => DataTypes.ReserveData) storage reservesData, mapping(uint256 => address) storage reservesList, mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories, DataTypes.UserConfigurationMap storage userConfig, DataTypes.ExecuteBorrowParams memory params ) external { DataTypes.ReserveData storage reserve = reservesData[params.asset]; DataTypes.ReserveCache memory reserveCache = reserve.cache(); reserve.updateState(reserveCache); uint256 amountScaled = params.amount.getVTokenMintScaledAmount( reserveCache.nextVariableBorrowIndex ); ValidationLogic.validateBorrow( reservesData, eModeCategories, DataTypes.ValidateBorrowParams({ reserveCache: reserveCache, asset: params.asset, amountScaled: amountScaled, interestRateMode: params.interestRateMode, userEModeCategory: params.userEModeCategory }) ); reserveCache.nextScaledVariableDebt = IVariableDebtToken(reserveCache.variableDebtTokenAddress) .mint( params.user, params.onBehalfOf, params.amount, amountScaled, reserveCache.nextVariableBorrowIndex ); uint16 cachedReserveId = reserve.id; if (!userConfig.isBorrowing(cachedReserveId)) { userConfig.setBorrowing(cachedReserveId, true); } reserve.updateInterestRatesAndVirtualBalance( reserveCache, params.asset, 0, params.releaseUnderlying ? params.amount : 0, params.interestRateStrategyAddress ); if (params.releaseUnderlying) { IAToken(reserveCache.aTokenAddress).transferUnderlyingTo(params.user, params.amount); } ValidationLogic.validateHFAndLtv( reservesData, reservesList, eModeCategories, userConfig, params.onBehalfOf, params.userEModeCategory, params.oracle ); emit IPool.Borrow( params.asset, params.user, params.onBehalfOf, params.amount, DataTypes.InterestRateMode.VARIABLE, reserve.currentVariableBorrowRate, params.referralCode ); } /** * @notice Implements the repay feature. Repaying transfers the underlying back to the aToken and clears the * equivalent amount of debt for the user by burning the corresponding debt token. * @dev Emits the `Repay()` event * @param reservesData The state of all the reserves * @param reservesList The addresses of all the active reserves * @param onBehalfOfConfig The user configuration mapping that tracks the supplied/borrowed assets * @param params The additional parameters needed to execute the repay function * @return The actual amount being repaid */ function executeRepay( mapping(address => DataTypes.ReserveData) storage reservesData, mapping(uint256 => address) storage reservesList, mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories, DataTypes.UserConfigurationMap storage onBehalfOfConfig, DataTypes.ExecuteRepayParams memory params ) external returns (uint256) { DataTypes.ReserveData storage reserve = reservesData[params.asset]; DataTypes.ReserveCache memory reserveCache = reserve.cache(); reserve.updateState(reserveCache); uint256 userDebtScaled = IVariableDebtToken(reserveCache.variableDebtTokenAddress) .scaledBalanceOf(params.onBehalfOf); uint256 userDebt = userDebtScaled.getVTokenBalance(reserveCache.nextVariableBorrowIndex); ValidationLogic.validateRepay( params.user, reserveCache, params.amount, params.interestRateMode, params.onBehalfOf, userDebtScaled ); uint256 paybackAmount = params.amount; if (params.useATokens && params.amount == type(uint256).max) { // Allows a user to repay with aTokens without leaving dust from interest. paybackAmount = IAToken(reserveCache.aTokenAddress) .scaledBalanceOf(params.user) .getATokenBalance(reserveCache.nextLiquidityIndex); } if (paybackAmount > userDebt) { paybackAmount = userDebt; } bool noMoreDebt; (noMoreDebt, reserveCache.nextScaledVariableDebt) = IVariableDebtToken( reserveCache.variableDebtTokenAddress ).burn({ from: params.onBehalfOf, scaledAmount: paybackAmount.getVTokenBurnScaledAmount(reserveCache.nextVariableBorrowIndex), index: reserveCache.nextVariableBorrowIndex }); reserve.updateInterestRatesAndVirtualBalance( reserveCache, params.asset, params.useATokens ? 0 : paybackAmount, 0, params.interestRateStrategyAddress ); if (noMoreDebt) { onBehalfOfConfig.setBorrowing(reserve.id, false); } // in case of aToken repayment the sender must always repay on behalf of itself if (params.useATokens) { // As aToken.burn rounds up the burned shares, we ensure at least an equivalent of >= paybackAmount is burned. bool zeroBalanceAfterBurn = IAToken(reserveCache.aTokenAddress).burn({ from: params.user, receiverOfUnderlying: reserveCache.aTokenAddress, amount: paybackAmount, scaledAmount: paybackAmount.getATokenBurnScaledAmount(reserveCache.nextLiquidityIndex), index: reserveCache.nextLiquidityIndex }); if (onBehalfOfConfig.isUsingAsCollateral(reserve.id)) { if (zeroBalanceAfterBurn) { onBehalfOfConfig.setUsingAsCollateral(reserve.id, params.asset, params.user, false); } if (onBehalfOfConfig.isBorrowingAny()) { ValidationLogic.validateHealthFactor( reservesData, reservesList, eModeCategories, onBehalfOfConfig, params.user, params.userEModeCategory, params.oracle ); } } } else { IERC20(params.asset).safeTransferFrom(params.user, reserveCache.aTokenAddress, paybackAmount); } emit IPool.Repay( params.asset, params.onBehalfOf, params.user, paybackAmount, params.useATokens ); return paybackAmount; } }