// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.10; import {GPv2SafeERC20} from '../../../dependencies/gnosis/contracts/GPv2SafeERC20.sol'; import {SafeCast} from '../../../dependencies/openzeppelin/contracts/SafeCast.sol'; import {IERC20} from '../../../dependencies/openzeppelin/contracts/IERC20.sol'; import {IStableDebtToken} from '../../../interfaces/IStableDebtToken.sol'; import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol'; import {IAToken} from '../../../interfaces/IAToken.sol'; import {UserConfiguration} from '../configuration/UserConfiguration.sol'; import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol'; import {Helpers} from '../helpers/Helpers.sol'; import {DataTypes} from '../types/DataTypes.sol'; import {ValidationLogic} from './ValidationLogic.sol'; import {ReserveLogic} from './ReserveLogic.sol'; import {IsolationModeLogic} from './IsolationModeLogic.sol'; /** * @title BorrowLogic library * @author Aave * @notice Implements the base logic for all the actions related to borrowing */ library BorrowLogic { 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; // See `IPool` for descriptions event Borrow( address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, DataTypes.InterestRateMode interestRateMode, uint256 borrowRate, uint16 indexed referralCode ); event Repay( address indexed reserve, address indexed user, address indexed repayer, uint256 amount, bool useATokens ); event RebalanceStableBorrowRate(address indexed reserve, address indexed user); event SwapBorrowRateMode( address indexed reserve, address indexed user, DataTypes.InterestRateMode interestRateMode ); event IsolationModeTotalDebtUpdated(address indexed asset, uint256 totalDebt); /** * @notice Implements the borrow feature. Borrowing allows users that provided collateral to draw liquidity from the * Aave protocol proportionally to their collateralization power. For isolated positions, it also increases the * isolated debt. * @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 ) public { DataTypes.ReserveData storage reserve = reservesData[params.asset]; DataTypes.ReserveCache memory reserveCache = reserve.cache(); reserve.updateState(reserveCache); ( bool isolationModeActive, address isolationModeCollateralAddress, uint256 isolationModeDebtCeiling ) = userConfig.getIsolationModeState(reservesData, reservesList); ValidationLogic.validateBorrow( reservesData, reservesList, eModeCategories, DataTypes.ValidateBorrowParams({ reserveCache: reserveCache, userConfig: userConfig, asset: params.asset, userAddress: params.onBehalfOf, amount: params.amount, interestRateMode: params.interestRateMode, maxStableLoanPercent: params.maxStableRateBorrowSizePercent, reservesCount: params.reservesCount, oracle: params.oracle, userEModeCategory: params.userEModeCategory, priceOracleSentinel: params.priceOracleSentinel, isolationModeActive: isolationModeActive, isolationModeCollateralAddress: isolationModeCollateralAddress, isolationModeDebtCeiling: isolationModeDebtCeiling }) ); uint256 currentStableRate = 0; bool isFirstBorrowing = false; if (params.interestRateMode == DataTypes.InterestRateMode.STABLE) { currentStableRate = reserve.currentStableBorrowRate; ( isFirstBorrowing, reserveCache.nextTotalStableDebt, reserveCache.nextAvgStableBorrowRate ) = IStableDebtToken(reserveCache.stableDebtTokenAddress).mint( params.user, params.onBehalfOf, params.amount, currentStableRate ); } else { (isFirstBorrowing, reserveCache.nextScaledVariableDebt) = IVariableDebtToken( reserveCache.variableDebtTokenAddress ).mint(params.user, params.onBehalfOf, params.amount, reserveCache.nextVariableBorrowIndex); } if (isFirstBorrowing) { userConfig.setBorrowing(reserve.id, true); } if (isolationModeActive) { uint256 nextIsolationModeTotalDebt = reservesData[isolationModeCollateralAddress] .isolationModeTotalDebt += (params.amount / 10 ** (reserveCache.reserveConfiguration.getDecimals() - ReserveConfiguration.DEBT_CEILING_DECIMALS)).toUint128(); emit IsolationModeTotalDebtUpdated( isolationModeCollateralAddress, nextIsolationModeTotalDebt ); } reserve.updateInterestRates( reserveCache, params.asset, 0, params.releaseUnderlying ? params.amount : 0 ); if (params.releaseUnderlying) { IAToken(reserveCache.aTokenAddress).transferUnderlyingTo(params.user, params.amount); } emit Borrow( params.asset, params.user, params.onBehalfOf, params.amount, params.interestRateMode, params.interestRateMode == DataTypes.InterestRateMode.STABLE ? currentStableRate : 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. For isolated positions, it also * reduces the isolated debt. * @dev Emits the `Repay()` event * @param reservesData The state of all the reserves * @param reservesList The addresses of all the active reserves * @param userConfig 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, DataTypes.UserConfigurationMap storage userConfig, DataTypes.ExecuteRepayParams memory params ) external returns (uint256) { DataTypes.ReserveData storage reserve = reservesData[params.asset]; DataTypes.ReserveCache memory reserveCache = reserve.cache(); reserve.updateState(reserveCache); (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt( params.onBehalfOf, reserveCache ); ValidationLogic.validateRepay( reserveCache, params.amount, params.interestRateMode, params.onBehalfOf, stableDebt, variableDebt ); uint256 paybackAmount = params.interestRateMode == DataTypes.InterestRateMode.STABLE ? stableDebt : variableDebt; // Allows a user to repay with aTokens without leaving dust from interest. if (params.useATokens && params.amount == type(uint256).max) { params.amount = IAToken(reserveCache.aTokenAddress).balanceOf(msg.sender); } if (params.amount < paybackAmount) { paybackAmount = params.amount; } if (params.interestRateMode == DataTypes.InterestRateMode.STABLE) { (reserveCache.nextTotalStableDebt, reserveCache.nextAvgStableBorrowRate) = IStableDebtToken( reserveCache.stableDebtTokenAddress ).burn(params.onBehalfOf, paybackAmount); } else { reserveCache.nextScaledVariableDebt = IVariableDebtToken( reserveCache.variableDebtTokenAddress ).burn(params.onBehalfOf, paybackAmount, reserveCache.nextVariableBorrowIndex); } reserve.updateInterestRates( reserveCache, params.asset, params.useATokens ? 0 : paybackAmount, 0 ); if (stableDebt + variableDebt - paybackAmount == 0) { userConfig.setBorrowing(reserve.id, false); } IsolationModeLogic.updateIsolatedDebtIfIsolated( reservesData, reservesList, userConfig, reserveCache, paybackAmount ); if (params.useATokens) { IAToken(reserveCache.aTokenAddress).burn( msg.sender, reserveCache.aTokenAddress, paybackAmount, reserveCache.nextLiquidityIndex ); } else { IERC20(params.asset).safeTransferFrom(msg.sender, reserveCache.aTokenAddress, paybackAmount); IAToken(reserveCache.aTokenAddress).handleRepayment( msg.sender, params.onBehalfOf, paybackAmount ); } emit Repay(params.asset, params.onBehalfOf, msg.sender, paybackAmount, params.useATokens); return paybackAmount; } /** * @notice Implements the rebalance stable borrow rate feature. In case of liquidity crunches on the protocol, stable * rate borrows might need to be rebalanced to bring back equilibrium between the borrow and supply APYs. * @dev The rules that define if a position can be rebalanced are implemented in `ValidationLogic.validateRebalanceStableBorrowRate()` * @dev Emits the `RebalanceStableBorrowRate()` event * @param reserve The state of the reserve of the asset being repaid * @param asset The asset of the position being rebalanced * @param user The user being rebalanced */ function executeRebalanceStableBorrowRate( DataTypes.ReserveData storage reserve, address asset, address user ) external { DataTypes.ReserveCache memory reserveCache = reserve.cache(); reserve.updateState(reserveCache); ValidationLogic.validateRebalanceStableBorrowRate(reserve, reserveCache, asset); IStableDebtToken stableDebtToken = IStableDebtToken(reserveCache.stableDebtTokenAddress); uint256 stableDebt = IERC20(address(stableDebtToken)).balanceOf(user); stableDebtToken.burn(user, stableDebt); (, reserveCache.nextTotalStableDebt, reserveCache.nextAvgStableBorrowRate) = stableDebtToken .mint(user, user, stableDebt, reserve.currentStableBorrowRate); reserve.updateInterestRates(reserveCache, asset, 0, 0); emit RebalanceStableBorrowRate(asset, user); } /** * @notice Implements the swap borrow rate feature. Borrowers can swap from variable to stable positions at any time. * @dev Emits the `Swap()` event * @param reserve The of the reserve of the asset being repaid * @param userConfig The user configuration mapping that tracks the supplied/borrowed assets * @param asset The asset of the position being swapped * @param interestRateMode The current interest rate mode of the position being swapped */ function executeSwapBorrowRateMode( DataTypes.ReserveData storage reserve, DataTypes.UserConfigurationMap storage userConfig, address asset, DataTypes.InterestRateMode interestRateMode ) external { DataTypes.ReserveCache memory reserveCache = reserve.cache(); reserve.updateState(reserveCache); (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt( msg.sender, reserveCache ); ValidationLogic.validateSwapRateMode( reserve, reserveCache, userConfig, stableDebt, variableDebt, interestRateMode ); if (interestRateMode == DataTypes.InterestRateMode.STABLE) { (reserveCache.nextTotalStableDebt, reserveCache.nextAvgStableBorrowRate) = IStableDebtToken( reserveCache.stableDebtTokenAddress ).burn(msg.sender, stableDebt); (, reserveCache.nextScaledVariableDebt) = IVariableDebtToken( reserveCache.variableDebtTokenAddress ).mint(msg.sender, msg.sender, stableDebt, reserveCache.nextVariableBorrowIndex); } else { reserveCache.nextScaledVariableDebt = IVariableDebtToken( reserveCache.variableDebtTokenAddress ).burn(msg.sender, variableDebt, reserveCache.nextVariableBorrowIndex); (, reserveCache.nextTotalStableDebt, reserveCache.nextAvgStableBorrowRate) = IStableDebtToken( reserveCache.stableDebtTokenAddress ).mint(msg.sender, msg.sender, variableDebt, reserve.currentStableBorrowRate); } reserve.updateInterestRates(reserveCache, asset, 0, 0); emit SwapBorrowRateMode(asset, msg.sender, interestRateMode); } }