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

581 lines
22 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 {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol';
import {IStableDebtToken} from '../../../interfaces/IStableDebtToken.sol';
import {IScaledBalanceToken} from '../../../interfaces/IScaledBalanceToken.sol';
import {IAToken} from '../../../interfaces/IAToken.sol';
import {DataTypes} from '../types/DataTypes.sol';
import {IPriceOracleGetter} from '../../../interfaces/IPriceOracleGetter.sol';
import {Address} from '../../../dependencies/openzeppelin/contracts/Address.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;
using Address for address;
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 reserveCache The cached data of the reserve
* @param amount The amount to be deposited
*/
function validateDeposit(DataTypes.ReserveCache memory reserveCache, uint256 amount)
internal
view
{
(bool isActive, bool isFrozen, , , bool isPaused) =
reserveCache.reserveConfiguration.getFlagsMemory();
(, , , uint256 reserveDecimals, ) = reserveCache.reserveConfiguration.getParamsMemory();
uint256 supplyCap = reserveCache.reserveConfiguration.getSupplyCapMemory();
require(amount != 0, Errors.VL_INVALID_AMOUNT);
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED);
require(!isFrozen, Errors.VL_RESERVE_FROZEN);
require(
supplyCap == 0 ||
IAToken(reserveCache.aTokenAddress)
.scaledTotalSupply()
.rayMul(reserveCache.nextLiquidityIndex)
.add(amount)
.div(10**reserveDecimals) <
supplyCap,
Errors.VL_SUPPLY_CAP_EXCEEDED
);
}
/**
* @dev Validates a withdraw action
* @param reserveCache The cached data of the reserve
* @param amount The amount to be withdrawn
* @param userBalance The balance of the user
*/
function validateWithdraw(
DataTypes.ReserveCache memory reserveCache,
uint256 amount,
uint256 userBalance
) internal pure {
require(amount != 0, Errors.VL_INVALID_AMOUNT);
require(amount <= userBalance, Errors.VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE);
(bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED);
}
struct ValidateBorrowLocalVars {
uint256 currentLtv;
uint256 currentLiquidationThreshold;
uint256 amountOfCollateralNeededETH;
uint256 userCollateralBalanceETH;
uint256 userBorrowBalanceETH;
uint256 availableLiquidity;
uint256 healthFactor;
uint256 totalDebt;
uint256 totalSupplyVariableDebt;
uint256 reserveDecimals;
uint256 borrowCap;
uint256 amountInETH;
bool isActive;
bool isFrozen;
bool isPaused;
bool borrowingEnabled;
bool stableRateBorrowingEnabled;
}
/**
* @dev Validates a borrow action
* @param reserveCache the cached data of the reserve
* @param asset The address of the asset to borrow
* @param userAddress The address of the user
* @param amount The amount to be borrowed
* @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(
DataTypes.ReserveCache memory reserveCache,
address asset,
address userAddress,
uint256 amount,
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.reserveDecimals, ) = reserveCache.reserveConfiguration.getParamsMemory();
(
vars.isActive,
vars.isFrozen,
vars.borrowingEnabled,
vars.stableRateBorrowingEnabled,
vars.isPaused
) = reserveCache.reserveConfiguration.getFlagsMemory();
require(vars.isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!vars.isPaused, Errors.VL_RESERVE_PAUSED);
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.borrowCap = reserveCache.reserveConfiguration.getBorrowCapMemory();
if (vars.borrowCap != 0) {
{
vars.totalSupplyVariableDebt = reserveCache.currScaledVariableDebt.rayMul(
reserveCache.nextVariableBorrowIndex
);
vars.totalDebt = reserveCache.currTotalStableDebt.add(vars.totalSupplyVariableDebt).add(
amount
);
require(
vars.totalDebt.div(10**vars.reserveDecimals) < vars.borrowCap,
Errors.VL_BORROW_CAP_EXCEEDED
);
}
}
(
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
);
vars.amountInETH = IPriceOracleGetter(oracle).getAssetPrice(asset);
vars.amountInETH = vars.amountInETH.mul(amount).div(10**vars.reserveDecimals);
//add the current already borrowed amount to the amount requested to calculate the total collateral needed.
vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(vars.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(reservesData[asset].id) ||
reserveCache.reserveConfiguration.getLtvMemory() == 0 ||
amount > IERC20(reserveCache.aTokenAddress).balanceOf(userAddress),
Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
);
vars.availableLiquidity = IERC20(asset).balanceOf(reserveCache.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 reserveCache The cached data of the reserve
* @param amountSent The amount sent for the repayment. Can be an actual value or uint(-1)
* @param rateMode the interest rate mode of the debt being repaid
* @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(
address lastBorrower,
uint40 lastBorrowTimestamp,
DataTypes.ReserveCache memory reserveCache,
uint256 amountSent,
DataTypes.InterestRateMode rateMode,
address onBehalfOf,
uint256 stableDebt,
uint256 variableDebt
) external view {
(bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED);
require(amountSent > 0, Errors.VL_INVALID_AMOUNT);
require(
lastBorrower != onBehalfOf || lastBorrowTimestamp != uint40(block.timestamp),
Errors.VL_SAME_BLOCK_BORROW_REPAY
);
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 reserveCache The cached data of the reserve
* @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 debt being swapped
*/
function validateSwapRateMode(
DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache,
DataTypes.UserConfigurationMap storage userConfig,
uint256 stableDebt,
uint256 variableDebt,
DataTypes.InterestRateMode currentRateMode
) external view {
(bool isActive, bool isFrozen, , bool stableRateEnabled, bool isPaused) =
reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED);
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) ||
reserveCache.reserveConfiguration.getLtvMemory() == 0 ||
stableDebt.add(variableDebt) > IERC20(reserveCache.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 reserveCache The cached state of the reserve
* @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,
DataTypes.ReserveCache memory reserveCache,
address reserveAddress,
IERC20 stableDebtToken,
IERC20 variableDebtToken,
address aTokenAddress
) external view {
// to avoid potential abuses using flashloans, the rebalance stable rate must happen through an EOA
require(!address(msg.sender).isContract(), Errors.LP_CALLER_NOT_EOA);
(bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED);
//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 = reserveCache.currLiquidityRate;
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 reserveCache The cached data of the reserve
*/
function validateSetUseReserveAsCollateral(DataTypes.ReserveCache memory reserveCache)
external
view
{
uint256 underlyingBalance = IERC20(reserveCache.aTokenAddress).balanceOf(msg.sender);
(bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED);
require(underlyingBalance > 0, Errors.VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0);
}
/**
* @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,
mapping(address => DataTypes.ReserveData) storage reservesData
) internal view {
for (uint256 i = 0; i < assets.length; i++) {
require(!reservesData[assets[i]].configuration.getPaused(), Errors.VL_RESERVE_PAUSED);
}
require(assets.length == amounts.length, Errors.VL_INCONSISTENT_FLASHLOAN_PARAMS);
}
struct ValidateLiquidationCallLocalVars {
uint256 healthFactor;
bool collateralReserveActive;
bool collateralReservePaused;
bool principalReserveActive;
bool principalReservePaused;
bool isCollateralEnabled;
}
/**
* @dev Validates the liquidation action
* @param collateralReserve The reserve data of the collateral
* @param principalReserveCache The cached reserve data of the principal
* @param userConfig The user configuration
* @param totalDebt Total debt balance of the user
* @param user The address of the user being liquidated
* @param reservesData The mapping of the reserves data
* @param userConfig The user configuration mapping
* @param reserves The list of the reserves
* @param reservesCount The number of reserves in the list
* @param oracle The address of the price oracle
**/
function validateLiquidationCall(
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveCache memory principalReserveCache,
uint256 totalDebt,
address user,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) internal view returns (uint256, string memory) {
ValidateLiquidationCallLocalVars memory vars;
(vars.collateralReserveActive, , , , vars.collateralReservePaused) = collateralReserve
.configuration
.getFlagsMemory();
(vars.principalReserveActive, , , , vars.principalReservePaused) = principalReserveCache
.reserveConfiguration
.getFlagsMemory();
if (!vars.collateralReserveActive || !vars.principalReserveActive) {
return (
uint256(Errors.CollateralManagerErrors.NO_ACTIVE_RESERVE),
Errors.VL_NO_ACTIVE_RESERVE
);
}
if (vars.collateralReservePaused || vars.principalReservePaused) {
return (uint256(Errors.CollateralManagerErrors.PAUSED_RESERVE), Errors.VL_RESERVE_PAUSED);
}
(, , , , vars.healthFactor, ) = GenericLogic.calculateUserAccountData(
user,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
);
if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
return (
uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
Errors.LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD
);
}
vars.isCollateralEnabled =
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
userConfig.isUsingAsCollateral(collateralReserve.id);
//if collateral isn't enabled as collateral by user, it cannot be liquidated
if (!vars.isCollateralEnabled) {
return (
uint256(Errors.CollateralManagerErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
Errors.LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED
);
}
if (totalDebt == 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 the health factor of a user and the exposure cap for the asset being withdrawn
* @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 reservesCount The number of available reserves
* @param oracle The price oracle
*/
function validateHFAndExposureCap(
address collateral,
address from,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) external view {
DataTypes.ReserveData memory reserve = reservesData[collateral];
(, , uint256 ltv, uint256 liquidationThreshold, uint256 healthFactor, uint256 uncappedLtv) =
GenericLogic.calculateUserAccountData(
from,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
);
require(
healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
uint256 exposureCap = reserve.configuration.getExposureCapMemory();
if (exposureCap != 0) {
if (ltv < uncappedLtv) {
uint256 totalSupplyAtoken = IERC20(reserve.aTokenAddress).totalSupply();
(, , , uint256 reserveDecimals, ) = reserve.configuration.getParamsMemory();
bool isAssetCapped = totalSupplyAtoken.div(10**reserveDecimals) >= exposureCap;
require(isAssetCapped, Errors.VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED);
}
}
}
/**
* @dev Validates a transfer action
* @param reserve The reserve object
*/
function validateTransfer(DataTypes.ReserveData storage reserve) internal view {
require(!reserve.configuration.getPaused(), Errors.VL_RESERVE_PAUSED);
}
/**
* @dev Validates a drop reserve action
* @param reserve The reserve object
**/
function validateDropReserve(DataTypes.ReserveData storage reserve) external view {
require(
IERC20(reserve.stableDebtTokenAddress).totalSupply() == 0,
Errors.RL_STABLE_DEBT_NOT_ZERO
);
require(
IERC20(reserve.variableDebtTokenAddress).totalSupply() == 0,
Errors.RL_VARIABLE_DEBT_SUPPLY_NOT_ZERO
);
require(IERC20(reserve.aTokenAddress).totalSupply() == 0, Errors.RL_ATOKEN_SUPPLY_NOT_ZERO);
}
}