Merge branch 'fix/45' into 'master'

Resolve "Update rebalance conditions"

Closes #45

See merge request aave-tech/protocol-v2!54
This commit is contained in:
Ernesto Boado 2020-09-21 20:14:34 +00:00
commit f756f44a8d
9 changed files with 140 additions and 96 deletions

View File

@ -10,9 +10,13 @@ interface IReserveInterestRateStrategy {
/**
* @dev returns the base variable borrow rate, in rays
*/
function baseVariableBorrowRate() external view returns (uint256);
/**
* @dev returns the maximum variable borrow rate
*/
function getMaxVariableBorrowRate() external view returns (uint256);
/**
* @dev calculates the liquidity, stable, and variable rates depending on the current utilization rate
* and the base parameters

View File

@ -91,6 +91,10 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy {
return _baseVariableBorrowRate;
}
function getMaxVariableBorrowRate() external override view returns (uint256) {
return _baseVariableBorrowRate.add(_variableRateSlope1).add(_variableRateSlope2);
}
struct CalcInterestRatesLocalVars {
uint256 totalBorrows;

View File

@ -28,21 +28,22 @@ import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import {ILendingPool} from '../interfaces/ILendingPool.sol';
import {LendingPoolStorage} from './LendingPoolStorage.sol';
import {IReserveInterestRateStrategy} from '../interfaces/IReserveInterestRateStrategy.sol';
/**
* @title LendingPool contract
* @notice Implements the actions of the LendingPool, and exposes accessory methods to fetch the users and reserve data
* @author Aave
**/
contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage {
using SafeMath for uint256;
using WadRayMath for uint256;
using PercentageMath for uint256;
using SafeERC20 for IERC20;
//main configuration parameters
uint256 public constant REBALANCE_DOWN_RATE_DELTA = (1e27) / 5;
uint256 public constant MAX_STABLE_RATE_BORROW_SIZE_PERCENT = 25;
uint256 public constant REBALANCE_UP_LIQUIDITY_RATE_THRESHOLD = 4000;
uint256 public constant REBALANCE_UP_USAGE_RATIO_THRESHOLD = 0.95 * 1e27; //usage ratio of 95%
uint256 public constant MAX_STABLE_RATE_BORROW_SIZE_PERCENT = 2500;
uint256 public constant FLASHLOAN_PREMIUM_TOTAL = 9;
uint256 public constant MAX_NUMBER_RESERVES = 128;
uint256 public constant LENDINGPOOL_REVISION = 0x2;
@ -50,13 +51,12 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
/**
* @dev only lending pools configurator can use functions affected by this modifier
**/
function onlyLendingPoolConfigurator() internal view {
function _onlyLendingPoolConfigurator() internal view {
require(
_addressesProvider.getLendingPoolConfigurator() == msg.sender,
Errors.CALLER_NOT_LENDING_POOL_CONFIGURATOR
);
}
/**
* @dev Function to make a function callable only when the contract is not paused.
*
@ -64,7 +64,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
*
* - The contract must not be paused.
*/
function whenNotPaused() internal view {
function _whenNotPaused() internal view {
require(!_paused, Errors.IS_PAUSED);
}
@ -94,7 +94,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
address onBehalfOf,
uint16 referralCode
) external override {
whenNotPaused();
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
ValidationLogic.validateDeposit(reserve, amount);
@ -123,7 +123,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
* @param amount the underlying amount to be redeemed
**/
function withdraw(address asset, uint256 amount) external override {
whenNotPaused();
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
address aToken = reserve.aTokenAddress;
@ -139,7 +139,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
ValidationLogic.validateWithdraw(
asset,
aToken,
amountToWithdraw,
userBalance,
_reserves,
@ -161,6 +160,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
emit Withdraw(asset, msg.sender, amount);
}
/**
* @dev returns the borrow allowance of the user
* @param asset The underlying asset of the debt token
* @param fromUser The user to giving allowance
* @param toUser The user to give allowance to
* @param interestRateMode Type of debt: 1 for stable, 2 for variable
* @return the current allowance of toUser
**/
function getBorrowAllowance(
address fromUser,
address toUser,
@ -184,7 +191,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint256 interestRateMode,
uint256 amount
) external override {
whenNotPaused();
_whenNotPaused();
address debtToken = _reserves[asset].getDebtTokenAddress(interestRateMode);
_borrowAllowance[debtToken][msg.sender][user] = amount;
@ -207,7 +214,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint16 referralCode,
address onBehalfOf
) external override {
whenNotPaused();
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
if (onBehalfOf != msg.sender) {
@ -247,7 +254,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint256 rateMode,
address onBehalfOf
) external override {
whenNotPaused();
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
@ -304,7 +311,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
* @param rateMode the rate mode that the user wants to swap
**/
function swapBorrowRateMode(address asset, uint256 rateMode) external override {
whenNotPaused();
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve);
@ -356,43 +363,50 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
* @param user the address of the user to be rebalanced
**/
function rebalanceStableBorrowRate(address asset, address user) external override {
whenNotPaused();
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
IStableDebtToken stableDebtToken = IStableDebtToken(reserve.stableDebtTokenAddress);
IERC20 stableDebtToken = IERC20(reserve.stableDebtTokenAddress);
IERC20 variableDebtToken = IERC20(reserve.variableDebtTokenAddress);
address aTokenAddress = reserve.aTokenAddress;
uint256 stableBorrowBalance = IERC20(address(stableDebtToken)).balanceOf(user);
uint256 stableBorrowBalance = IERC20(stableDebtToken).balanceOf(user);
// user must be borrowing on asset at a stable rate
require(stableBorrowBalance > 0, Errors.NOT_ENOUGH_STABLE_BORROW_BALANCE);
//if the utilization rate is below 95%, no rebalances are needed
uint256 totalBorrows = stableDebtToken.totalSupply().add(variableDebtToken.totalSupply()).wadToRay();
uint256 availableLiquidity = IERC20(asset).balanceOf(aTokenAddress).wadToRay();
uint256 usageRatio = totalBorrows == 0
? 0
: totalBorrows.rayDiv(availableLiquidity.add(totalBorrows));
uint256 rebalanceDownRateThreshold = WadRayMath.ray().add(REBALANCE_DOWN_RATE_DELTA).rayMul(
reserve.currentStableBorrowRate
);
//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.
//1. user stable borrow rate is below the current liquidity rate. The loan needs to be rebalanced,
//as this situation can be abused (user putting back the borrowed liquidity in the same reserve to earn on it)
//2. user stable rate is above the market avg borrow rate of a certain delta, and utilization rate is low.
//In this case, the user is paying an interest that is too high, and needs to be rescaled down.
uint256 userStableRate = stableDebtToken.getUserStableRate(user);
uint256 currentLiquidityRate = reserve.currentLiquidityRate;
uint256 maxVariableBorrowRate = IReserveInterestRateStrategy(
reserve
.interestRateStrategyAddress
)
.getMaxVariableBorrowRate();
require(
userStableRate < reserve.currentLiquidityRate || userStableRate > rebalanceDownRateThreshold,
usageRatio >= REBALANCE_UP_USAGE_RATIO_THRESHOLD &&
currentLiquidityRate <=
maxVariableBorrowRate.percentMul(REBALANCE_UP_LIQUIDITY_RATE_THRESHOLD),
Errors.INTEREST_RATE_REBALANCE_CONDITIONS_NOT_MET
);
reserve.updateState();
//burn old debt tokens, mint new ones
stableDebtToken.burn(user, stableBorrowBalance);
stableDebtToken.mint(user, stableBorrowBalance, reserve.currentStableBorrowRate);
IStableDebtToken(address(stableDebtToken)).burn(user, stableBorrowBalance);
IStableDebtToken(address(stableDebtToken)).mint(user, stableBorrowBalance, reserve.currentStableBorrowRate);
reserve.updateInterestRates(asset, reserve.aTokenAddress, 0, 0);
reserve.updateInterestRates(asset, aTokenAddress, 0, 0);
emit RebalanceStableBorrowRate(asset, user);
return;
}
/**
@ -401,7 +415,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
* @param useAsCollateral true if the user wants to user the deposit as collateral, false otherwise.
**/
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override {
whenNotPaused();
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
ValidationLogic.validateSetUseReserveAsCollateral(
@ -438,7 +452,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint256 purchaseAmount,
bool receiveAToken
) external override {
whenNotPaused();
_whenNotPaused();
address collateralManager = _addressesProvider.getLendingPoolCollateralManager();
//solium-disable-next-line
@ -482,7 +496,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
address receiver,
bytes calldata params
) external override {
whenNotPaused();
_whenNotPaused();
require(!_flashLiquidationLocked, Errors.REENTRANCY_NOT_ALLOWED);
_flashLiquidationLocked = true;
@ -538,7 +552,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
bytes calldata params,
uint16 referralCode
) external override {
whenNotPaused();
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
FlashLoanLocalVars memory vars;
@ -600,7 +614,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint256 amountToSwap,
bytes calldata params
) external override {
whenNotPaused();
_whenNotPaused();
address collateralManager = _addressesProvider.getLendingPoolCollateralManager();
//solium-disable-next-line
@ -740,7 +754,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
_reservesList,
_addressesProvider.getPriceOracle()
);
availableBorrowsETH = GenericLogic.calculateAvailableBorrowsETH(
totalCollateralETH,
totalBorrowsETH,
@ -799,7 +813,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
address variableDebtAddress,
address interestRateStrategyAddress
) external override {
onlyLendingPoolConfigurator();
_onlyLendingPoolConfigurator();
_reserves[asset].init(
aTokenAddress,
stableDebtAddress,
@ -819,12 +833,12 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
external
override
{
onlyLendingPoolConfigurator();
_onlyLendingPoolConfigurator();
_reserves[asset].interestRateStrategyAddress = rateStrategyAddress;
}
function setConfiguration(address asset, uint256 configuration) external override {
onlyLendingPoolConfigurator();
_onlyLendingPoolConfigurator();
_reserves[asset].configuration.data = configuration;
}
@ -980,7 +994,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
address user,
uint256 amount
) external override view returns (bool) {
whenNotPaused();
_whenNotPaused();
return
GenericLogic.balanceDecreaseAllowed(
asset,
@ -998,7 +1012,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
* @param val the boolean value to set the current pause state of LendingPool
*/
function setPause(bool val) external override {
onlyLendingPoolConfigurator();
_onlyLendingPoolConfigurator();
_paused = val;
if (_paused) {

View File

@ -6,7 +6,6 @@ import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {ReserveLogic} from '../logic/ReserveLogic.sol';
import {WadRayMath} from '../math/WadRayMath.sol';
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
import "@nomiclabs/buidler/console.sol";
/**
* @title ReserveConfiguration library
@ -143,7 +142,7 @@ library ReserveConfiguration {
* @param self the reserve configuration
* @param active the active state
**/
function setActive(ReserveConfiguration.Map memory self, bool active) internal {
function setActive(ReserveConfiguration.Map memory self, bool active) internal pure {
self.data = (self.data & ACTIVE_MASK) | (uint256(active ? 1 : 0) << 56);
}

View File

@ -254,7 +254,7 @@ library GenericLogic {
return (collateralBalanceETH.percentMul(liquidationThreshold)).wadDiv(borrowBalanceETH);
}
/**
/**
* @dev calculates the equivalent amount in ETH that an user can borrow, depending on the available collateral and the
* average Loan To Value.
* @param collateralBalanceETH the total collateral balance

View File

@ -127,7 +127,7 @@ library ReserveLogic {
* @return an address of the corresponding debt token from reserve configuration
**/
function getDebtTokenAddress(ReserveLogic.ReserveData storage reserve, uint256 interestRateMode)
internal
external
view
returns (address)
{
@ -297,7 +297,7 @@ library ReserveLogic {
uint40 stableSupplyUpdatedTimestamp;
}
/**
/**
* @dev mints part of the repaid interest to the reserve treasury, depending on the reserveFactor for the
* specific asset.
* @param reserve the reserve reserve to be updated
@ -358,7 +358,7 @@ library ReserveLogic {
IAToken(reserve.aTokenAddress).mintToTreasury(vars.amountToMint, newLiquidityIndex);
}
/**
/**
* @dev updates the reserve indexes and the timestamp of the update
* @param reserve the reserve reserve to be updated
* @param variableDebtToken the debt token address
@ -371,7 +371,6 @@ library ReserveLogic {
uint256 liquidityIndex,
uint256 variableBorrowIndex
) internal returns (uint256, uint256) {
uint40 timestamp = reserve.lastUpdateTimestamp;
uint256 currentLiquidityRate = reserve.currentLiquidityRate;

View File

@ -34,7 +34,7 @@ library ValidationLogic {
* @param reserve the reserve state on which the user is depositing
* @param amount the amount to be deposited
*/
function validateDeposit(ReserveLogic.ReserveData storage reserve, uint256 amount) internal view {
function validateDeposit(ReserveLogic.ReserveData storage reserve, uint256 amount) external view {
(bool isActive, bool isFreezed, , ) = reserve.configuration.getFlags();
require(amount > 0, Errors.AMOUNT_NOT_GREATER_THAN_0);
@ -45,13 +45,11 @@ library ValidationLogic {
/**
* @dev validates a withdraw action.
* @param reserveAddress the address of the reserve
* @param aTokenAddress the address of the aToken for the reserve
* @param amount the amount to be withdrawn
* @param userBalance the balance of the user
*/
function validateWithdraw(
address reserveAddress,
address aTokenAddress,
uint256 amount,
uint256 userBalance,
mapping(address => ReserveLogic.ReserveData) storage reservesData,

View File

@ -8,7 +8,6 @@ import {DebtTokenBase} from './base/DebtTokenBase.sol';
import {MathUtils} from '../libraries/math/MathUtils.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {IStableDebtToken} from './interfaces/IStableDebtToken.sol';
import "@nomiclabs/buidler/console.sol";
/**
* @title contract StableDebtToken

View File

@ -19,7 +19,7 @@
]
},
{
"description": "User 0 deposits 1000 DAI, user 1 deposits 1 ETH, borrows 100 DAI at a variable rate, user 0 rebalances user 1 (revert expected)",
"description": "User 0 deposits 1000 DAI, user 1 deposits 5 ETH, borrows 600 DAI at a variable rate, user 0 rebalances user 1 (revert expected)",
"actions": [
{
"name": "mint",
@ -51,7 +51,7 @@
"name": "mint",
"args": {
"reserve": "WETH",
"amount": "1",
"amount": "5",
"user": "1"
},
"expected": "success"
@ -69,7 +69,7 @@
"args": {
"reserve": "WETH",
"amount": "1",
"amount": "5",
"user": "1"
},
"expected": "success"
@ -78,8 +78,8 @@
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"borrowRateMode": "variable",
"amount": "250",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
@ -98,14 +98,16 @@
]
},
{
"description": "User 1 swaps to stable, user 0 tries to rebalance but the conditions are not met (revert expected)",
"description": "User 1 borrows another 200 at stable, user 0 tries to rebalance but the conditions are not met (revert expected)",
"actions": [
{
"name": "swapBorrowRateMode",
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "200",
"borrowRateMode": "stable",
"user": "1",
"borrowRateMode": "variable"
"timeTravel": "365"
},
"expected": "success"
},
@ -122,7 +124,59 @@
]
},
{
"description": "User 2 deposits ETH and borrows the remaining DAI, causing the stable rates to rise (liquidity rate < user 1 borrow rate). User 0 tries to rebalance user 1 (revert expected)",
"description": "User 1 borrows another 200 at stable, user 0 tries to rebalance but the conditions are not met (revert expected)",
"actions": [
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "200",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
"expected": "success"
},
{
"name": "rebalanceStableBorrowRate",
"args": {
"reserve": "DAI",
"user": "0",
"target": "1"
},
"expected": "revert",
"revertMessage": "Interest rate rebalance conditions were not met"
}
]
},
{
"description": "User 1 borrows another 100 at stable, user 0 tries to rebalance but the conditions are not met (revert expected)",
"actions": [
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
"expected": "success"
},
{
"name": "rebalanceStableBorrowRate",
"args": {
"reserve": "DAI",
"user": "0",
"target": "1"
},
"expected": "revert",
"revertMessage": "Interest rate rebalance conditions were not met"
}
]
},
{
"description": "User 2 deposits ETH and borrows the remaining DAI, causing the stable rates to rise (usage ratio = 94%). User 0 tries to rebalance user 1 (revert expected)",
"actions": [
{
"name": "mint",
@ -155,7 +209,7 @@
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"amount": "190",
"borrowRateMode": "variable",
"user": "2"
},
@ -174,40 +228,13 @@
]
},
{
"description": "User 2 borrows more DAI, causing the liquidity rate to rise above user 1 stable borrow rate User 0 rebalances user 1",
"description": "User 2 borrows the remaining DAI (usage ratio = 100%). User 0 rebalances user 1",
"actions": [
{
"name": "mint",
"args": {
"reserve": "WETH",
"amount": "3",
"user": "2"
},
"expected": "success"
},
{
"name": "approve",
"args": {
"reserve": "WETH",
"user": "2"
},
"expected": "success"
},
{
"name": "deposit",
"args": {
"reserve": "WETH",
"amount": "3",
"user": "2"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "700",
"amount": "60",
"borrowRateMode": "variable",
"user": "2"
},