aave-protocol-v2/contracts/protocol/libraries/logic/ValidationLogic.sol

470 lines
17 KiB
Solidity

// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {SafeMath} from '../../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../../../dependencies/openzeppelin/contracts/IERC20.sol';
import {ReserveLogic} from './ReserveLogic.sol';
import {GenericLogic} from './GenericLogic.sol';
import {WadRayMath} from '../math/WadRayMath.sol';
import {PercentageMath} from '../math/PercentageMath.sol';
import {SafeERC20} from '../../../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
import {Errors} from '../helpers/Errors.sol';
import {Helpers} from '../helpers/Helpers.sol';
import {IReserveInterestRateStrategy} from '../../../interfaces/IReserveInterestRateStrategy.sol';
import {DataTypes} from '../types/DataTypes.sol';
/**
* @title ReserveLogic library
* @author Aave
* @notice Implements functions to validate the different actions of the protocol
*/
library ValidationLogic {
using ReserveLogic for DataTypes.ReserveData;
using SafeMath for uint256;
using WadRayMath for uint256;
using PercentageMath for uint256;
using SafeERC20 for IERC20;
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
using UserConfiguration for DataTypes.UserConfigurationMap;
uint256 public constant REBALANCE_UP_LIQUIDITY_RATE_THRESHOLD = 4000;
uint256 public constant REBALANCE_UP_USAGE_RATIO_THRESHOLD = 0.95 * 1e27; //usage ratio of 95%
/**
* @dev Validates a deposit action
* @param reserve The reserve object on which the user is depositing
* @param amount The amount to be deposited
*/
function validateDeposit(DataTypes.ReserveData storage reserve, uint256 amount) external view {
(bool isActive, bool isFrozen, , ) = reserve.configuration.getFlags();
require(amount != 0, Errors.VL_INVALID_AMOUNT);
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isFrozen, Errors.VL_RESERVE_FROZEN);
}
/**
* @dev Validates a withdraw action
* @param reserveAddress The address of the reserve
* @param amount The amount to be withdrawn
* @param userBalance The balance of the user
* @param reservesData The reserves state
* @param userConfig The user configuration
* @param reserves The addresses of the reserves
* @param reservesCount The number of reserves
* @param oracle The price oracle
*/
function validateWithdraw(
address reserveAddress,
uint256 amount,
uint256 userBalance,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) external view {
require(amount != 0, Errors.VL_INVALID_AMOUNT);
require(amount <= userBalance, Errors.VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE);
(bool isActive, , , ) = reservesData[reserveAddress].configuration.getFlags();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(
GenericLogic.balanceDecreaseAllowed(
reserveAddress,
msg.sender,
amount,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
),
Errors.VL_TRANSFER_NOT_ALLOWED
);
}
struct ValidateBorrowLocalVars {
uint256 currentLtv;
uint256 currentLiquidationThreshold;
uint256 amountOfCollateralNeededETH;
uint256 userCollateralBalanceETH;
uint256 userBorrowBalanceETH;
uint256 availableLiquidity;
uint256 healthFactor;
bool isActive;
bool isFrozen;
bool borrowingEnabled;
bool stableRateBorrowingEnabled;
}
/**
* @dev Validates a borrow action
* @param asset The address of the asset to borrow
* @param reserve The reserve state from which the user is borrowing
* @param userAddress The address of the user
* @param amount The amount to be borrowed
* @param amountInETH The amount to be borrowed, in ETH
* @param interestRateMode The interest rate mode at which the user is borrowing
* @param maxStableLoanPercent The max amount of the liquidity that can be borrowed at stable rate, in percentage
* @param reservesData The state of all the reserves
* @param userConfig The state of the user for the specific reserve
* @param reserves The addresses of all the active reserves
* @param oracle The price oracle
*/
function validateBorrow(
address asset,
DataTypes.ReserveData storage reserve,
address userAddress,
uint256 amount,
uint256 amountInETH,
uint256 interestRateMode,
uint256 maxStableLoanPercent,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) external view {
ValidateBorrowLocalVars memory vars;
(vars.isActive, vars.isFrozen, vars.borrowingEnabled, vars.stableRateBorrowingEnabled) = reserve
.configuration
.getFlags();
require(vars.isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!vars.isFrozen, Errors.VL_RESERVE_FROZEN);
require(amount != 0, Errors.VL_INVALID_AMOUNT);
require(vars.borrowingEnabled, Errors.VL_BORROWING_NOT_ENABLED);
//validate interest rate mode
require(
uint256(DataTypes.InterestRateMode.VARIABLE) == interestRateMode ||
uint256(DataTypes.InterestRateMode.STABLE) == interestRateMode,
Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED
);
(
vars.userCollateralBalanceETH,
vars.userBorrowBalanceETH,
vars.currentLtv,
vars.currentLiquidationThreshold,
vars.healthFactor
) = GenericLogic.calculateUserAccountData(
userAddress,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
);
require(vars.userCollateralBalanceETH > 0, Errors.VL_COLLATERAL_BALANCE_IS_0);
require(
vars.healthFactor > GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
//add the current already borrowed amount to the amount requested to calculate the total collateral needed.
vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(amountInETH).percentDiv(
vars.currentLtv
); //LTV is calculated in percentage
require(
vars.amountOfCollateralNeededETH <= vars.userCollateralBalanceETH,
Errors.VL_COLLATERAL_CANNOT_COVER_NEW_BORROW
);
/**
* Following conditions need to be met if the user is borrowing at a stable rate:
* 1. Reserve must be enabled for stable rate borrowing
* 2. Users cannot borrow from the reserve if their collateral is (mostly) the same currency
* they are borrowing, to prevent abuses.
* 3. Users will be able to borrow only a portion of the total available liquidity
**/
if (interestRateMode == uint256(DataTypes.InterestRateMode.STABLE)) {
//check if the borrow mode is stable and if stable rate borrowing is enabled on this reserve
require(vars.stableRateBorrowingEnabled, Errors.VL_STABLE_BORROWING_NOT_ENABLED);
require(
!userConfig.isUsingAsCollateral(reserve.id) ||
reserve.configuration.getLtv() == 0 ||
amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress),
Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
);
vars.availableLiquidity = IERC20(asset).balanceOf(reserve.aTokenAddress);
//calculate the max available loan size in stable rate mode as a percentage of the
//available liquidity
uint256 maxLoanSizeStable = vars.availableLiquidity.percentMul(maxStableLoanPercent);
require(amount <= maxLoanSizeStable, Errors.VL_AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLE);
}
}
/**
* @dev Validates a repay action
* @param reserve The reserve state from which the user is repaying
* @param amountSent The amount sent for the repayment. Can be an actual value or uint(-1)
* @param onBehalfOf The address of the user msg.sender is repaying for
* @param stableDebt The borrow balance of the user
* @param variableDebt The borrow balance of the user
*/
function validateRepay(
DataTypes.ReserveData storage reserve,
uint256 amountSent,
DataTypes.InterestRateMode rateMode,
address onBehalfOf,
uint256 stableDebt,
uint256 variableDebt
) external view {
bool isActive = reserve.configuration.getActive();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(amountSent > 0, Errors.VL_INVALID_AMOUNT);
require(
(stableDebt > 0 &&
DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.STABLE) ||
(variableDebt > 0 &&
DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.VARIABLE),
Errors.VL_NO_DEBT_OF_SELECTED_TYPE
);
require(
amountSent != uint256(-1) || msg.sender == onBehalfOf,
Errors.VL_NO_EXPLICIT_AMOUNT_TO_REPAY_ON_BEHALF
);
}
/**
* @dev Validates a swap of borrow rate mode.
* @param reserve The reserve state on which the user is swapping the rate
* @param userConfig The user reserves configuration
* @param stableDebt The stable debt of the user
* @param variableDebt The variable debt of the user
* @param currentRateMode The rate mode of the borrow
*/
function validateSwapRateMode(
DataTypes.ReserveData storage reserve,
DataTypes.UserConfigurationMap storage userConfig,
uint256 stableDebt,
uint256 variableDebt,
DataTypes.InterestRateMode currentRateMode
) external view {
(bool isActive, bool isFrozen, , bool stableRateEnabled) = reserve.configuration.getFlags();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isFrozen, Errors.VL_RESERVE_FROZEN);
if (currentRateMode == DataTypes.InterestRateMode.STABLE) {
require(stableDebt > 0, Errors.VL_NO_STABLE_RATE_LOAN_IN_RESERVE);
} else if (currentRateMode == DataTypes.InterestRateMode.VARIABLE) {
require(variableDebt > 0, Errors.VL_NO_VARIABLE_RATE_LOAN_IN_RESERVE);
/**
* user wants to swap to stable, before swapping we need to ensure that
* 1. stable borrow rate is enabled on the reserve
* 2. user is not trying to abuse the reserve by depositing
* more collateral than he is borrowing, artificially lowering
* the interest rate, borrowing at variable, and switching to stable
**/
require(stableRateEnabled, Errors.VL_STABLE_BORROWING_NOT_ENABLED);
require(
!userConfig.isUsingAsCollateral(reserve.id) ||
reserve.configuration.getLtv() == 0 ||
stableDebt.add(variableDebt) > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
);
} else {
revert(Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED);
}
}
/**
* @dev Validates a stable borrow rate rebalance action
* @param reserve The reserve state on which the user is getting rebalanced
* @param reserveAddress The address of the reserve
* @param stableDebtToken The stable debt token instance
* @param variableDebtToken The variable debt token instance
* @param aTokenAddress The address of the aToken contract
*/
function validateRebalanceStableBorrowRate(
DataTypes.ReserveData storage reserve,
address reserveAddress,
IERC20 stableDebtToken,
IERC20 variableDebtToken,
address aTokenAddress
) external view {
(bool isActive, , , ) = reserve.configuration.getFlags();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
//if the usage ratio is below 95%, no rebalances are needed
uint256 totalDebt =
stableDebtToken.totalSupply().add(variableDebtToken.totalSupply()).wadToRay();
uint256 availableLiquidity = IERC20(reserveAddress).balanceOf(aTokenAddress).wadToRay();
uint256 usageRatio = totalDebt == 0 ? 0 : totalDebt.rayDiv(availableLiquidity.add(totalDebt));
//if the liquidity rate is below REBALANCE_UP_THRESHOLD of the max variable APR at 95% usage,
//then we allow rebalancing of the stable rate positions.
uint256 currentLiquidityRate = reserve.currentLiquidityRate;
uint256 maxVariableBorrowRate =
IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).getMaxVariableBorrowRate();
require(
usageRatio >= REBALANCE_UP_USAGE_RATIO_THRESHOLD &&
currentLiquidityRate <=
maxVariableBorrowRate.percentMul(REBALANCE_UP_LIQUIDITY_RATE_THRESHOLD),
Errors.LP_INTEREST_RATE_REBALANCE_CONDITIONS_NOT_MET
);
}
/**
* @dev Validates the action of setting an asset as collateral
* @param reserve The state of the reserve that the user is enabling or disabling as collateral
* @param reserveAddress The address of the reserve
* @param reservesData The data of all the reserves
* @param userConfig The state of the user for the specific reserve
* @param reserves The addresses of all the active reserves
* @param oracle The price oracle
*/
function validateSetUseReserveAsCollateral(
DataTypes.ReserveData storage reserve,
address reserveAddress,
bool useAsCollateral,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) external view {
uint256 underlyingBalance = IERC20(reserve.aTokenAddress).balanceOf(msg.sender);
require(underlyingBalance > 0, Errors.VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0);
require(
useAsCollateral ||
GenericLogic.balanceDecreaseAllowed(
reserveAddress,
msg.sender,
underlyingBalance,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
),
Errors.VL_DEPOSIT_ALREADY_IN_USE
);
}
/**
* @dev Validates a flashloan action
* @param assets The assets being flashborrowed
* @param amounts The amounts for each asset being borrowed
**/
function validateFlashloan(address[] memory assets, uint256[] memory amounts) internal pure {
require(assets.length == amounts.length, Errors.VL_INCONSISTENT_FLASHLOAN_PARAMS);
}
/**
* @dev Validates the liquidation action
* @param collateralReserve The reserve data of the collateral
* @param principalReserve The reserve data of the principal
* @param userConfig The user configuration
* @param userHealthFactor The user's health factor
* @param userStableDebt Total stable debt balance of the user
* @param userVariableDebt Total variable debt balance of the user
**/
function validateLiquidationCall(
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveData storage principalReserve,
DataTypes.UserConfigurationMap storage userConfig,
uint256 userHealthFactor,
uint256 userStableDebt,
uint256 userVariableDebt
) internal view returns (uint256, string memory) {
if (
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
) {
return (
uint256(Errors.CollateralManagerErrors.NO_ACTIVE_RESERVE),
Errors.VL_NO_ACTIVE_RESERVE
);
}
if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
return (
uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
Errors.LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD
);
}
bool isCollateralEnabled =
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
userConfig.isUsingAsCollateral(collateralReserve.id);
//if collateral isn't enabled as collateral by user, it cannot be liquidated
if (!isCollateralEnabled) {
return (
uint256(Errors.CollateralManagerErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
Errors.LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED
);
}
if (userStableDebt == 0 && userVariableDebt == 0) {
return (
uint256(Errors.CollateralManagerErrors.CURRRENCY_NOT_BORROWED),
Errors.LPCM_SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
);
}
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.LPCM_NO_ERRORS);
}
/**
* @dev Validates an aToken transfer
* @param from The user from which the aTokens are being transferred
* @param reservesData The state of all the reserves
* @param userConfig The state of the user for the specific reserve
* @param reserves The addresses of all the active reserves
* @param oracle The price oracle
*/
function validateTransfer(
address from,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) internal view {
(, , , , uint256 healthFactor) =
GenericLogic.calculateUserAccountData(
from,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
);
require(
healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.VL_TRANSFER_NOT_ALLOWED
);
}
}