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

484 lines
17 KiB
Solidity
Raw Normal View History

2020-06-20 23:40:03 +00:00
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
2020-08-05 10:40:24 +00:00
pragma experimental ABIEncoderV2;
2020-06-20 23:40:03 +00:00
2020-10-15 13:41:56 +00:00
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
2020-06-30 12:09:28 +00:00
import {ReserveLogic} from './ReserveLogic.sol';
import {GenericLogic} from './GenericLogic.sol';
2020-08-20 07:51:21 +00:00
import {WadRayMath} from '../math/WadRayMath.sol';
import {PercentageMath} from '../math/PercentageMath.sol';
2020-10-15 13:41:56 +00:00
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
2020-08-20 07:51:21 +00:00
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';
2020-06-20 23:40:03 +00:00
2020-06-27 02:13:32 +00:00
/**
2020-06-30 12:09:28 +00:00
* @title ReserveLogic library
* @author Aave
* @notice Implements functions to validate specific action on the protocol.
*/
2020-06-20 23:40:03 +00:00
library ValidationLogic {
2020-07-08 22:16:05 +00:00
using ReserveLogic for ReserveLogic.ReserveData;
2020-06-30 12:09:28 +00:00
using SafeMath for uint256;
using WadRayMath for uint256;
using PercentageMath for uint256;
2020-08-12 17:36:58 +00:00
using SafeERC20 for IERC20;
2020-07-23 15:18:06 +00:00
using ReserveConfiguration for ReserveConfiguration.Map;
2020-08-05 10:40:24 +00:00
using UserConfiguration for UserConfiguration.Map;
2020-06-30 12:09:28 +00:00
uint256 public constant REBALANCE_UP_LIQUIDITY_RATE_THRESHOLD = 4000;
uint256 public constant REBALANCE_UP_USAGE_RATIO_THRESHOLD = 0.95 * 1e27; //usage ratio of 95%
2020-06-30 12:09:28 +00:00
/**
* @dev validates a deposit.
* @param reserve the reserve state on which the user is depositing
* @param amount the amount to be deposited
2020-06-30 12:09:28 +00:00
*/
function validateDeposit(ReserveLogic.ReserveData storage reserve, uint256 amount) external view {
2020-10-31 12:47:16 +00:00
(bool isActive, bool isFrozen, , ) = reserve.configuration.getFlags();
2020-07-23 15:18:06 +00:00
2020-11-10 13:11:01 +00:00
require(amount != 0, Errors.VL_INVALID_AMOUNT);
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
2020-10-31 12:47:16 +00:00
require(!isFrozen, Errors.VL_RESERVE_FROZEN);
2020-06-30 12:09:28 +00:00
}
/**
2020-08-18 19:19:11 +00:00
* @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
2020-10-29 10:57:43 +00:00
* @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
2020-06-30 12:09:28 +00:00
*/
2020-08-18 19:19:11 +00:00
function validateWithdraw(
address reserveAddress,
uint256 amount,
uint256 userBalance,
mapping(address => ReserveLogic.ReserveData) storage reservesData,
UserConfiguration.Map storage userConfig,
2020-10-06 13:51:48 +00:00
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) external view {
2020-08-18 19:19:11 +00:00
2020-11-10 15:29:43 +00:00
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);
2020-08-18 19:19:11 +00:00
require(
GenericLogic.balanceDecreaseAllowed(
reserveAddress,
2020-08-18 19:19:11 +00:00
msg.sender,
2020-10-15 15:57:54 +00:00
amount,
reservesData,
userConfig,
reserves,
2020-10-06 13:51:48 +00:00
reservesCount,
oracle
2020-08-18 19:19:11 +00:00
),
Errors.VL_TRANSFER_NOT_ALLOWED
2020-08-18 19:19:11 +00:00
);
2020-06-30 12:09:28 +00:00
}
struct ValidateBorrowLocalVars {
uint256 principalBorrowBalance;
uint256 currentLtv;
uint256 currentLiquidationThreshold;
uint256 requestedBorrowAmountETH;
uint256 amountOfCollateralNeededETH;
uint256 userCollateralBalanceETH;
uint256 userBorrowBalanceETH;
uint256 borrowBalanceIncrease;
uint256 currentReserveStableRate;
uint256 availableLiquidity;
uint256 finalUserBorrowRate;
uint256 healthFactor;
2020-07-08 22:16:05 +00:00
ReserveLogic.InterestRateMode rateMode;
2020-06-30 12:09:28 +00:00
bool healthFactorBelowThreshold;
2020-07-23 15:18:06 +00:00
bool isActive;
2020-10-31 12:47:16 +00:00
bool isFrozen;
2020-07-23 15:18:06 +00:00
bool borrowingEnabled;
bool stableRateBorrowingEnabled;
2020-06-30 12:09:28 +00:00
}
/**
* @dev validates a borrow.
* @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
2020-06-30 12:09:28 +00:00
*/
function validateBorrow(
address asset,
ReserveLogic.ReserveData storage reserve,
address userAddress,
uint256 amount,
uint256 amountInETH,
uint256 interestRateMode,
uint256 maxStableLoanPercent,
mapping(address => ReserveLogic.ReserveData) storage reservesData,
UserConfiguration.Map storage userConfig,
2020-10-06 13:51:48 +00:00
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
2020-06-30 12:09:28 +00:00
) external view {
ValidateBorrowLocalVars memory vars;
2020-10-31 12:47:16 +00:00
(vars.isActive, vars.isFrozen, vars.borrowingEnabled, vars.stableRateBorrowingEnabled) = reserve
.configuration
.getFlags();
2020-07-23 15:18:06 +00:00
require(vars.isActive, Errors.VL_NO_ACTIVE_RESERVE);
2020-10-31 12:47:16 +00:00
require(!vars.isFrozen, Errors.VL_RESERVE_FROZEN);
2020-11-10 13:11:01 +00:00
require(amount != 0, Errors.VL_INVALID_AMOUNT);
2020-06-30 12:09:28 +00:00
require(vars.borrowingEnabled, Errors.VL_BORROWING_NOT_ENABLED);
2020-06-30 12:09:28 +00:00
//validate interest rate mode
require(
uint256(ReserveLogic.InterestRateMode.VARIABLE) == interestRateMode ||
uint256(ReserveLogic.InterestRateMode.STABLE) == interestRateMode,
Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED
2020-06-30 12:09:28 +00:00
);
(
vars.userCollateralBalanceETH,
vars.userBorrowBalanceETH,
vars.currentLtv,
vars.currentLiquidationThreshold,
vars.healthFactor
) = GenericLogic.calculateUserAccountData(
userAddress,
reservesData,
userConfig,
reserves,
2020-10-06 13:51:48 +00:00
reservesCount,
oracle
2020-06-30 12:09:28 +00:00
);
require(vars.userCollateralBalanceETH > 0, Errors.VL_COLLATERAL_BALANCE_IS_0);
2020-06-30 12:09:28 +00:00
require(
vars.healthFactor > GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
2020-06-30 12:09:28 +00:00
//add the current already borrowed amount to the amount requested to calculate the total collateral needed.
vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(amountInETH).percentDiv(
2020-07-23 15:18:06 +00:00
vars.currentLtv
); //LTV is calculated in percentage
2020-06-30 12:09:28 +00:00
require(
vars.amountOfCollateralNeededETH <= vars.userCollateralBalanceETH,
Errors.VL_COLLATERAL_CANNOT_COVER_NEW_BORROW
2020-06-30 12:09:28 +00:00
);
2020-06-27 02:13:32 +00:00
/**
2020-06-30 12:09:28 +00:00
* 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 relatively small, configurable amount of the total
* liquidity
**/
2020-07-08 22:16:05 +00:00
if (vars.rateMode == ReserveLogic.InterestRateMode.STABLE) {
2020-06-30 12:09:28 +00:00
//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);
2020-06-30 12:09:28 +00:00
require(
!userConfig.isUsingAsCollateral(reserve.id) ||
reserve.configuration.getLtv() == 0 ||
amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress),
2020-10-31 12:10:26 +00:00
Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
2020-06-30 12:09:28 +00:00
);
vars.availableLiquidity = IERC20(asset).balanceOf(reserve.aTokenAddress);
2020-06-30 12:09:28 +00:00
//calculate the max available loan size in stable rate mode as a percentage of the
//available liquidity
uint256 maxLoanSizeStable = vars.availableLiquidity.percentMul(maxStableLoanPercent);
2020-06-30 12:09:28 +00:00
require(amount <= maxLoanSizeStable, Errors.VL_AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLE);
2020-06-20 23:40:03 +00:00
}
2020-06-30 12:09:28 +00:00
}
/**
* @dev validates a repay.
* @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
2020-08-21 17:10:48 +00:00
* @param stableDebt the borrow balance of the user
* @param variableDebt the borrow balance of the user
2020-06-30 12:09:28 +00:00
*/
function validateRepay(
ReserveLogic.ReserveData storage reserve,
uint256 amountSent,
ReserveLogic.InterestRateMode rateMode,
address onBehalfOf,
2020-08-21 17:10:48 +00:00
uint256 stableDebt,
uint256 variableDebt
2020-06-30 12:09:28 +00:00
) external view {
bool isActive = reserve.configuration.getActive();
2020-07-23 15:18:06 +00:00
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
2020-06-30 12:09:28 +00:00
2020-11-10 13:11:01 +00:00
require(amountSent > 0, Errors.VL_INVALID_AMOUNT);
2020-06-30 12:09:28 +00:00
require(
2020-08-21 17:10:48 +00:00
(stableDebt > 0 &&
ReserveLogic.InterestRateMode(rateMode) == ReserveLogic.InterestRateMode.STABLE) ||
2020-08-21 17:10:48 +00:00
(variableDebt > 0 &&
ReserveLogic.InterestRateMode(rateMode) == ReserveLogic.InterestRateMode.VARIABLE),
Errors.VL_NO_DEBT_OF_SELECTED_TYPE
2020-06-30 12:09:28 +00:00
);
require(
amountSent != uint256(-1) || msg.sender == onBehalfOf,
Errors.VL_NO_EXPLICIT_AMOUNT_TO_REPAY_ON_BEHALF
2020-06-30 12:09:28 +00:00
);
}
/**
* @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
2020-10-30 10:38:49 +00:00
* @param stableDebt the stable debt of the user
* @param variableDebt the variable debt of the user
* @param currentRateMode the rate mode of the borrow
2020-06-30 12:09:28 +00:00
*/
function validateSwapRateMode(
ReserveLogic.ReserveData storage reserve,
UserConfiguration.Map storage userConfig,
2020-10-30 10:36:53 +00:00
uint256 stableDebt,
uint256 variableDebt,
ReserveLogic.InterestRateMode currentRateMode
2020-06-30 12:09:28 +00:00
) external view {
2020-10-31 12:47:16 +00:00
(bool isActive, bool isFrozen, , bool stableRateEnabled) = reserve.configuration.getFlags();
2020-07-23 15:18:06 +00:00
require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
2020-10-31 12:47:16 +00:00
require(!isFrozen, Errors.VL_RESERVE_FROZEN);
2020-06-30 12:09:28 +00:00
if (currentRateMode == ReserveLogic.InterestRateMode.STABLE) {
2020-10-30 15:52:44 +00:00
require(stableDebt > 0, Errors.VL_NO_STABLE_RATE_LOAN_IN_RESERVE);
} else if (currentRateMode == ReserveLogic.InterestRateMode.VARIABLE) {
2020-10-30 15:52:44 +00:00
require(variableDebt > 0, Errors.VL_NO_VARIABLE_RATE_LOAN_IN_RESERVE);
2020-06-30 12:09:28 +00:00
/**
* 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);
2020-06-30 12:09:28 +00:00
require(
!userConfig.isUsingAsCollateral(reserve.id) ||
reserve.configuration.getLtv() == 0 ||
stableDebt.add(variableDebt) > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
2020-10-31 12:10:26 +00:00
Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
2020-06-30 12:09:28 +00:00
);
2020-07-13 08:54:08 +00:00
} else {
revert(Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED);
2020-07-07 10:07:31 +00:00
}
2020-06-30 12:09:28 +00:00
}
2020-11-10 15:07:13 +00:00
/**
* @dev validates a stable borrow rate rebalance
* @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(
ReserveLogic.ReserveData storage reserve,
address reserveAddress,
IERC20 stableDebtToken,
IERC20 variableDebtToken,
2020-11-10 15:07:13 +00:00
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
);
}
2020-06-30 12:09:28 +00:00
/**
* @dev validates the choice of a user of setting (or not) 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
2020-06-30 12:09:28 +00:00
*/
function validateSetUseReserveAsCollateral(
ReserveLogic.ReserveData storage reserve,
address reserveAddress,
bool useAsCollateral,
mapping(address => ReserveLogic.ReserveData) storage reservesData,
UserConfiguration.Map storage userConfig,
2020-10-06 13:51:48 +00:00
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
2020-06-30 12:09:28 +00:00
) external view {
uint256 underlyingBalance = IERC20(reserve.aTokenAddress).balanceOf(msg.sender);
2020-06-30 12:09:28 +00:00
require(underlyingBalance > 0, Errors.VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0);
2020-06-30 12:09:28 +00:00
require(
useAsCollateral ||
2020-06-30 12:09:28 +00:00
GenericLogic.balanceDecreaseAllowed(
reserveAddress,
2020-06-30 12:09:28 +00:00
msg.sender,
underlyingBalance,
reservesData,
userConfig,
reserves,
2020-10-06 13:51:48 +00:00
reservesCount,
oracle
2020-06-30 12:09:28 +00:00
),
Errors.VL_DEPOSIT_ALREADY_IN_USE
2020-06-30 12:09:28 +00:00
);
}
2020-09-03 13:17:46 +00:00
/**
2020-09-09 19:26:52 +00:00
* @dev validates a flashloan action
2020-10-22 18:37:50 +00:00
* @param assets the assets being flashborrowed
* @param amounts the amounts for each asset being borrowed
2020-09-09 19:26:52 +00:00
**/
function validateFlashloan(address[] memory assets, uint256[] memory amounts) internal pure {
2020-10-30 15:52:44 +00:00
require(assets.length == amounts.length, Errors.VL_INCONSISTENT_FLASHLOAN_PARAMS);
2020-09-03 13:17:46 +00:00
}
2020-09-13 08:08:14 +00:00
/**
* @dev Validates the liquidationCall() action
2020-09-13 08:08:14 +00:00
* @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
2020-09-13 08:08:14 +00:00
**/
function validateLiquidationCall(
2020-09-13 08:08:14 +00:00
ReserveLogic.ReserveData storage collateralReserve,
ReserveLogic.ReserveData storage principalReserve,
UserConfiguration.Map 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 (
2020-09-16 12:09:42 +00:00
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 (
2020-09-16 12:09:42 +00:00
uint256(Errors.CollateralManagerErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
Errors.LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED
);
}
if (userStableDebt == 0 && userVariableDebt == 0) {
return (
2020-09-16 12:09:42 +00:00
uint256(Errors.CollateralManagerErrors.CURRRENCY_NOT_BORROWED),
Errors.LPCM_SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
);
}
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.LPCM_NO_ERRORS);
}
/**
2020-10-29 10:57:43 +00:00
* @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 => ReserveLogic.ReserveData) storage reservesData,
UserConfiguration.Map storage userConfig,
2020-10-29 10:57:43 +00:00
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) internal view {
(, , , , uint256 healthFactor) = GenericLogic.calculateUserAccountData(
from,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
);
2020-10-29 10:57:43 +00:00
require(
healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
2020-10-30 12:40:06 +00:00
Errors.VL_TRANSFER_NOT_ALLOWED
2020-10-29 10:57:43 +00:00
);
}
2020-06-20 23:40:03 +00:00
}