Merge branch 'protocol-2.5' into feat/2.5-exposure-ceiling

This commit is contained in:
Hadrien Charlanes 2021-06-15 14:13:25 +02:00 committed by GitHub
commit 9727c0ab79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1870 additions and 340 deletions

View File

@ -167,6 +167,13 @@ interface ILendingPool {
uint256 variableBorrowIndex uint256 variableBorrowIndex
); );
/**
* @dev Emitted when the protocol treasury receives minted aTokens from the accrued interest.
* @param reserve the address of the reserve
* @param amountMinted the amount minted to the treasury
**/
event MintedToTreasury(address indexed reserve, uint256 amountMinted);
/** /**
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC * - E.g. User deposits 100 USDC and gets in return 100 aUSDC
@ -396,6 +403,8 @@ interface ILendingPool {
address interestRateStrategyAddress address interestRateStrategyAddress
) external; ) external;
function dropReserve(address reserve) external;
function setReserveInterestRateStrategyAddress(address reserve, address rateStrategyAddress) function setReserveInterestRateStrategyAddress(address reserve, address rateStrategyAddress)
external; external;
@ -458,4 +467,21 @@ interface ILendingPool {
function setPause(bool val) external; function setPause(bool val) external;
function paused() external view returns (bool); function paused() external view returns (bool);
function updateFlashBorrowerAuthorization(address flashBorrower, bool authorized) external;
function isFlashBorrowerAuthorized(address flashBorrower) external view returns (bool);
function updateFlashloanPremiums(
uint256 flashLoanPremiumTotal,
uint256 flashLoanPremiumToProtocol
) external;
function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() external view returns (uint256);
function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint256);
function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint256);
function MAX_NUMBER_RESERVES() external view returns (uint256);
} }

View File

@ -132,6 +132,12 @@ interface ILendingPoolConfigurator {
**/ **/
event ReserveUnpaused(address indexed asset); event ReserveUnpaused(address indexed asset);
/**
* @dev Emitted when a reserve is dropped
* @param asset The address of the underlying asset of the reserve
**/
event ReserveDropped(address indexed asset);
/** /**
* @dev Emitted when a reserve factor is updated * @dev Emitted when a reserve factor is updated
* @param asset The address of the underlying asset of the reserve * @param asset The address of the underlying asset of the reserve
@ -210,6 +216,18 @@ interface ILendingPoolConfigurator {
address indexed implementation address indexed implementation
); );
/**
* @dev Emitted when a new borrower is authorized (fees = 0)
* @param flashBorrower The address of the authorized borrower
**/
event FlashBorrowerAuthorized(address indexed flashBorrower);
/**
* @dev Emitted when a borrower is unauthorized
* @param flashBorrower The address of the unauthorized borrower
**/
event FlashBorrowerUnauthorized(address indexed flashBorrower);
/** /**
* @dev Emitted when a new risk admin is registered * @dev Emitted when a new risk admin is registered
* @param admin the newly registered admin * @param admin the newly registered admin
@ -222,6 +240,18 @@ interface ILendingPoolConfigurator {
**/ **/
event RiskAdminUnregistered(address indexed admin); event RiskAdminUnregistered(address indexed admin);
/**
* @dev Emitted when a the total premium on flashloans is updated
* @param flashloanPremiumTotal the new premium
**/
event FlashloanPremiumTotalUpdated(uint256 flashloanPremiumTotal);
/**
* @dev Emitted when a the part of the premium that goes to protoco lis updated
* @param flashloanPremiumToProtocol the new premium
**/
event FlashloanPremiumToProcolUpdated(uint256 flashloanPremiumToProtocol);
/** /**
* @dev Initializes reserves in batch * @dev Initializes reserves in batch
* @param input The array of reserves initialization parameters * @param input The array of reserves initialization parameters
@ -390,4 +420,37 @@ interface ILendingPoolConfigurator {
* @param admin The address of the potential admin * @param admin The address of the potential admin
**/ **/
function isRiskAdmin(address admin) external view returns (bool); function isRiskAdmin(address admin) external view returns (bool);
/**
* @dev Authorize a new borrower (fees are 0 for the authorized borrower)
* @param flashBorrower The address of the authorized borrower
**/
function authorizeFlashBorrower(address flashBorrower) external;
/**
* @dev Unauthorize a borrower
* @param flashBorrower The address of the unauthorized borrower
**/
function unauthorizeFlashBorrower(address flashBorrower) external;
/**
* @dev Drops a reserve entirely
* @param asset the address of the reserve to drop
**/
function dropReserve(address asset) external;
/**
* @dev Updates the total flash loan premium
* flash loan premium consist in 2 parts
* - A part is sent to aToken holders as extra balance
* - A part is collected by the protocol reserves
* @param flashloanPremiumTotal total premium in bps
*/
function updateFlashloanPremiumTotal(uint256 flashloanPremiumTotal) external;
/**
* @dev Updates the flash loan premium collected by protocol reserves
* @param flashloanPremiumToProtocol part of the premium sent to protocol
*/
function updateFlashloanPremiumToProtocol(uint256 flashloanPremiumToProtocol) external;
} }

View File

@ -49,6 +49,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
using WadRayMath for uint256; using WadRayMath for uint256;
using PercentageMath for uint256; using PercentageMath for uint256;
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
using ReserveLogic for DataTypes.ReserveCache;
uint256 public constant LENDINGPOOL_REVISION = 0x2; uint256 public constant LENDINGPOOL_REVISION = 0x2;
@ -89,6 +90,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
_maxStableRateBorrowSizePercent = 2500; _maxStableRateBorrowSizePercent = 2500;
_flashLoanPremiumTotal = 9; _flashLoanPremiumTotal = 9;
_maxNumberOfReserves = 128; _maxNumberOfReserves = 128;
_flashLoanPremiumToProtocol = 0;
} }
/** /**
@ -189,7 +191,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint16 referralCode, uint16 referralCode,
address onBehalfOf address onBehalfOf
) external override whenNotPaused { ) external override whenNotPaused {
DataTypes.ReserveData storage reserve = _reserves[asset];
_executeBorrow( _executeBorrow(
ExecuteBorrowParams( ExecuteBorrowParams(
asset, asset,
@ -197,7 +198,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
onBehalfOf, onBehalfOf,
amount, amount,
interestRateMode, interestRateMode,
reserve.aTokenAddress,
referralCode, referralCode,
true true
) )
@ -270,6 +270,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
**/ **/
function swapBorrowRateMode(address asset, uint256 rateMode) external override whenNotPaused { function swapBorrowRateMode(address asset, uint256 rateMode) external override whenNotPaused {
DataTypes.ReserveData storage reserve = _reserves[asset]; DataTypes.ReserveData storage reserve = _reserves[asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve); (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve);
@ -277,37 +278,40 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
ValidationLogic.validateSwapRateMode( ValidationLogic.validateSwapRateMode(
reserve, reserve,
reserveCache,
_usersConfig[msg.sender], _usersConfig[msg.sender],
stableDebt, stableDebt,
variableDebt, variableDebt,
interestRateMode interestRateMode
); );
reserve.updateState(); reserve.updateState(reserveCache);
if (interestRateMode == DataTypes.InterestRateMode.STABLE) { if (interestRateMode == DataTypes.InterestRateMode.STABLE) {
IStableDebtToken(reserve.stableDebtTokenAddress).burn(msg.sender, stableDebt); IStableDebtToken(reserveCache.stableDebtTokenAddress).burn(msg.sender, stableDebt);
IVariableDebtToken(reserve.variableDebtTokenAddress).mint( IVariableDebtToken(reserveCache.variableDebtTokenAddress).mint(
msg.sender, msg.sender,
msg.sender, msg.sender,
stableDebt, stableDebt,
reserve.variableBorrowIndex reserveCache.nextVariableBorrowIndex
); );
reserveCache.refreshDebt(0, stableDebt, stableDebt, 0);
} else { } else {
IVariableDebtToken(reserve.variableDebtTokenAddress).burn( IVariableDebtToken(reserveCache.variableDebtTokenAddress).burn(
msg.sender, msg.sender,
variableDebt, variableDebt,
reserve.variableBorrowIndex reserveCache.nextVariableBorrowIndex
); );
IStableDebtToken(reserve.stableDebtTokenAddress).mint( IStableDebtToken(reserveCache.stableDebtTokenAddress).mint(
msg.sender, msg.sender,
msg.sender, msg.sender,
variableDebt, variableDebt,
reserve.currentStableBorrowRate reserve.currentStableBorrowRate
); );
reserveCache.refreshDebt(variableDebt, 0, 0, variableDebt);
} }
reserve.updateInterestRates(asset, reserve.aTokenAddress, 0, 0); reserve.updateInterestRates(reserveCache, asset, 0, 0);
emit Swap(asset, msg.sender, rateMode); emit Swap(asset, msg.sender, rateMode);
} }
@ -323,22 +327,22 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
**/ **/
function rebalanceStableBorrowRate(address asset, address user) external override whenNotPaused { function rebalanceStableBorrowRate(address asset, address user) external override whenNotPaused {
DataTypes.ReserveData storage reserve = _reserves[asset]; DataTypes.ReserveData storage reserve = _reserves[asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
IERC20 stableDebtToken = IERC20(reserve.stableDebtTokenAddress); IERC20 stableDebtToken = IERC20(reserveCache.stableDebtTokenAddress);
IERC20 variableDebtToken = IERC20(reserve.variableDebtTokenAddress); IERC20 variableDebtToken = IERC20(reserveCache.variableDebtTokenAddress);
address aTokenAddress = reserve.aTokenAddress;
uint256 stableDebt = IERC20(stableDebtToken).balanceOf(user); uint256 stableDebt = IERC20(stableDebtToken).balanceOf(user);
ValidationLogic.validateRebalanceStableBorrowRate( ValidationLogic.validateRebalanceStableBorrowRate(
reserve, reserve,
reserveCache,
asset, asset,
stableDebtToken, stableDebtToken,
variableDebtToken, variableDebtToken,
aTokenAddress reserveCache.aTokenAddress
); );
reserve.updateState(); reserve.updateState(reserveCache);
IStableDebtToken(address(stableDebtToken)).burn(user, stableDebt); IStableDebtToken(address(stableDebtToken)).burn(user, stableDebt);
IStableDebtToken(address(stableDebtToken)).mint( IStableDebtToken(address(stableDebtToken)).mint(
@ -348,7 +352,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
reserve.currentStableBorrowRate reserve.currentStableBorrowRate
); );
reserve.updateInterestRates(asset, aTokenAddress, 0, 0); reserveCache.refreshDebt(stableDebt, stableDebt, 0, 0);
reserve.updateInterestRates(reserveCache, asset, 0, 0);
emit RebalanceStableBorrowRate(asset, user); emit RebalanceStableBorrowRate(asset, user);
} }
@ -364,8 +370,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
whenNotPaused whenNotPaused
{ {
DataTypes.ReserveData storage reserve = _reserves[asset]; DataTypes.ReserveData storage reserve = _reserves[asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
ValidationLogic.validateSetUseReserveAsCollateral(reserve); ValidationLogic.validateSetUseReserveAsCollateral(reserveCache);
_usersConfig[msg.sender].setUsingAsCollateral(reserve.id, useAsCollateral); _usersConfig[msg.sender].setUsingAsCollateral(reserve.id, useAsCollateral);
@ -433,9 +440,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
address currentAsset; address currentAsset;
address currentATokenAddress; address currentATokenAddress;
uint256 currentAmount; uint256 currentAmount;
uint256 currentPremium; uint256 currentPremiumToLP;
uint256 currentPremiumToProtocol;
uint256 currentAmountPlusPremium; uint256 currentAmountPlusPremium;
address debtToken; address debtToken;
address[] aTokenAddresses;
uint256[] totalPremiums;
uint256 flashloanPremiumTotal;
uint256 flashloanPremiumToProtocol;
} }
/** /**
@ -466,42 +478,52 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
) external override whenNotPaused { ) external override whenNotPaused {
FlashLoanLocalVars memory vars; FlashLoanLocalVars memory vars;
vars.aTokenAddresses = new address[](assets.length);
vars.totalPremiums = new uint256[](assets.length);
ValidationLogic.validateFlashloan(assets, amounts, _reserves); ValidationLogic.validateFlashloan(assets, amounts, _reserves);
address[] memory aTokenAddresses = new address[](assets.length);
uint256[] memory premiums = new uint256[](assets.length);
vars.receiver = IFlashLoanReceiver(receiverAddress); vars.receiver = IFlashLoanReceiver(receiverAddress);
(vars.flashloanPremiumTotal, vars.flashloanPremiumToProtocol) = _authorizedFlashBorrowers[
msg.sender
]
? (0, 0)
: (_flashLoanPremiumTotal, _flashLoanPremiumToProtocol);
for (vars.i = 0; vars.i < assets.length; vars.i++) { for (vars.i = 0; vars.i < assets.length; vars.i++) {
aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress; vars.aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress;
vars.totalPremiums[vars.i] = amounts[vars.i].percentMul(vars.flashloanPremiumTotal);
premiums[vars.i] = amounts[vars.i].mul(_flashLoanPremiumTotal).div(10000); IAToken(vars.aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]);
IAToken(aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]);
} }
require( require(
vars.receiver.executeOperation(assets, amounts, premiums, msg.sender, params), vars.receiver.executeOperation(assets, amounts, vars.totalPremiums, msg.sender, params),
Errors.LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN Errors.LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN
); );
for (vars.i = 0; vars.i < assets.length; vars.i++) { for (vars.i = 0; vars.i < assets.length; vars.i++) {
vars.currentAsset = assets[vars.i]; vars.currentAsset = assets[vars.i];
vars.currentAmount = amounts[vars.i]; vars.currentAmount = amounts[vars.i];
vars.currentPremium = premiums[vars.i]; vars.currentATokenAddress = vars.aTokenAddresses[vars.i];
vars.currentATokenAddress = aTokenAddresses[vars.i]; vars.currentAmountPlusPremium = vars.currentAmount.add(vars.totalPremiums[vars.i]);
vars.currentAmountPlusPremium = vars.currentAmount.add(vars.currentPremium); vars.currentPremiumToProtocol = amounts[vars.i].percentMul(vars.flashloanPremiumToProtocol);
vars.currentPremiumToLP = vars.totalPremiums[vars.i].sub(vars.currentPremiumToProtocol);
if (DataTypes.InterestRateMode(modes[vars.i]) == DataTypes.InterestRateMode.NONE) { if (DataTypes.InterestRateMode(modes[vars.i]) == DataTypes.InterestRateMode.NONE) {
_reserves[vars.currentAsset].updateState(); DataTypes.ReserveData storage reserve = _reserves[vars.currentAsset];
_reserves[vars.currentAsset].cumulateToLiquidityIndex( DataTypes.ReserveCache memory reserveCache = reserve.cache();
reserve.updateState(reserveCache);
reserve.cumulateToLiquidityIndex(
IERC20(vars.currentATokenAddress).totalSupply(), IERC20(vars.currentATokenAddress).totalSupply(),
vars.currentPremium vars.currentPremiumToLP
); );
_reserves[vars.currentAsset].updateInterestRates( reserve.accruedToTreasury = reserve.accruedToTreasury.add(
vars.currentPremiumToProtocol.rayDiv(reserve.liquidityIndex)
);
reserve.updateInterestRates(
reserveCache,
vars.currentAsset, vars.currentAsset,
vars.currentATokenAddress,
vars.currentAmountPlusPremium, vars.currentAmountPlusPremium,
0 0
); );
@ -521,7 +543,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
onBehalfOf, onBehalfOf,
vars.currentAmount, vars.currentAmount,
modes[vars.i], modes[vars.i],
vars.currentATokenAddress,
referralCode, referralCode,
false false
) )
@ -532,12 +553,40 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
msg.sender, msg.sender,
vars.currentAsset, vars.currentAsset,
vars.currentAmount, vars.currentAmount,
vars.currentPremium, vars.totalPremiums[vars.i],
referralCode referralCode
); );
} }
} }
/**
* @dev Mints the assets accrued through the reserve factor to the treasury in the form of aTokens
* @param reserves The list of reserves for which the minting needs to be executed
**/
function mintToTreasury(address[] calldata reserves) public {
for (uint256 i = 0; i < reserves.length; i++) {
address reserveAddress = reserves[i];
DataTypes.ReserveData storage reserve = _reserves[reserveAddress];
// this cover both inactive reserves and invalid reserves since the flag will be 0 for both
if (!reserve.configuration.getActive()) {
continue;
}
uint256 accruedToTreasury = reserve.accruedToTreasury;
if (accruedToTreasury != 0) {
uint256 normalizedIncome = reserve.getNormalizedIncome();
uint256 amountToMint = accruedToTreasury.rayMul(normalizedIncome);
IAToken(reserve.aTokenAddress).mintToTreasury(amountToMint, normalizedIncome);
reserve.accruedToTreasury = 0;
emit MintedToTreasury(reserveAddress, amountToMint);
}
}
}
/** /**
* @dev Returns the state and configuration of the reserve * @dev Returns the state and configuration of the reserve
* @param asset The address of the underlying asset of the reserve * @param asset The address of the underlying asset of the reserve
@ -581,7 +630,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
ltv, ltv,
currentLiquidationThreshold, currentLiquidationThreshold,
healthFactor healthFactor
) = GenericLogic.calculateUserAccountData( ) = GenericLogic.getUserAccountData(
user, user,
_reserves, _reserves,
_usersConfig[user], _usersConfig[user],
@ -662,15 +711,29 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
} }
/** /**
* @dev Returns the list of the initialized reserves * @dev Returns the list of the initialized reserves, does not contain dropped reserves
**/ **/
function getReservesList() external view override returns (address[] memory) { function getReservesList() external view override returns (address[] memory) {
address[] memory _activeReserves = new address[](_reservesCount); uint256 reserveListCount = _reservesCount;
uint256 droppedReservesCount = 0;
address[] memory reserves = new address[](reserveListCount);
for (uint256 i = 0; i < _reservesCount; i++) { for (uint256 i = 0; i < reserveListCount; i++) {
_activeReserves[i] = _reservesList[i]; if (_reservesList[i] != address(0)) {
reserves[i - droppedReservesCount] = _reservesList[i];
} else {
droppedReservesCount++;
}
} }
return _activeReserves;
if (droppedReservesCount == 0) return reserves;
address[] memory undroppedReserves = new address[](reserveListCount - droppedReservesCount);
for (uint256 i = 0; i < reserveListCount - droppedReservesCount; i++) {
undroppedReserves[i] = reserves[i];
}
return undroppedReserves;
} }
/** /**
@ -683,21 +746,28 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
/** /**
* @dev Returns the percentage of available liquidity that can be borrowed at once at stable rate * @dev Returns the percentage of available liquidity that can be borrowed at once at stable rate
*/ */
function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() public view returns (uint256) { function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() public view override returns (uint256) {
return _maxStableRateBorrowSizePercent; return _maxStableRateBorrowSizePercent;
} }
/** /**
* @dev Returns the fee on flash loans * @dev Returns the total fee on flash loans
*/ */
function FLASHLOAN_PREMIUM_TOTAL() public view returns (uint256) { function FLASHLOAN_PREMIUM_TOTAL() public view override returns (uint256) {
return _flashLoanPremiumTotal; return _flashLoanPremiumTotal;
} }
/**
* @dev Returns the part of the flashloan fees sent to protocol
*/
function FLASHLOAN_PREMIUM_TO_PROTOCOL() public view override returns (uint256) {
return _flashLoanPremiumToProtocol;
}
/** /**
* @dev Returns the maximum number of reserves supported to be listed in this LendingPool * @dev Returns the maximum number of reserves supported to be listed in this LendingPool
*/ */
function MAX_NUMBER_RESERVES() public view returns (uint256) { function MAX_NUMBER_RESERVES() public view override returns (uint256) {
return _maxNumberOfReserves; return _maxNumberOfReserves;
} }
@ -781,6 +851,17 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
_addReserveToList(asset); _addReserveToList(asset);
} }
/**
* @dev Drop a reserve
* - Only callable by the LendingPoolConfigurator contract
* @param asset The address of the underlying asset of the reserve
**/
function dropReserve(address asset) external override onlyLendingPoolConfigurator {
ValidationLogic.validateDropReserve(_reserves[asset]);
_removeReserveFromList(asset);
delete _reserves[asset];
}
/** /**
* @dev Updates the address of the interest rate strategy contract * @dev Updates the address of the interest rate strategy contract
* - Only callable by the LendingPoolConfigurator contract * - Only callable by the LendingPoolConfigurator contract
@ -823,13 +904,50 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
} }
} }
/**
* @dev Authorizes/Unauthorizes a flash borrower. Authorized borrowers pay no flash loan premium
* @param flashBorrower address of the flash borrower
* @param authorized `true` to authorize, `false` to unauthorize
*/
function updateFlashBorrowerAuthorization(address flashBorrower, bool authorized)
external
override
onlyLendingPoolConfigurator
{
_authorizedFlashBorrowers[flashBorrower] = authorized;
}
/**
* @dev Returns whether a flashborrower is authorized (pays no premium)
* @param flashBorrower address of the flash borrower
* @return `true` if authorized, `false` if not
*/
function isFlashBorrowerAuthorized(address flashBorrower) external view override returns (bool) {
return _authorizedFlashBorrowers[flashBorrower];
}
/**
* @dev Updates flash loan premiums
* flash loan premium consist in 2 parts
* - A part is sent to aToken holders as extra balance
* - A part is collected by the protocol reserves
* @param flashLoanPremiumTotal total premium in bps
* @param flashLoanPremiumToProtocol part of the premium sent to protocol
*/
function updateFlashloanPremiums(
uint256 flashLoanPremiumTotal,
uint256 flashLoanPremiumToProtocol
) external override onlyLendingPoolConfigurator {
_flashLoanPremiumTotal = flashLoanPremiumTotal;
_flashLoanPremiumToProtocol = flashLoanPremiumToProtocol;
}
struct ExecuteBorrowParams { struct ExecuteBorrowParams {
address asset; address asset;
address user; address user;
address onBehalfOf; address onBehalfOf;
uint256 amount; uint256 amount;
uint256 interestRateMode; uint256 interestRateMode;
address aTokenAddress;
uint16 referralCode; uint16 referralCode;
bool releaseUnderlying; bool releaseUnderlying;
} }
@ -837,50 +955,45 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
function _executeBorrow(ExecuteBorrowParams memory vars) internal { function _executeBorrow(ExecuteBorrowParams memory vars) internal {
DataTypes.ReserveData storage reserve = _reserves[vars.asset]; DataTypes.ReserveData storage reserve = _reserves[vars.asset];
DataTypes.UserConfigurationMap storage userConfig = _usersConfig[vars.onBehalfOf]; DataTypes.UserConfigurationMap storage userConfig = _usersConfig[vars.onBehalfOf];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
address oracle = _addressesProvider.getPriceOracle(); reserve.updateState(reserveCache);
uint256 amountInETH =
IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div(
10**reserve.configuration.getDecimals()
);
ValidationLogic.validateBorrow( ValidationLogic.validateBorrow(
reserveCache,
vars.asset, vars.asset,
reserve,
vars.onBehalfOf, vars.onBehalfOf,
vars.amount, vars.amount,
amountInETH,
vars.interestRateMode, vars.interestRateMode,
_maxStableRateBorrowSizePercent, _maxStableRateBorrowSizePercent,
_reserves, _reserves,
userConfig, userConfig,
_reservesList, _reservesList,
_reservesCount, _reservesCount,
oracle _addressesProvider.getPriceOracle()
); );
reserve.updateState();
uint256 currentStableRate = 0; uint256 currentStableRate = 0;
bool isFirstBorrowing = false; bool isFirstBorrowing = false;
if (DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE) { if (DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE) {
currentStableRate = reserve.currentStableBorrowRate; currentStableRate = reserve.currentStableBorrowRate;
isFirstBorrowing = IStableDebtToken(reserve.stableDebtTokenAddress).mint( isFirstBorrowing = IStableDebtToken(reserveCache.stableDebtTokenAddress).mint(
vars.user, vars.user,
vars.onBehalfOf, vars.onBehalfOf,
vars.amount, vars.amount,
currentStableRate currentStableRate
); );
reserveCache.refreshDebt(vars.amount, 0, 0, 0);
} else { } else {
isFirstBorrowing = IVariableDebtToken(reserve.variableDebtTokenAddress).mint( isFirstBorrowing = IVariableDebtToken(reserveCache.variableDebtTokenAddress).mint(
vars.user, vars.user,
vars.onBehalfOf, vars.onBehalfOf,
vars.amount, vars.amount,
reserve.variableBorrowIndex reserveCache.nextVariableBorrowIndex
); );
reserveCache.refreshDebt(0, 0, vars.amount, 0);
} }
if (isFirstBorrowing) { if (isFirstBorrowing) {
@ -888,14 +1001,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
} }
reserve.updateInterestRates( reserve.updateInterestRates(
reserveCache,
vars.asset, vars.asset,
vars.aTokenAddress,
0, 0,
vars.releaseUnderlying ? vars.amount : 0 vars.releaseUnderlying ? vars.amount : 0
); );
if (vars.releaseUnderlying) { if (vars.releaseUnderlying) {
IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount); IAToken(reserveCache.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
} }
emit Borrow( emit Borrow(
@ -918,17 +1031,18 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint16 referralCode uint16 referralCode
) internal { ) internal {
DataTypes.ReserveData storage reserve = _reserves[asset]; DataTypes.ReserveData storage reserve = _reserves[asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
ValidationLogic.validateDeposit(reserve, amount); reserve.updateState(reserveCache);
address aToken = reserve.aTokenAddress; ValidationLogic.validateDeposit(reserveCache, amount);
reserve.updateState(); reserve.updateInterestRates(reserveCache, asset, amount, 0);
reserve.updateInterestRates(asset, aToken, amount, 0);
IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); IERC20(asset).safeTransferFrom(msg.sender, reserveCache.aTokenAddress, amount);
bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); bool isFirstDeposit =
IAToken(reserveCache.aTokenAddress).mint(onBehalfOf, amount, reserveCache.nextLiquidityIndex);
if (isFirstDeposit) { if (isFirstDeposit) {
_usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true); _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true);
@ -945,14 +1059,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
) internal returns (uint256) { ) internal returns (uint256) {
DataTypes.ReserveData storage reserve = _reserves[asset]; DataTypes.ReserveData storage reserve = _reserves[asset];
DataTypes.UserConfigurationMap storage userConfig = _usersConfig[msg.sender]; DataTypes.UserConfigurationMap storage userConfig = _usersConfig[msg.sender];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
address aToken = reserve.aTokenAddress; reserve.updateState(reserveCache);
reserve.updateState(); uint256 userBalance =
IAToken(reserveCache.aTokenAddress).scaledBalanceOf(msg.sender).rayMul(
uint256 liquidityIndex = reserve.liquidityIndex; reserveCache.nextLiquidityIndex
);
uint256 userBalance = IAToken(aToken).scaledBalanceOf(msg.sender).rayMul(liquidityIndex);
uint256 amountToWithdraw = amount; uint256 amountToWithdraw = amount;
@ -960,11 +1074,16 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
amountToWithdraw = userBalance; amountToWithdraw = userBalance;
} }
ValidationLogic.validateWithdraw(reserve, amountToWithdraw, userBalance); ValidationLogic.validateWithdraw(reserveCache, amountToWithdraw, userBalance);
reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw); reserve.updateInterestRates(reserveCache, asset, 0, amountToWithdraw);
IAToken(aToken).burn(msg.sender, to, amountToWithdraw, liquidityIndex); IAToken(reserveCache.aTokenAddress).burn(
msg.sender,
to,
amountToWithdraw,
reserveCache.nextLiquidityIndex
);
if (userConfig.isUsingAsCollateral(reserve.id)) { if (userConfig.isUsingAsCollateral(reserve.id)) {
if (userConfig.isBorrowingAny()) { if (userConfig.isBorrowingAny()) {
@ -997,13 +1116,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
address onBehalfOf address onBehalfOf
) internal returns (uint256) { ) internal returns (uint256) {
DataTypes.ReserveData storage reserve = _reserves[asset]; DataTypes.ReserveData storage reserve = _reserves[asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve); (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve);
DataTypes.InterestRateMode interestRateMode = DataTypes.InterestRateMode(rateMode); DataTypes.InterestRateMode interestRateMode = DataTypes.InterestRateMode(rateMode);
ValidationLogic.validateRepay( ValidationLogic.validateRepay(
reserve, reserveCache,
amount, amount,
interestRateMode, interestRateMode,
onBehalfOf, onBehalfOf,
@ -1018,35 +1138,36 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
paybackAmount = amount; paybackAmount = amount;
} }
reserve.updateState(); reserve.updateState(reserveCache);
if (interestRateMode == DataTypes.InterestRateMode.STABLE) { if (interestRateMode == DataTypes.InterestRateMode.STABLE) {
IStableDebtToken(reserve.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount); IStableDebtToken(reserveCache.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount);
reserveCache.refreshDebt(0, paybackAmount, 0, 0);
} else { } else {
IVariableDebtToken(reserve.variableDebtTokenAddress).burn( IVariableDebtToken(reserveCache.variableDebtTokenAddress).burn(
onBehalfOf, onBehalfOf,
paybackAmount, paybackAmount,
reserve.variableBorrowIndex reserveCache.nextVariableBorrowIndex
); );
reserveCache.refreshDebt(0, 0, 0, paybackAmount);
} }
address aToken = reserve.aTokenAddress; reserve.updateInterestRates(reserveCache, asset, paybackAmount, 0);
reserve.updateInterestRates(asset, aToken, paybackAmount, 0);
if (stableDebt.add(variableDebt).sub(paybackAmount) == 0) { if (stableDebt.add(variableDebt).sub(paybackAmount) == 0) {
_usersConfig[onBehalfOf].setBorrowing(reserve.id, false); _usersConfig[onBehalfOf].setBorrowing(reserve.id, false);
} }
IERC20(asset).safeTransferFrom(msg.sender, aToken, paybackAmount); IERC20(asset).safeTransferFrom(msg.sender, reserveCache.aTokenAddress, paybackAmount);
IAToken(aToken).handleRepayment(msg.sender, paybackAmount); IAToken(reserveCache.aTokenAddress).handleRepayment(msg.sender, paybackAmount);
emit Repay(asset, onBehalfOf, msg.sender, paybackAmount); emit Repay(asset, onBehalfOf, msg.sender, paybackAmount);
return paybackAmount; return paybackAmount;
} }
function _addReserveToList(address asset) internal { function _addReserveToList(address asset) internal returns (uint8) {
uint256 reservesCount = _reservesCount; uint256 reservesCount = _reservesCount;
require(reservesCount < _maxNumberOfReserves, Errors.LP_NO_MORE_RESERVES_ALLOWED); require(reservesCount < _maxNumberOfReserves, Errors.LP_NO_MORE_RESERVES_ALLOWED);
@ -1054,10 +1175,18 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
bool reserveAlreadyAdded = _reserves[asset].id != 0 || _reservesList[0] == asset; bool reserveAlreadyAdded = _reserves[asset].id != 0 || _reservesList[0] == asset;
if (!reserveAlreadyAdded) { if (!reserveAlreadyAdded) {
_reserves[asset].id = uint8(reservesCount); for (uint8 i = 0; i <= reservesCount; i++) {
_reservesList[reservesCount] = asset; if (_reservesList[i] == address(0)) {
_reserves[asset].id = i;
_reservesCount = reservesCount + 1; _reservesList[i] = asset;
_reservesCount = reservesCount + 1;
return i;
}
}
} }
} }
function _removeReserveFromList(address asset) internal {
_reservesList[_reserves[asset].id] = address(0);
}
} }

View File

@ -1,6 +1,8 @@
// SPDX-License-Identifier: agpl-3.0 // SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12; pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {SafeMath} from '../../dependencies/openzeppelin/contracts//SafeMath.sol'; import {SafeMath} from '../../dependencies/openzeppelin/contracts//SafeMath.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts//IERC20.sol'; import {IERC20} from '../../dependencies/openzeppelin/contracts//IERC20.sol';
import {IAToken} from '../../interfaces/IAToken.sol'; import {IAToken} from '../../interfaces/IAToken.sol';
@ -9,7 +11,7 @@ import {IVariableDebtToken} from '../../interfaces/IVariableDebtToken.sol';
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol'; import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
import {ILendingPoolCollateralManager} from '../../interfaces/ILendingPoolCollateralManager.sol'; import {ILendingPoolCollateralManager} from '../../interfaces/ILendingPoolCollateralManager.sol';
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol'; import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
import {GenericLogic} from '../libraries/logic/GenericLogic.sol'; import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
import {Helpers} from '../libraries/helpers/Helpers.sol'; import {Helpers} from '../libraries/helpers/Helpers.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {PercentageMath} from '../libraries/math/PercentageMath.sol';
@ -35,6 +37,7 @@ contract LendingPoolCollateralManager is
using SafeMath for uint256; using SafeMath for uint256;
using WadRayMath for uint256; using WadRayMath for uint256;
using PercentageMath for uint256; using PercentageMath for uint256;
using ReserveLogic for DataTypes.ReserveCache;
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000; uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
@ -52,6 +55,7 @@ contract LendingPoolCollateralManager is
uint256 healthFactor; uint256 healthFactor;
uint256 liquidatorPreviousATokenBalance; uint256 liquidatorPreviousATokenBalance;
IAToken collateralAtoken; IAToken collateralAtoken;
IPriceOracleGetter oracle;
bool isCollateralEnabled; bool isCollateralEnabled;
DataTypes.InterestRateMode borrowRateMode; DataTypes.InterestRateMode borrowRateMode;
uint256 errorCode; uint256 errorCode;
@ -87,28 +91,25 @@ contract LendingPoolCollateralManager is
) external override returns (uint256, string memory) { ) external override returns (uint256, string memory) {
DataTypes.ReserveData storage collateralReserve = _reserves[collateralAsset]; DataTypes.ReserveData storage collateralReserve = _reserves[collateralAsset];
DataTypes.ReserveData storage debtReserve = _reserves[debtAsset]; DataTypes.ReserveData storage debtReserve = _reserves[debtAsset];
DataTypes.ReserveCache memory debtReserveCache = debtReserve.cache();
DataTypes.UserConfigurationMap storage userConfig = _usersConfig[user]; DataTypes.UserConfigurationMap storage userConfig = _usersConfig[user];
LiquidationCallLocalVars memory vars; LiquidationCallLocalVars memory vars;
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
vars.oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle());
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
collateralReserve,
debtReserveCache,
vars.userStableDebt.add(vars.userVariableDebt),
user, user,
_reserves, _reserves,
userConfig, userConfig,
_reservesList, _reservesList,
_reservesCount, _reservesCount,
_addressesProvider.getPriceOracle() address(vars.oracle)
);
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
collateralReserve,
debtReserve,
userConfig,
vars.healthFactor,
vars.userStableDebt,
vars.userVariableDebt
); );
if (Errors.CollateralManagerErrors(vars.errorCode) != Errors.CollateralManagerErrors.NO_ERROR) { if (Errors.CollateralManagerErrors(vars.errorCode) != Errors.CollateralManagerErrors.NO_ERROR) {
@ -132,11 +133,12 @@ contract LendingPoolCollateralManager is
vars.debtAmountNeeded vars.debtAmountNeeded
) = _calculateAvailableCollateralToLiquidate( ) = _calculateAvailableCollateralToLiquidate(
collateralReserve, collateralReserve,
debtReserve, debtReserveCache,
collateralAsset, collateralAsset,
debtAsset, debtAsset,
vars.actualDebtToLiquidate, vars.actualDebtToLiquidate,
vars.userCollateralBalance vars.userCollateralBalance,
vars.oracle
); );
// If debtAmountNeeded < actualDebtToLiquidate, there isn't enough // If debtAmountNeeded < actualDebtToLiquidate, there isn't enough
@ -160,35 +162,38 @@ contract LendingPoolCollateralManager is
} }
} }
debtReserve.updateState(); debtReserve.updateState(debtReserveCache);
if (vars.userVariableDebt >= vars.actualDebtToLiquidate) { if (vars.userVariableDebt >= vars.actualDebtToLiquidate) {
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( IVariableDebtToken(debtReserveCache.variableDebtTokenAddress).burn(
user, user,
vars.actualDebtToLiquidate, vars.actualDebtToLiquidate,
debtReserve.variableBorrowIndex debtReserveCache.nextVariableBorrowIndex
); );
debtReserveCache.refreshDebt(0, 0, 0, vars.actualDebtToLiquidate);
debtReserve.updateInterestRates(debtReserveCache, debtAsset, vars.actualDebtToLiquidate, 0);
} else { } else {
// If the user doesn't have variable debt, no need to try to burn variable debt tokens // If the user doesn't have variable debt, no need to try to burn variable debt tokens
if (vars.userVariableDebt > 0) { if (vars.userVariableDebt > 0) {
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( IVariableDebtToken(debtReserveCache.variableDebtTokenAddress).burn(
user, user,
vars.userVariableDebt, vars.userVariableDebt,
debtReserve.variableBorrowIndex debtReserveCache.nextVariableBorrowIndex
); );
} }
IStableDebtToken(debtReserve.stableDebtTokenAddress).burn( IStableDebtToken(debtReserveCache.stableDebtTokenAddress).burn(
user, user,
vars.actualDebtToLiquidate.sub(vars.userVariableDebt) vars.actualDebtToLiquidate.sub(vars.userVariableDebt)
); );
} debtReserveCache.refreshDebt(
0,
vars.actualDebtToLiquidate.sub(vars.userVariableDebt),
0,
vars.userVariableDebt
);
debtReserve.updateInterestRates( debtReserve.updateInterestRates(debtReserveCache, debtAsset, vars.actualDebtToLiquidate, 0);
debtAsset, }
debtReserve.aTokenAddress,
vars.actualDebtToLiquidate,
0
);
if (receiveAToken) { if (receiveAToken) {
vars.liquidatorPreviousATokenBalance = IERC20(vars.collateralAtoken).balanceOf(msg.sender); vars.liquidatorPreviousATokenBalance = IERC20(vars.collateralAtoken).balanceOf(msg.sender);
@ -200,10 +205,11 @@ contract LendingPoolCollateralManager is
emit ReserveUsedAsCollateralEnabled(collateralAsset, msg.sender); emit ReserveUsedAsCollateralEnabled(collateralAsset, msg.sender);
} }
} else { } else {
collateralReserve.updateState(); DataTypes.ReserveCache memory collateralReserveCache = collateralReserve.cache();
collateralReserve.updateState(collateralReserveCache);
collateralReserve.updateInterestRates( collateralReserve.updateInterestRates(
collateralReserveCache,
collateralAsset, collateralAsset,
address(vars.collateralAtoken),
0, 0,
vars.maxCollateralToLiquidate vars.maxCollateralToLiquidate
); );
@ -213,7 +219,7 @@ contract LendingPoolCollateralManager is
user, user,
msg.sender, msg.sender,
vars.maxCollateralToLiquidate, vars.maxCollateralToLiquidate,
collateralReserve.liquidityIndex collateralReserveCache.nextLiquidityIndex
); );
} }
@ -227,7 +233,7 @@ contract LendingPoolCollateralManager is
// Transfers the debt asset being repaid to the aToken, where the liquidity is kept // Transfers the debt asset being repaid to the aToken, where the liquidity is kept
IERC20(debtAsset).safeTransferFrom( IERC20(debtAsset).safeTransferFrom(
msg.sender, msg.sender,
debtReserve.aTokenAddress, debtReserveCache.aTokenAddress,
vars.actualDebtToLiquidate vars.actualDebtToLiquidate
); );
@ -260,7 +266,7 @@ contract LendingPoolCollateralManager is
* - This function needs to be called after all the checks to validate the liquidation have been performed, * - This function needs to be called after all the checks to validate the liquidation have been performed,
* otherwise it might fail. * otherwise it might fail.
* @param collateralReserve The data of the collateral reserve * @param collateralReserve The data of the collateral reserve
* @param debtReserve The data of the debt reserve * @param debtReserveCache The cached data of the debt reserve
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
@ -271,15 +277,15 @@ contract LendingPoolCollateralManager is
**/ **/
function _calculateAvailableCollateralToLiquidate( function _calculateAvailableCollateralToLiquidate(
DataTypes.ReserveData storage collateralReserve, DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveData storage debtReserve, DataTypes.ReserveCache memory debtReserveCache,
address collateralAsset, address collateralAsset,
address debtAsset, address debtAsset,
uint256 debtToCover, uint256 debtToCover,
uint256 userCollateralBalance uint256 userCollateralBalance,
IPriceOracleGetter oracle
) internal view returns (uint256, uint256) { ) internal view returns (uint256, uint256) {
uint256 collateralAmount = 0; uint256 collateralAmount = 0;
uint256 debtAmountNeeded = 0; uint256 debtAmountNeeded = 0;
IPriceOracleGetter oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle());
AvailableCollateralToLiquidateLocalVars memory vars; AvailableCollateralToLiquidateLocalVars memory vars;
@ -289,7 +295,7 @@ contract LendingPoolCollateralManager is
(, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve (, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve
.configuration .configuration
.getParams(); .getParams();
vars.debtAssetDecimals = debtReserve.configuration.getDecimals(); vars.debtAssetDecimals = debtReserveCache.reserveConfiguration.getDecimalsMemory();
// This is the maximum possible amount of the selected collateral that can be liquidated, given the // This is the maximum possible amount of the selected collateral that can be liquidated, given the
// max amount of liquidatable debt // max amount of liquidatable debt

View File

@ -159,6 +159,12 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur
); );
} }
/// @inheritdoc ILendingPoolConfigurator
function dropReserve(address asset) external override onlyPoolAdmin {
_pool.dropReserve(asset);
emit ReserveDropped(asset);
}
/// @inheritdoc ILendingPoolConfigurator /// @inheritdoc ILendingPoolConfigurator
function updateAToken(UpdateATokenInput calldata input) external override onlyPoolAdmin { function updateAToken(UpdateATokenInput calldata input) external override onlyPoolAdmin {
ILendingPool cachedPool = _pool; ILendingPool cachedPool = _pool;
@ -229,7 +235,6 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur
onlyPoolAdmin onlyPoolAdmin
{ {
ILendingPool cachedPool = _pool; ILendingPool cachedPool = _pool;
DataTypes.ReserveData memory reserveData = cachedPool.getReserveData(input.asset); DataTypes.ReserveData memory reserveData = cachedPool.getReserveData(input.asset);
(, , , uint256 decimals, ) = cachedPool.getConfiguration(input.asset).getParamsMemory(); (, , , uint256 decimals, ) = cachedPool.getConfiguration(input.asset).getParamsMemory();
@ -504,11 +509,43 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur
emit RiskAdminUnregistered(admin); emit RiskAdminUnregistered(admin);
} }
/// @inheritdoc ILendingPoolConfigurator
function authorizeFlashBorrower(address flashBorrower) external override onlyPoolAdmin {
_pool.updateFlashBorrowerAuthorization(flashBorrower, true);
emit FlashBorrowerAuthorized(flashBorrower);
}
/// @inheritdoc ILendingPoolConfigurator
function unauthorizeFlashBorrower(address flashBorrower) external override onlyPoolAdmin {
_pool.updateFlashBorrowerAuthorization(flashBorrower, false);
emit FlashBorrowerUnauthorized(flashBorrower);
}
/// @inheritdoc ILendingPoolConfigurator /// @inheritdoc ILendingPoolConfigurator
function isRiskAdmin(address admin) external view override onlyPoolAdmin returns (bool) { function isRiskAdmin(address admin) external view override onlyPoolAdmin returns (bool) {
return _riskAdmins[admin]; return _riskAdmins[admin];
} }
/// @inheritdoc ILendingPoolConfigurator
function updateFlashloanPremiumTotal(uint256 flashloanPremiumTotal)
external
override
onlyPoolAdmin
{
_pool.updateFlashloanPremiums(flashloanPremiumTotal, _pool.FLASHLOAN_PREMIUM_TO_PROTOCOL());
emit FlashloanPremiumTotalUpdated(flashloanPremiumTotal);
}
/// @inheritdoc ILendingPoolConfigurator
function updateFlashloanPremiumToProtocol(uint256 flashloanPremiumToProtocol)
external
override
onlyPoolAdmin
{
_pool.updateFlashloanPremiums(_pool.FLASHLOAN_PREMIUM_TOTAL(), flashloanPremiumToProtocol);
emit FlashloanPremiumToProcolUpdated(flashloanPremiumToProtocol);
}
function _initTokenWithProxy(address implementation, bytes memory initParams) function _initTokenWithProxy(address implementation, bytes memory initParams)
internal internal
returns (address) returns (address)

View File

@ -29,4 +29,8 @@ contract LendingPoolStorage {
uint256 internal _flashLoanPremiumTotal; uint256 internal _flashLoanPremiumTotal;
uint256 internal _maxNumberOfReserves; uint256 internal _maxNumberOfReserves;
mapping(address => bool) _authorizedFlashBorrowers;
uint256 internal _flashLoanPremiumToProtocol;
} }

View File

@ -68,6 +68,16 @@ library ReserveConfiguration {
return self.data & ~LTV_MASK; return self.data & ~LTV_MASK;
} }
/**
* @dev Gets the Loan to Value of the reserve
* @param self The reserve configuration
* @return The loan to value
**/
function getLtvMemory(DataTypes.ReserveConfigurationMap memory self) internal pure returns (uint256) {
return self.data & ~LTV_MASK;
}
/** /**
* @dev Sets the liquidation threshold of the reserve * @dev Sets the liquidation threshold of the reserve
* @param self The reserve configuration * @param self The reserve configuration
@ -153,6 +163,19 @@ library ReserveConfiguration {
return (self.data & ~DECIMALS_MASK) >> RESERVE_DECIMALS_START_BIT_POSITION; return (self.data & ~DECIMALS_MASK) >> RESERVE_DECIMALS_START_BIT_POSITION;
} }
/**
* @dev Gets the decimals of the underlying asset of the reserve
* @param self The reserve configuration
* @return The decimals of the asset
**/
function getDecimalsMemory(DataTypes.ReserveConfigurationMap memory self)
internal
pure
returns (uint256)
{
return (self.data & ~DECIMALS_MASK) >> RESERVE_DECIMALS_START_BIT_POSITION;
}
/** /**
* @dev Sets the active state of the reserve * @dev Sets the active state of the reserve
* @param self The reserve configuration * @param self The reserve configuration
@ -296,6 +319,19 @@ library ReserveConfiguration {
return (self.data & ~RESERVE_FACTOR_MASK) >> RESERVE_FACTOR_START_BIT_POSITION; return (self.data & ~RESERVE_FACTOR_MASK) >> RESERVE_FACTOR_START_BIT_POSITION;
} }
/**
* @dev Gets the reserve factor of the reserve
* @param self The reserve configuration
* @return The reserve factor
**/
function getReserveFactorMemory(DataTypes.ReserveConfigurationMap memory self)
internal
pure
returns (uint256)
{
return (self.data & ~RESERVE_FACTOR_MASK) >> RESERVE_FACTOR_START_BIT_POSITION;
}
/** /**
* @dev Sets the borrow cap of the reserve * @dev Sets the borrow cap of the reserve
* @param self The reserve configuration * @param self The reserve configuration

View File

@ -109,8 +109,11 @@ library Errors {
string public constant LPC_CALLER_NOT_EMERGENCY_OR_POOL_ADMIN = '85'; string public constant LPC_CALLER_NOT_EMERGENCY_OR_POOL_ADMIN = '85';
string public constant VL_RESERVE_PAUSED = '86'; string public constant VL_RESERVE_PAUSED = '86';
string public constant LPC_CALLER_NOT_RISK_OR_POOL_ADMIN = '87'; string public constant LPC_CALLER_NOT_RISK_OR_POOL_ADMIN = '87';
string public constant RC_INVALID_EXPOSURE_CAP = '88'; string public constant RL_ATOKEN_SUPPLY_NOT_ZERO = '88';
string public constant VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED = '89'; string public constant RL_STABLE_DEBT_NOT_ZERO = '89';
string public constant RL_VARIABLE_DEBT_SUPPLY_NOT_ZERO = '90';
string public constant RC_INVALID_EXPOSURE_CAP = '91';
string public constant VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED = '92';
enum CollateralManagerErrors { enum CollateralManagerErrors {
NO_ERROR, NO_ERROR,

View File

@ -95,6 +95,9 @@ library GenericLogic {
} }
vars.currentReserveAddress = reserves[vars.i]; vars.currentReserveAddress = reserves[vars.i];
if (vars.currentReserveAddress == address(0)) continue;
DataTypes.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress]; DataTypes.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress];
(vars.ltv, vars.liquidationThreshold, , vars.decimals, ) = currentReserve (vars.ltv, vars.liquidationThreshold, , vars.decimals, ) = currentReserve
@ -201,4 +204,36 @@ library GenericLogic {
availableBorrowsETH = availableBorrowsETH.sub(totalDebtInETH); availableBorrowsETH = availableBorrowsETH.sub(totalDebtInETH);
return availableBorrowsETH; return availableBorrowsETH;
} }
/**
* @dev proxy call for calculateUserAccountData as external function.
* Used in LendingPool to work around contract size limit issues
* @param user The address of the user
* @param reservesData Data of all the reserves
* @param userConfig The configuration of the user
* @param reserves The list of the available reserves
* @param oracle The price oracle address
* @return The total collateral and total debt of the user in ETH, the avg ltv, liquidation threshold and the HF
**/
function getUserAccountData(
address user,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap memory userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
)
external
view
returns (
uint256,
uint256,
uint256,
uint256,
uint256
)
{
return
calculateUserAccountData(user, reservesData, userConfig, reserves, reservesCount, oracle);
}
} }

View File

@ -107,30 +107,12 @@ library ReserveLogic {
* @dev Updates the liquidity cumulative index and the variable borrow index. * @dev Updates the liquidity cumulative index and the variable borrow index.
* @param reserve the reserve object * @param reserve the reserve object
**/ **/
function updateState(DataTypes.ReserveData storage reserve) internal { function updateState(
uint256 scaledVariableDebt = DataTypes.ReserveData storage reserve,
IVariableDebtToken(reserve.variableDebtTokenAddress).scaledTotalSupply(); DataTypes.ReserveCache memory reserveCache
uint256 previousVariableBorrowIndex = reserve.variableBorrowIndex; ) internal {
uint256 previousLiquidityIndex = reserve.liquidityIndex; _updateIndexes(reserve, reserveCache);
uint40 lastUpdatedTimestamp = reserve.lastUpdateTimestamp; _accrueToTreasury(reserve, reserveCache);
(uint256 newLiquidityIndex, uint256 newVariableBorrowIndex) =
_updateIndexes(
reserve,
scaledVariableDebt,
previousLiquidityIndex,
previousVariableBorrowIndex,
lastUpdatedTimestamp
);
_mintToTreasury(
reserve,
scaledVariableDebt,
previousVariableBorrowIndex,
newLiquidityIndex,
newVariableBorrowIndex,
lastUpdatedTimestamp
);
} }
/** /**
@ -179,9 +161,6 @@ library ReserveLogic {
} }
struct UpdateInterestRatesLocalVars { struct UpdateInterestRatesLocalVars {
address stableDebtTokenAddress;
uint256 availableLiquidity;
uint256 totalStableDebt;
uint256 newLiquidityRate; uint256 newLiquidityRate;
uint256 newStableRate; uint256 newStableRate;
uint256 newVariableRate; uint256 newVariableRate;
@ -197,38 +176,29 @@ library ReserveLogic {
**/ **/
function updateInterestRates( function updateInterestRates(
DataTypes.ReserveData storage reserve, DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache,
address reserveAddress, address reserveAddress,
address aTokenAddress,
uint256 liquidityAdded, uint256 liquidityAdded,
uint256 liquidityTaken uint256 liquidityTaken
) internal { ) internal {
UpdateInterestRatesLocalVars memory vars; UpdateInterestRatesLocalVars memory vars;
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress; vars.totalVariableDebt = reserveCache.nextScaledVariableDebt.rayMul(
reserveCache.nextVariableBorrowIndex
(vars.totalStableDebt, vars.avgStableRate) = IStableDebtToken(vars.stableDebtTokenAddress) );
.getTotalSupplyAndAvgRate();
//calculates the total variable debt locally using the scaled total supply instead
//of totalSupply(), as it's noticeably cheaper. Also, the index has been
//updated by the previous updateState() call
vars.totalVariableDebt = IVariableDebtToken(reserve.variableDebtTokenAddress)
.scaledTotalSupply()
.rayMul(reserve.variableBorrowIndex);
( (
vars.newLiquidityRate, vars.newLiquidityRate,
vars.newStableRate, vars.newStableRate,
vars.newVariableRate vars.newVariableRate
) = IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).calculateInterestRates( ) = IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).calculateInterestRates(
reserveAddress, reserveAddress,
aTokenAddress, reserveCache.aTokenAddress,
liquidityAdded, liquidityAdded,
liquidityTaken, liquidityTaken,
vars.totalStableDebt, reserveCache.nextTotalStableDebt,
vars.totalVariableDebt, vars.totalVariableDebt,
vars.avgStableRate, reserveCache.nextAvgStableBorrowRate,
reserve.configuration.getReserveFactor() reserveCache.reserveConfiguration.getReserveFactorMemory()
); );
require(vars.newLiquidityRate <= type(uint128).max, Errors.RL_LIQUIDITY_RATE_OVERFLOW); require(vars.newLiquidityRate <= type(uint128).max, Errors.RL_LIQUIDITY_RATE_OVERFLOW);
require(vars.newStableRate <= type(uint128).max, Errors.RL_STABLE_BORROW_RATE_OVERFLOW); require(vars.newStableRate <= type(uint128).max, Errors.RL_STABLE_BORROW_RATE_OVERFLOW);
@ -243,17 +213,15 @@ library ReserveLogic {
vars.newLiquidityRate, vars.newLiquidityRate,
vars.newStableRate, vars.newStableRate,
vars.newVariableRate, vars.newVariableRate,
reserve.liquidityIndex, reserveCache.nextLiquidityIndex,
reserve.variableBorrowIndex reserveCache.nextVariableBorrowIndex
); );
} }
struct MintToTreasuryLocalVars { struct MintToTreasuryLocalVars {
uint256 currentStableDebt; uint256 prevTotalStableDebt;
uint256 principalStableDebt; uint256 prevTotalVariableDebt;
uint256 previousStableDebt; uint256 currTotalVariableDebt;
uint256 currentVariableDebt;
uint256 previousVariableDebt;
uint256 avgStableRate; uint256 avgStableRate;
uint256 cumulatedStableInterest; uint256 cumulatedStableInterest;
uint256 totalDebtAccrued; uint256 totalDebtAccrued;
@ -266,108 +234,187 @@ library ReserveLogic {
* @dev Mints part of the repaid interest to the reserve treasury as a function of the reserveFactor for the * @dev Mints part of the repaid interest to the reserve treasury as a function of the reserveFactor for the
* specific asset. * specific asset.
* @param reserve The reserve reserve to be updated * @param reserve The reserve reserve to be updated
* @param scaledVariableDebt The current scaled total variable debt * @param reserveCache The caching layer for the reserve data
* @param previousVariableBorrowIndex The variable borrow index before the last accumulation of the interest
* @param newLiquidityIndex The new liquidity index
* @param newVariableBorrowIndex The variable borrow index after the last accumulation of the interest
**/ **/
function _mintToTreasury( function _accrueToTreasury(
DataTypes.ReserveData storage reserve, DataTypes.ReserveData storage reserve,
uint256 scaledVariableDebt, DataTypes.ReserveCache memory reserveCache
uint256 previousVariableBorrowIndex,
uint256 newLiquidityIndex,
uint256 newVariableBorrowIndex,
uint40 timestamp
) internal { ) internal {
MintToTreasuryLocalVars memory vars; MintToTreasuryLocalVars memory vars;
vars.reserveFactor = reserve.configuration.getReserveFactor(); vars.reserveFactor = reserveCache.reserveConfiguration.getReserveFactorMemory();
if (vars.reserveFactor == 0) { if (vars.reserveFactor == 0) {
return; return;
} }
//fetching the principal, total stable debt and the avg stable rate //calculate the total variable debt at moment of the last interaction
( vars.prevTotalVariableDebt = reserveCache.currScaledVariableDebt.rayMul(
vars.principalStableDebt, reserveCache.currVariableBorrowIndex
vars.currentStableDebt, );
vars.avgStableRate,
vars.stableSupplyUpdatedTimestamp
) = IStableDebtToken(reserve.stableDebtTokenAddress).getSupplyData();
//calculate the last principal variable debt //calculate the new total variable debt after accumulation of the interest on the index
vars.previousVariableDebt = scaledVariableDebt.rayMul(previousVariableBorrowIndex); vars.currTotalVariableDebt = reserveCache.currScaledVariableDebt.rayMul(
reserveCache.nextVariableBorrowIndex
//calculate the new total supply after accumulation of the index );
vars.currentVariableDebt = scaledVariableDebt.rayMul(newVariableBorrowIndex);
//calculate the stable debt until the last timestamp update //calculate the stable debt until the last timestamp update
vars.cumulatedStableInterest = MathUtils.calculateCompoundedInterest( vars.cumulatedStableInterest = MathUtils.calculateCompoundedInterest(
vars.avgStableRate, reserveCache.currAvgStableBorrowRate,
vars.stableSupplyUpdatedTimestamp, reserveCache.stableDebtLastUpdateTimestamp,
timestamp reserveCache.reserveLastUpdateTimestamp
); );
vars.previousStableDebt = vars.principalStableDebt.rayMul(vars.cumulatedStableInterest); vars.prevTotalStableDebt = reserveCache.currPrincipalStableDebt.rayMul(
vars.cumulatedStableInterest
);
//debt accrued is the sum of the current debt minus the sum of the debt at the last update //debt accrued is the sum of the current debt minus the sum of the debt at the last update
vars.totalDebtAccrued = vars vars.totalDebtAccrued = vars
.currentVariableDebt .currTotalVariableDebt
.add(vars.currentStableDebt) .add(reserveCache.currTotalStableDebt)
.sub(vars.previousVariableDebt) .sub(vars.prevTotalVariableDebt)
.sub(vars.previousStableDebt); .sub(vars.prevTotalStableDebt);
vars.amountToMint = vars.totalDebtAccrued.percentMul(vars.reserveFactor); vars.amountToMint = vars.totalDebtAccrued.percentMul(vars.reserveFactor);
if (vars.amountToMint != 0) { if (vars.amountToMint != 0) {
IAToken(reserve.aTokenAddress).mintToTreasury(vars.amountToMint, newLiquidityIndex); reserve.accruedToTreasury = reserve.accruedToTreasury.add(
vars.amountToMint.rayDiv(reserveCache.nextLiquidityIndex)
);
} }
} }
/** /**
* @dev Updates the reserve indexes and the timestamp of the update * @dev Updates the reserve indexes and the timestamp of the update
* @param reserve The reserve reserve to be updated * @param reserve The reserve reserve to be updated
* @param scaledVariableDebt The scaled variable debt * @param reserveCache The cache layer holding the cached protocol data
* @param liquidityIndex The last stored liquidity index
* @param variableBorrowIndex The last stored variable borrow index
**/ **/
function _updateIndexes( function _updateIndexes(
DataTypes.ReserveData storage reserve, DataTypes.ReserveData storage reserve,
uint256 scaledVariableDebt, DataTypes.ReserveCache memory reserveCache
uint256 liquidityIndex, ) internal {
uint256 variableBorrowIndex, reserveCache.nextLiquidityIndex = reserveCache.currLiquidityIndex;
uint40 timestamp reserveCache.nextVariableBorrowIndex = reserveCache.currVariableBorrowIndex;
) internal returns (uint256, uint256) {
uint256 currentLiquidityRate = reserve.currentLiquidityRate;
uint256 newLiquidityIndex = liquidityIndex;
uint256 newVariableBorrowIndex = variableBorrowIndex;
//only cumulating if there is any income being produced //only cumulating if there is any income being produced
if (currentLiquidityRate > 0) { if (reserveCache.currLiquidityRate > 0) {
uint256 cumulatedLiquidityInterest = uint256 cumulatedLiquidityInterest =
MathUtils.calculateLinearInterest(currentLiquidityRate, timestamp); MathUtils.calculateLinearInterest(
newLiquidityIndex = cumulatedLiquidityInterest.rayMul(liquidityIndex); reserveCache.currLiquidityRate,
require(newLiquidityIndex <= type(uint128).max, Errors.RL_LIQUIDITY_INDEX_OVERFLOW); reserveCache.reserveLastUpdateTimestamp
);
reserve.liquidityIndex = uint128(newLiquidityIndex); reserveCache.nextLiquidityIndex = cumulatedLiquidityInterest.rayMul(
reserveCache.currLiquidityIndex
);
require(
reserveCache.nextLiquidityIndex <= type(uint128).max,
Errors.RL_LIQUIDITY_INDEX_OVERFLOW
);
reserve.liquidityIndex = uint128(reserveCache.nextLiquidityIndex);
//as the liquidity rate might come only from stable rate loans, we need to ensure //as the liquidity rate might come only from stable rate loans, we need to ensure
//that there is actual variable debt before accumulating //that there is actual variable debt before accumulating
if (scaledVariableDebt != 0) { if (reserveCache.currScaledVariableDebt != 0) {
uint256 cumulatedVariableBorrowInterest = uint256 cumulatedVariableBorrowInterest =
MathUtils.calculateCompoundedInterest(reserve.currentVariableBorrowRate, timestamp); MathUtils.calculateCompoundedInterest(
newVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(variableBorrowIndex); reserveCache.currVariableBorrowRate,
reserveCache.reserveLastUpdateTimestamp
);
reserveCache.nextVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(
reserveCache.currVariableBorrowIndex
);
require( require(
newVariableBorrowIndex <= type(uint128).max, reserveCache.nextVariableBorrowIndex <= type(uint128).max,
Errors.RL_VARIABLE_BORROW_INDEX_OVERFLOW Errors.RL_VARIABLE_BORROW_INDEX_OVERFLOW
); );
reserve.variableBorrowIndex = uint128(newVariableBorrowIndex); reserve.variableBorrowIndex = uint128(reserveCache.nextVariableBorrowIndex);
} }
} }
//solium-disable-next-line //solium-disable-next-line
reserve.lastUpdateTimestamp = uint40(block.timestamp); reserve.lastUpdateTimestamp = uint40(block.timestamp);
return (newLiquidityIndex, newVariableBorrowIndex); }
/**
* @dev Creates a cache object to avoid repeated storage reads and external contract calls when updating state and interest rates.
* @param reserve The reserve object for which the cache will be filled
* @return The cache object
*/
function cache(DataTypes.ReserveData storage reserve)
internal
view
returns (DataTypes.ReserveCache memory)
{
DataTypes.ReserveCache memory reserveCache;
reserveCache.reserveConfiguration = reserve.configuration;
reserveCache.currLiquidityIndex = reserve.liquidityIndex;
reserveCache.currVariableBorrowIndex = reserve.variableBorrowIndex;
reserveCache.currLiquidityRate = reserve.currentLiquidityRate;
reserveCache.currVariableBorrowRate = reserve.currentVariableBorrowRate;
reserveCache.aTokenAddress = reserve.aTokenAddress;
reserveCache.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
reserveCache.variableDebtTokenAddress = reserve.variableDebtTokenAddress;
reserveCache.reserveLastUpdateTimestamp = reserve.lastUpdateTimestamp;
reserveCache.currScaledVariableDebt = reserveCache.nextScaledVariableDebt = IVariableDebtToken(
reserveCache
.variableDebtTokenAddress
)
.scaledTotalSupply();
(
reserveCache.currPrincipalStableDebt,
reserveCache.currTotalStableDebt,
reserveCache.currAvgStableBorrowRate,
reserveCache.stableDebtLastUpdateTimestamp
) = IStableDebtToken(reserveCache.stableDebtTokenAddress).getSupplyData();
// by default the actions are considered as not affecting the debt balances.
// if the action involves mint/burn of debt, the cache needs to be updated through refreshDebt()
reserveCache.nextTotalStableDebt = reserveCache.currTotalStableDebt;
reserveCache.nextAvgStableBorrowRate = reserveCache.currAvgStableBorrowRate;
return reserveCache;
}
/**
* @dev Updates the debt data in the cache object. MUST be invoked before updateInterestRates() when a protocol interaction
* causes minting or burning of debt.
* @param cache The cache object
* @param stableDebtMinted The stable debt minted as a consequence of the interaction
* @param stableDebtBurned The stable debt burned as a consequence of the interaction
* @param variableDebtMinted The variable debt minted as a consequence of the interaction
* @param variableDebtBurned The variable debt burned as a consequence of the interaction
*/
function refreshDebt(
DataTypes.ReserveCache memory cache,
uint256 stableDebtMinted,
uint256 stableDebtBurned,
uint256 variableDebtMinted,
uint256 variableDebtBurned
) internal view {
if (stableDebtMinted != 0 || stableDebtBurned != 0) {
if (cache.currTotalStableDebt.add(stableDebtMinted) > stableDebtBurned) {
cache.nextTotalStableDebt = cache.currTotalStableDebt.add(stableDebtMinted).sub(
stableDebtBurned
);
cache.nextAvgStableBorrowRate = IStableDebtToken(cache.stableDebtTokenAddress)
.getAverageStableRate();
} else {
cache.nextTotalStableDebt = cache.nextAvgStableBorrowRate = 0;
}
}
if (variableDebtMinted != 0 || variableDebtBurned != 0) {
uint256 scaledVariableDebtMinted = variableDebtMinted.rayDiv(cache.nextVariableBorrowIndex);
uint256 scaledVariableDebtBurned = variableDebtBurned.rayDiv(cache.nextVariableBorrowIndex);
cache.nextScaledVariableDebt = cache.currScaledVariableDebt.add(scaledVariableDebtMinted).sub(
scaledVariableDebtBurned
);
}
} }
} }

View File

@ -16,7 +16,10 @@ import {Helpers} from '../helpers/Helpers.sol';
import {IReserveInterestRateStrategy} from '../../../interfaces/IReserveInterestRateStrategy.sol'; import {IReserveInterestRateStrategy} from '../../../interfaces/IReserveInterestRateStrategy.sol';
import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol'; import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol';
import {IStableDebtToken} from '../../../interfaces/IStableDebtToken.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 {DataTypes} from '../types/DataTypes.sol';
import {IPriceOracleGetter} from '../../../interfaces/IPriceOracleGetter.sol';
/** /**
* @title ReserveLogic library * @title ReserveLogic library
@ -37,14 +40,17 @@ library ValidationLogic {
/** /**
* @dev Validates a deposit action * @dev Validates a deposit action
* @param reserve The reserve object on which the user is depositing * @param reserveCache The cached data of the reserve
* @param amount The amount to be deposited * @param amount The amount to be deposited
*/ */
function validateDeposit(DataTypes.ReserveData storage reserve, uint256 amount) internal view { function validateDeposit(DataTypes.ReserveCache memory reserveCache, uint256 amount)
DataTypes.ReserveConfigurationMap memory reserveConfiguration = reserve.configuration; internal
(bool isActive, bool isFrozen, , , bool isPaused) = reserveConfiguration.getFlagsMemory(); view
(, , , uint256 reserveDecimals, ) = reserveConfiguration.getParamsMemory(); {
uint256 supplyCap = reserveConfiguration.getSupplyCapMemory(); (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(amount != 0, Errors.VL_INVALID_AMOUNT);
require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
@ -52,7 +58,11 @@ library ValidationLogic {
require(!isFrozen, Errors.VL_RESERVE_FROZEN); require(!isFrozen, Errors.VL_RESERVE_FROZEN);
require( require(
supplyCap == 0 || supplyCap == 0 ||
IERC20(reserve.aTokenAddress).totalSupply().add(amount).div(10**reserveDecimals) < IAToken(reserveCache.aTokenAddress)
.scaledTotalSupply()
.rayMul(reserveCache.nextLiquidityIndex)
.add(amount)
.div(10**reserveDecimals) <
supplyCap, supplyCap,
Errors.VL_SUPPLY_CAP_EXCEEDED Errors.VL_SUPPLY_CAP_EXCEEDED
); );
@ -60,19 +70,19 @@ library ValidationLogic {
/** /**
* @dev Validates a withdraw action * @dev Validates a withdraw action
* @param reserve The reserve object * @param reserveCache The cached data of the reserve
* @param amount The amount to be withdrawn * @param amount The amount to be withdrawn
* @param userBalance The balance of the user * @param userBalance The balance of the user
*/ */
function validateWithdraw( function validateWithdraw(
DataTypes.ReserveData storage reserve, DataTypes.ReserveCache memory reserveCache,
uint256 amount, uint256 amount,
uint256 userBalance uint256 userBalance
) external view { ) internal pure {
require(amount != 0, Errors.VL_INVALID_AMOUNT); require(amount != 0, Errors.VL_INVALID_AMOUNT);
require(amount <= userBalance, Errors.VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE); require(amount <= userBalance, Errors.VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE);
(bool isActive, , , , bool isPaused) = reserve.configuration.getFlags(); (bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED); require(!isPaused, Errors.VL_RESERVE_PAUSED);
} }
@ -85,10 +95,11 @@ library ValidationLogic {
uint256 userBorrowBalanceETH; uint256 userBorrowBalanceETH;
uint256 availableLiquidity; uint256 availableLiquidity;
uint256 healthFactor; uint256 healthFactor;
uint256 totalSupplyStableDebt; uint256 totalDebt;
uint256 totalSupplyVariableDebt; uint256 totalSupplyVariableDebt;
uint256 reserveDecimals; uint256 reserveDecimals;
uint256 borrowCap; uint256 borrowCap;
uint256 amountInETH;
bool isActive; bool isActive;
bool isFrozen; bool isFrozen;
bool isPaused; bool isPaused;
@ -98,11 +109,10 @@ library ValidationLogic {
/** /**
* @dev Validates a borrow action * @dev Validates a borrow action
* @param reserveCache the cached data of the reserve
* @param asset The address of the asset to 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 userAddress The address of the user
* @param amount The amount to be borrowed * @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 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 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 reservesData The state of all the reserves
@ -112,11 +122,10 @@ library ValidationLogic {
*/ */
function validateBorrow( function validateBorrow(
DataTypes.ReserveCache memory reserveCache,
address asset, address asset,
DataTypes.ReserveData storage reserve,
address userAddress, address userAddress,
uint256 amount, uint256 amount,
uint256 amountInETH,
uint256 interestRateMode, uint256 interestRateMode,
uint256 maxStableLoanPercent, uint256 maxStableLoanPercent,
mapping(address => DataTypes.ReserveData) storage reservesData, mapping(address => DataTypes.ReserveData) storage reservesData,
@ -127,8 +136,7 @@ library ValidationLogic {
) external view { ) external view {
ValidateBorrowLocalVars memory vars; ValidateBorrowLocalVars memory vars;
DataTypes.ReserveConfigurationMap memory reserveConfiguration = reserve.configuration; (, , , vars.reserveDecimals, ) = reserveCache.reserveConfiguration.getParamsMemory();
(, , , vars.reserveDecimals, ) = reserveConfiguration.getParamsMemory();
( (
vars.isActive, vars.isActive,
@ -136,7 +144,7 @@ library ValidationLogic {
vars.borrowingEnabled, vars.borrowingEnabled,
vars.stableRateBorrowingEnabled, vars.stableRateBorrowingEnabled,
vars.isPaused vars.isPaused
) = reserveConfiguration.getFlagsMemory(); ) = reserveCache.reserveConfiguration.getFlagsMemory();
require(vars.isActive, Errors.VL_NO_ACTIVE_RESERVE); require(vars.isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!vars.isPaused, Errors.VL_RESERVE_PAUSED); require(!vars.isPaused, Errors.VL_RESERVE_PAUSED);
@ -152,18 +160,23 @@ library ValidationLogic {
Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED
); );
vars.totalSupplyStableDebt = IERC20(reserve.stableDebtTokenAddress).totalSupply(); vars.borrowCap = reserveCache.reserveConfiguration.getBorrowCapMemory();
vars.borrowCap = reserveConfiguration.getBorrowCapMemory();
vars.totalSupplyVariableDebt = IERC20(reserve.variableDebtTokenAddress).totalSupply();
require( if (vars.borrowCap != 0) {
vars.borrowCap == 0 || {
vars.totalSupplyStableDebt.add(vars.totalSupplyVariableDebt).add(amount).div( vars.totalSupplyVariableDebt = reserveCache.currScaledVariableDebt.rayMul(
10**vars.reserveDecimals reserveCache.nextVariableBorrowIndex
) < );
vars.borrowCap,
Errors.VL_BORROW_CAP_EXCEEDED 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.userCollateralBalanceETH,
@ -187,8 +200,11 @@ library ValidationLogic {
Errors.VL_HEALTH_FACTOR_LOWER_THAN_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. //add the current already borrowed amount to the amount requested to calculate the total collateral needed.
vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(amountInETH).percentDiv( vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(vars.amountInETH).percentDiv(
vars.currentLtv vars.currentLtv
); //LTV is calculated in percentage ); //LTV is calculated in percentage
@ -211,13 +227,13 @@ library ValidationLogic {
require(vars.stableRateBorrowingEnabled, Errors.VL_STABLE_BORROWING_NOT_ENABLED); require(vars.stableRateBorrowingEnabled, Errors.VL_STABLE_BORROWING_NOT_ENABLED);
require( require(
!userConfig.isUsingAsCollateral(reserve.id) || !userConfig.isUsingAsCollateral(reservesData[asset].id) ||
reserve.configuration.getLtv() == 0 || reserveCache.reserveConfiguration.getLtvMemory() == 0 ||
amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress), amount > IERC20(reserveCache.aTokenAddress).balanceOf(userAddress),
Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
); );
vars.availableLiquidity = IERC20(asset).balanceOf(reserve.aTokenAddress); vars.availableLiquidity = IERC20(asset).balanceOf(reserveCache.aTokenAddress);
//calculate the max available loan size in stable rate mode as a percentage of the //calculate the max available loan size in stable rate mode as a percentage of the
//available liquidity //available liquidity
@ -229,21 +245,22 @@ library ValidationLogic {
/** /**
* @dev Validates a repay action * @dev Validates a repay action
* @param reserve The reserve state from which the user is repaying * @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 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 onBehalfOf The address of the user msg.sender is repaying for
* @param stableDebt The borrow balance of the user * @param stableDebt The borrow balance of the user
* @param variableDebt The borrow balance of the user * @param variableDebt The borrow balance of the user
*/ */
function validateRepay( function validateRepay(
DataTypes.ReserveData storage reserve, DataTypes.ReserveCache memory reserveCache,
uint256 amountSent, uint256 amountSent,
DataTypes.InterestRateMode rateMode, DataTypes.InterestRateMode rateMode,
address onBehalfOf, address onBehalfOf,
uint256 stableDebt, uint256 stableDebt,
uint256 variableDebt uint256 variableDebt
) external view { ) internal view {
(bool isActive, , , , bool isPaused) = reserve.configuration.getFlags(); (bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED); require(!isPaused, Errors.VL_RESERVE_PAUSED);
@ -266,20 +283,22 @@ library ValidationLogic {
/** /**
* @dev Validates a swap of borrow rate mode. * @dev Validates a swap of borrow rate mode.
* @param reserve The reserve state on which the user is swapping the rate * @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 userConfig The user reserves configuration
* @param stableDebt The stable debt of the user * @param stableDebt The stable debt of the user
* @param variableDebt The variable debt of the user * @param variableDebt The variable debt of the user
* @param currentRateMode The rate mode of the borrow * @param currentRateMode The rate mode of the debt being swapped
*/ */
function validateSwapRateMode( function validateSwapRateMode(
DataTypes.ReserveData storage reserve, DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache,
DataTypes.UserConfigurationMap storage userConfig, DataTypes.UserConfigurationMap storage userConfig,
uint256 stableDebt, uint256 stableDebt,
uint256 variableDebt, uint256 variableDebt,
DataTypes.InterestRateMode currentRateMode DataTypes.InterestRateMode currentRateMode
) external view { ) external view {
(bool isActive, bool isFrozen, , bool stableRateEnabled, bool isPaused) = (bool isActive, bool isFrozen, , bool stableRateEnabled, bool isPaused) =
reserve.configuration.getFlags(); reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED); require(!isPaused, Errors.VL_RESERVE_PAUSED);
@ -300,8 +319,8 @@ library ValidationLogic {
require( require(
!userConfig.isUsingAsCollateral(reserve.id) || !userConfig.isUsingAsCollateral(reserve.id) ||
reserve.configuration.getLtv() == 0 || reserveCache.reserveConfiguration.getLtvMemory() == 0 ||
stableDebt.add(variableDebt) > IERC20(reserve.aTokenAddress).balanceOf(msg.sender), stableDebt.add(variableDebt) > IERC20(reserveCache.aTokenAddress).balanceOf(msg.sender),
Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
); );
} else { } else {
@ -312,6 +331,7 @@ library ValidationLogic {
/** /**
* @dev Validates a stable borrow rate rebalance action * @dev Validates a stable borrow rate rebalance action
* @param reserve The reserve state on which the user is getting rebalanced * @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 reserveAddress The address of the reserve
* @param stableDebtToken The stable debt token instance * @param stableDebtToken The stable debt token instance
* @param variableDebtToken The variable debt token instance * @param variableDebtToken The variable debt token instance
@ -319,12 +339,13 @@ library ValidationLogic {
*/ */
function validateRebalanceStableBorrowRate( function validateRebalanceStableBorrowRate(
DataTypes.ReserveData storage reserve, DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache,
address reserveAddress, address reserveAddress,
IERC20 stableDebtToken, IERC20 stableDebtToken,
IERC20 variableDebtToken, IERC20 variableDebtToken,
address aTokenAddress address aTokenAddress
) external view { ) external view {
(bool isActive, , , , bool isPaused) = reserve.configuration.getFlags(); (bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory();
require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(isActive, Errors.VL_NO_ACTIVE_RESERVE);
require(!isPaused, Errors.VL_RESERVE_PAUSED); require(!isPaused, Errors.VL_RESERVE_PAUSED);
@ -338,7 +359,7 @@ library ValidationLogic {
//if the liquidity rate is below REBALANCE_UP_THRESHOLD of the max variable APR at 95% usage, //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. //then we allow rebalancing of the stable rate positions.
uint256 currentLiquidityRate = reserve.currentLiquidityRate; uint256 currentLiquidityRate = reserveCache.currLiquidityRate;
uint256 maxVariableBorrowRate = uint256 maxVariableBorrowRate =
IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).getMaxVariableBorrowRate(); IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).getMaxVariableBorrowRate();
@ -352,12 +373,16 @@ library ValidationLogic {
/** /**
* @dev Validates the action of setting an asset as collateral * @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 reserveCache The cached data of the reserve
*/ */
function validateSetUseReserveAsCollateral(DataTypes.ReserveData storage reserve) external view { function validateSetUseReserveAsCollateral(DataTypes.ReserveCache memory reserveCache)
uint256 underlyingBalance = IERC20(reserve.aTokenAddress).balanceOf(msg.sender); external
bool isPaused = reserve.configuration.getPaused(); 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(!isPaused, Errors.VL_RESERVE_PAUSED);
require(underlyingBalance > 0, Errors.VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0); require(underlyingBalance > 0, Errors.VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0);
@ -372,62 +397,95 @@ library ValidationLogic {
address[] memory assets, address[] memory assets,
uint256[] memory amounts, uint256[] memory amounts,
mapping(address => DataTypes.ReserveData) storage reservesData mapping(address => DataTypes.ReserveData) storage reservesData
) external view { ) internal view {
for (uint256 i = 0; i < assets.length; i++) { for (uint256 i = 0; i < assets.length; i++) {
require(!reservesData[assets[i]].configuration.getPaused(), Errors.VL_RESERVE_PAUSED); require(!reservesData[assets[i]].configuration.getPaused(), Errors.VL_RESERVE_PAUSED);
} }
require(assets.length == amounts.length, Errors.VL_INCONSISTENT_FLASHLOAN_PARAMS); 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 * @dev Validates the liquidation action
* @param collateralReserve The reserve data of the collateral * @param collateralReserve The reserve data of the collateral
* @param principalReserve The reserve data of the principal * @param principalReserveCache The cached reserve data of the principal
* @param userConfig The user configuration * @param userConfig The user configuration
* @param userHealthFactor The user's health factor * @param totalDebt Total debt balance of the user
* @param userStableDebt Total stable debt balance of the user * @param user The address of the user being liquidated
* @param userVariableDebt Total variable debt balance of the user * @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( function validateLiquidationCall(
DataTypes.ReserveData storage collateralReserve, DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveData storage principalReserve, DataTypes.ReserveCache memory principalReserveCache,
uint256 totalDebt,
address user,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig, DataTypes.UserConfigurationMap storage userConfig,
uint256 userHealthFactor, mapping(uint256 => address) storage reserves,
uint256 userStableDebt, uint256 reservesCount,
uint256 userVariableDebt address oracle
) internal view returns (uint256, string memory) { ) internal view returns (uint256, string memory) {
if ( ValidateLiquidationCallLocalVars memory vars;
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
) { (vars.collateralReserveActive, , , , vars.collateralReservePaused) = collateralReserve
.configuration
.getFlagsMemory();
(vars.principalReserveActive, , , , vars.principalReservePaused) = principalReserveCache
.reserveConfiguration
.getFlagsMemory();
if (!vars.collateralReserveActive || !vars.principalReserveActive) {
return ( return (
uint256(Errors.CollateralManagerErrors.NO_ACTIVE_RESERVE), uint256(Errors.CollateralManagerErrors.NO_ACTIVE_RESERVE),
Errors.VL_NO_ACTIVE_RESERVE Errors.VL_NO_ACTIVE_RESERVE
); );
} }
if (collateralReserve.configuration.getPaused() || principalReserve.configuration.getPaused()) { if (vars.collateralReservePaused || vars.principalReservePaused) {
return (uint256(Errors.CollateralManagerErrors.PAUSED_RESERVE), Errors.VL_RESERVE_PAUSED); return (uint256(Errors.CollateralManagerErrors.PAUSED_RESERVE), Errors.VL_RESERVE_PAUSED);
} }
if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) { (, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
user,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
);
if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
return ( return (
uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_ABOVE_THRESHOLD), uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
Errors.LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD Errors.LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD
); );
} }
bool isCollateralEnabled = vars.isCollateralEnabled =
collateralReserve.configuration.getLiquidationThreshold() > 0 && collateralReserve.configuration.getLiquidationThreshold() > 0 &&
userConfig.isUsingAsCollateral(collateralReserve.id); userConfig.isUsingAsCollateral(collateralReserve.id);
//if collateral isn't enabled as collateral by user, it cannot be liquidated //if collateral isn't enabled as collateral by user, it cannot be liquidated
if (!isCollateralEnabled) { if (!vars.isCollateralEnabled) {
return ( return (
uint256(Errors.CollateralManagerErrors.COLLATERAL_CANNOT_BE_LIQUIDATED), uint256(Errors.CollateralManagerErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
Errors.LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED Errors.LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED
); );
} }
if (userStableDebt == 0 && userVariableDebt == 0) { if (totalDebt == 0) {
return ( return (
uint256(Errors.CollateralManagerErrors.CURRRENCY_NOT_BORROWED), uint256(Errors.CollateralManagerErrors.CURRRENCY_NOT_BORROWED),
Errors.LPCM_SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER Errors.LPCM_SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
@ -454,7 +512,7 @@ library ValidationLogic {
mapping(uint256 => address) storage reserves, mapping(uint256 => address) storage reserves,
uint256 reservesCount, uint256 reservesCount,
address oracle address oracle
) internal view { ) external view {
DataTypes.ReserveData memory reserve = reservesData[collateral]; DataTypes.ReserveData memory reserve = reservesData[collateral];
(, , uint256 ltv, uint256 liquidationThreshold, uint256 healthFactor) = (, , uint256 ltv, uint256 liquidationThreshold, uint256 healthFactor) =
GenericLogic.calculateUserAccountData( GenericLogic.calculateUserAccountData(
@ -497,4 +555,20 @@ library ValidationLogic {
function validateTransfer(DataTypes.ReserveData storage reserve) internal view { function validateTransfer(DataTypes.ReserveData storage reserve) internal view {
require(!reserve.configuration.getPaused(), Errors.VL_RESERVE_PAUSED); 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);
}
} }

View File

@ -25,6 +25,8 @@ library DataTypes {
address interestRateStrategyAddress; address interestRateStrategyAddress;
//the id of the reserve. Represents the position in the list of the active reserves //the id of the reserve. Represents the position in the list of the active reserves
uint8 id; uint8 id;
//the current treasury balance, scaled
uint256 accruedToTreasury;
} }
struct ReserveConfigurationMap { struct ReserveConfigurationMap {
@ -50,4 +52,26 @@ library DataTypes {
} }
enum InterestRateMode {NONE, STABLE, VARIABLE} enum InterestRateMode {NONE, STABLE, VARIABLE}
struct ReserveCache {
uint256 currScaledVariableDebt;
uint256 nextScaledVariableDebt;
uint256 currPrincipalStableDebt;
uint256 currAvgStableBorrowRate;
uint256 currTotalStableDebt;
uint256 nextAvgStableBorrowRate;
uint256 nextTotalStableDebt;
uint256 currLiquidityIndex;
uint256 nextLiquidityIndex;
uint256 currVariableBorrowIndex;
uint256 nextVariableBorrowIndex;
uint256 currLiquidityRate;
uint256 currVariableBorrowRate;
DataTypes.ReserveConfigurationMap reserveConfiguration;
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
uint40 reserveLastUpdateTimestamp;
uint40 stableDebtLastUpdateTimestamp;
}
} }

View File

@ -188,7 +188,8 @@ export const deployAaveLibraries = async (
return { return {
['__$de8c0cf1a7d7c36c802af9a64fb9d86036$__']: validationLogic.address, ['__$de8c0cf1a7d7c36c802af9a64fb9d86036$__']: validationLogic.address,
['__$22cd43a9dda9ce44e9b92ba393b88fb9ac$__']: reserveLogic.address, ['__$22cd43a9dda9ce44e9b92ba393b88fb9ac$__']: reserveLogic.address,
}; ["__$52a8a86ab43135662ff256bbc95497e8e3$__"]: genericLogic.address,
}
}; };
export const deployLendingPool = async (verify?: boolean) => { export const deployLendingPool = async (verify?: boolean) => {

View File

@ -184,8 +184,11 @@ export enum ProtocolErrors {
LPC_CALLER_NOT_EMERGENCY_OR_POOL_ADMIN = '85', LPC_CALLER_NOT_EMERGENCY_OR_POOL_ADMIN = '85',
VL_RESERVE_PAUSED = '86', VL_RESERVE_PAUSED = '86',
LPC_CALLER_NOT_RISK_OR_POOL_ADMIN = '87', LPC_CALLER_NOT_RISK_OR_POOL_ADMIN = '87',
RC_INVALID_EXPOSURE_CAP = '88', RL_ATOKEN_SUPPLY_NOT_ZERO = '88',
VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED = '89', RL_STABLE_DEBT_NOT_ZERO = '89',
RL_VARIABLE_DEBT_SUPPLY_NOT_ZERO = '90',
RC_INVALID_EXPOSURE_CAP = '91',
VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED = '92',
// old // old

16
package-lock.json generated
View File

@ -11142,14 +11142,6 @@
"requires": { "requires": {
"min-document": "^2.19.0", "min-document": "^2.19.0",
"process": "^0.11.10" "process": "^0.11.10"
},
"dependencies": {
"process": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz",
"integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=",
"dev": true
}
} }
}, },
"got": { "got": {
@ -12794,6 +12786,12 @@
"integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
"dev": true "dev": true
}, },
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
"dev": true
},
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -14795,7 +14793,7 @@
} }
}, },
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1a27c59c15ab1e95ee8e5c4ed6ad814c49cc439e", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"dev": true, "dev": true,
"requires": { "requires": {

View File

@ -265,6 +265,7 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
); );
await configureReservesByHelper(reservesParams, allReservesAddresses, testHelpers, admin); await configureReservesByHelper(reservesParams, allReservesAddresses, testHelpers, admin);
lendingPoolConfiguratorProxy.dropReserve(mockTokens.KNC.address);
const collateralManager = await deployLendingPoolCollateralManager(); const collateralManager = await deployLendingPoolCollateralManager();
await waitForTx( await waitForTx(

View File

@ -0,0 +1,501 @@
import BigNumber from 'bignumber.js';
import { TestEnv, makeSuite } from './helpers/make-suite';
import { APPROVAL_AMOUNT_LENDING_POOL, oneRay } from '../../helpers/constants';
import { convertToCurrencyDecimals, getContract } from '../../helpers/contracts-helpers';
import { ethers } from 'ethers';
import { MockFlashLoanReceiver } from '../../types/MockFlashLoanReceiver';
import { ProtocolErrors, eContractid } from '../../helpers/types';
import { VariableDebtToken } from '../../types/VariableDebtToken';
import { StableDebtToken } from '../../types/StableDebtToken';
import {
getMockFlashLoanReceiver,
getStableDebtToken,
getVariableDebtToken,
} from '../../helpers/contracts-getters';
const { expect } = require('chai');
makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
let _mockFlashLoanReceiver = {} as MockFlashLoanReceiver;
const {
VL_COLLATERAL_BALANCE_IS_0,
TRANSFER_AMOUNT_EXCEEDS_BALANCE,
LP_INVALID_FLASHLOAN_MODE,
SAFEERC20_LOWLEVEL_CALL,
LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN,
LP_BORROW_ALLOWANCE_NOT_ENOUGH,
} = ProtocolErrors;
before(async () => {
_mockFlashLoanReceiver = await getMockFlashLoanReceiver();
});
it('Authorize a flash borrower', async () => {
const { deployer, pool, weth, configurator } = testEnv;
await configurator.authorizeFlashBorrower(deployer.address);
});
it('Deposits WETH into the reserve', async () => {
const { pool, weth } = testEnv;
const userAddress = await pool.signer.getAddress();
const amountToDeposit = ethers.utils.parseEther('1');
await weth.mint(amountToDeposit);
await weth.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.deposit(weth.address, amountToDeposit, userAddress, '0');
});
it('Takes WETH flash loan with mode = 0, returns the funds correctly', async () => {
const { pool, helpersContract, weth } = testEnv;
await pool.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[ethers.utils.parseEther('0.8')],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
ethers.utils.parseUnits('10000');
const reserveData = await helpersContract.getReserveData(weth.address);
const currentLiquidityRate = reserveData.liquidityRate;
const currentLiquidityIndex = reserveData.liquidityIndex;
const totalLiquidity = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidity.toString()).to.be.equal('1000000000000000000');
expect(currentLiquidityRate.toString()).to.be.equal('0');
expect(currentLiquidityIndex.toString()).to.be.equal('1000000000000000000000000000');
});
it('Takes an ETH flash loan with mode = 0 as big as the available liquidity', async () => {
const { pool, helpersContract, weth } = testEnv;
const reserveDataBefore = await helpersContract.getReserveData(weth.address);
const txResult = await pool.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
['1000000000000000000'],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
const reserveData = await helpersContract.getReserveData(weth.address);
const currentLiquidityRate = reserveData.liquidityRate;
const currentLiquidityIndex = reserveData.liquidityIndex;
const totalLiquidity = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidity.toString()).to.be.equal('1000000000000000000');
expect(currentLiquidityRate.toString()).to.be.equal('0');
expect(currentLiquidityIndex.toString()).to.be.equal('1000000000000000000000000000');
});
it('Takes WETH flashloan, does not return the funds with mode = 0. (revert expected)', async () => {
const { pool, weth, users } = testEnv;
const caller = users[1];
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await expect(
pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[ethers.utils.parseEther('0.8')],
[0],
caller.address,
'0x10',
'0'
)
).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL);
});
it('Takes WETH flash loan, simulating a receiver as EOA (revert expected)', async () => {
const { pool, weth, users } = testEnv;
const caller = users[1];
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await _mockFlashLoanReceiver.setSimulateEOA(true);
await expect(
pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[ethers.utils.parseEther('0.8')],
[0],
caller.address,
'0x10',
'0'
)
).to.be.revertedWith(LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN);
});
it('Takes a WETH flashloan with an invalid mode. (revert expected)', async () => {
const { pool, weth, users } = testEnv;
const caller = users[1];
await _mockFlashLoanReceiver.setSimulateEOA(false);
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await expect(
pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[ethers.utils.parseEther('0.8')],
[4],
caller.address,
'0x10',
'0'
)
).to.be.reverted;
});
it('Caller deposits 1000 DAI as collateral, Takes WETH flashloan with mode = 2, does not return the funds. A variable loan for caller is created', async () => {
const { dai, pool, weth, users, helpersContract } = testEnv;
const caller = users[1];
await dai.connect(caller.signer).mint(await convertToCurrencyDecimals(dai.address, '1000'));
await dai.connect(caller.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000');
await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, caller.address, '0');
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[ethers.utils.parseEther('0.8')],
[2],
caller.address,
'0x10',
'0'
);
const { variableDebtTokenAddress } = await helpersContract.getReserveTokensAddresses(
weth.address
);
const wethDebtToken = await getVariableDebtToken(variableDebtTokenAddress);
const callerDebt = await wethDebtToken.balanceOf(caller.address);
expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt');
});
it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => {
const { pool, weth, users } = testEnv;
const caller = users[1];
await expect(
pool.connect(caller.signer).flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
['1000000000000000001'], //slightly higher than the available liquidity
[2],
caller.address,
'0x10',
'0'
),
TRANSFER_AMOUNT_EXCEEDS_BALANCE
).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL);
});
it('tries to take a flashloan using a non contract address as receiver (revert expected)', async () => {
const { pool, deployer, weth, users } = testEnv;
const caller = users[1];
await expect(
pool.flashLoan(
deployer.address,
[weth.address],
['1000000000000000000'],
[2],
caller.address,
'0x10',
'0'
)
).to.be.reverted;
});
it('Deposits USDC into the reserve', async () => {
const { usdc, pool } = testEnv;
const userAddress = await pool.signer.getAddress();
await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000'));
await usdc.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const amountToDeposit = await convertToCurrencyDecimals(usdc.address, '1000');
await pool.deposit(usdc.address, amountToDeposit, userAddress, '0');
});
it('Takes out a 500 USDC flashloan, returns the funds correctly', async () => {
const { usdc, pool, helpersContract, deployer: depositor } = testEnv;
await _mockFlashLoanReceiver.setFailExecutionTransfer(false);
const reserveDataBefore = await helpersContract.getReserveData(usdc.address);
const flashloanAmount = await convertToCurrencyDecimals(usdc.address, '500');
await pool.flashLoan(
_mockFlashLoanReceiver.address,
[usdc.address],
[flashloanAmount],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
const reserveDataAfter = helpersContract.getReserveData(usdc.address);
const reserveData = await helpersContract.getReserveData(usdc.address);
const userData = await helpersContract.getUserReserveData(usdc.address, depositor.address);
const totalLiquidity = reserveData.availableLiquidity
.add(reserveData.totalStableDebt)
.add(reserveData.totalVariableDebt)
.toString();
const currentLiquidityRate = reserveData.liquidityRate.toString();
const currentLiquidityIndex = reserveData.liquidityIndex.toString();
const currentUserBalance = userData.currentATokenBalance.toString();
const expectedLiquidity = await convertToCurrencyDecimals(usdc.address, '1000');
expect(totalLiquidity).to.be.equal(expectedLiquidity, 'Invalid total liquidity');
expect(currentLiquidityRate).to.be.equal('0', 'Invalid liquidity rate');
expect(currentLiquidityIndex).to.be.equal(
new BigNumber('1.00000').multipliedBy(oneRay).toFixed(),
'Invalid liquidity index'
);
expect(currentUserBalance.toString()).to.be.equal(expectedLiquidity, 'Invalid user balance');
});
it('Takes out a 500 USDC flashloan with mode = 0, does not return the funds. (revert expected)', async () => {
const { usdc, pool, users } = testEnv;
const caller = users[2];
const flashloanAmount = await convertToCurrencyDecimals(usdc.address, '500');
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await expect(
pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[usdc.address],
[flashloanAmount],
[2],
caller.address,
'0x10',
'0'
)
).to.be.revertedWith(VL_COLLATERAL_BALANCE_IS_0);
});
it('Caller deposits 5 WETH as collateral, Takes a USDC flashloan with mode = 2, does not return the funds. A loan for caller is created', async () => {
const { usdc, pool, weth, users, helpersContract } = testEnv;
const caller = users[2];
await weth.connect(caller.signer).mint(await convertToCurrencyDecimals(weth.address, '5'));
await weth.connect(caller.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const amountToDeposit = await convertToCurrencyDecimals(weth.address, '5');
await pool.connect(caller.signer).deposit(weth.address, amountToDeposit, caller.address, '0');
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
const flashloanAmount = await convertToCurrencyDecimals(usdc.address, '500');
await pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[usdc.address],
[flashloanAmount],
[2],
caller.address,
'0x10',
'0'
);
const { variableDebtTokenAddress } = await helpersContract.getReserveTokensAddresses(
usdc.address
);
const usdcDebtToken = await getVariableDebtToken(variableDebtTokenAddress);
const callerDebt = await usdcDebtToken.balanceOf(caller.address);
expect(callerDebt.toString()).to.be.equal('500000000', 'Invalid user debt');
});
it('Caller deposits 1000 DAI as collateral, Takes a WETH flashloan with mode = 0, does not approve the transfer of the funds', async () => {
const { dai, pool, weth, users } = testEnv;
const caller = users[3];
await dai.connect(caller.signer).mint(await convertToCurrencyDecimals(dai.address, '1000'));
await dai.connect(caller.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000');
await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, caller.address, '0');
const flashAmount = ethers.utils.parseEther('0.8');
await _mockFlashLoanReceiver.setFailExecutionTransfer(false);
await _mockFlashLoanReceiver.setAmountToApprove(flashAmount.div(2));
await expect(
pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[0],
caller.address,
'0x10',
'0'
)
).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL);
});
it('Caller takes a WETH flashloan with mode = 1', async () => {
const { dai, pool, weth, users, helpersContract } = testEnv;
const caller = users[3];
const flashAmount = ethers.utils.parseEther('0.8');
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[1],
caller.address,
'0x10',
'0'
);
const { stableDebtTokenAddress } = await helpersContract.getReserveTokensAddresses(
weth.address
);
const wethDebtToken = await getStableDebtToken(stableDebtTokenAddress);
const callerDebt = await wethDebtToken.balanceOf(caller.address);
expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt');
});
it('Caller takes a WETH flashloan with mode = 1 onBehalfOf user without allowance', async () => {
const { dai, pool, weth, users, helpersContract } = testEnv;
const caller = users[5];
const onBehalfOf = users[4];
// Deposit 1000 dai for onBehalfOf user
await dai.connect(onBehalfOf.signer).mint(await convertToCurrencyDecimals(dai.address, '1000'));
await dai.connect(onBehalfOf.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000');
await pool
.connect(onBehalfOf.signer)
.deposit(dai.address, amountToDeposit, onBehalfOf.address, '0');
const flashAmount = ethers.utils.parseEther('0.8');
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await expect(
pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[1],
onBehalfOf.address,
'0x10',
'0'
)
).to.be.revertedWith(LP_BORROW_ALLOWANCE_NOT_ENOUGH);
});
it('Caller takes a WETH flashloan with mode = 1 onBehalfOf user with allowance. A loan for onBehalfOf is creatd.', async () => {
const { dai, pool, weth, users, helpersContract } = testEnv;
const caller = users[5];
const onBehalfOf = users[4];
const flashAmount = ethers.utils.parseEther('0.8');
const reserveData = await pool.getReserveData(weth.address);
const stableDebtToken = await getStableDebtToken(reserveData.stableDebtTokenAddress);
// Deposited for onBehalfOf user already, delegate borrow allowance
await stableDebtToken.connect(onBehalfOf.signer).approveDelegation(caller.address, flashAmount);
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[1],
onBehalfOf.address,
'0x10',
'0'
);
const { stableDebtTokenAddress } = await helpersContract.getReserveTokensAddresses(
weth.address
);
const wethDebtToken = await getStableDebtToken(stableDebtTokenAddress);
const onBehalfOfDebt = await wethDebtToken.balanceOf(onBehalfOf.address);
expect(onBehalfOfDebt.toString()).to.be.equal(
'800000000000000000',
'Invalid onBehalfOf user debt'
);
});
});

View File

@ -73,7 +73,148 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => {
expect(await configurator.signer.getAddress()).to.be.equal( expect(await configurator.signer.getAddress()).to.be.equal(
await addressesProvider.getPoolAdmin() await addressesProvider.getPoolAdmin()
); );
await configurator.pauseReserve(weth.address);
const {
decimals,
ltv,
liquidationBonus,
liquidationThreshold,
reserveFactor,
stableBorrowRateEnabled,
borrowingEnabled,
isActive,
isFrozen,
} = await helpersContract.getReserveConfigurationData(weth.address);
const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address);
const isPaused = await helpersContract.getPaused(weth.address);
expect(borrowingEnabled).to.be.equal(true);
expect(isActive).to.be.equal(true);
expect(isPaused).to.be.equal(true);
expect(isFrozen).to.be.equal(false);
expect(decimals).to.be.equal(strategyWETH.reserveDecimals);
expect(ltv).to.be.equal(strategyWETH.baseLTVAsCollateral);
expect(liquidationThreshold).to.be.equal(strategyWETH.liquidationThreshold);
expect(liquidationBonus).to.be.equal(strategyWETH.liquidationBonus);
expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled);
expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor);
expect(borrowCap).to.be.equal(strategyWETH.borrowCap);
expect(supplyCap).to.be.equal(strategyWETH.supplyCap);
});
it('Unpauses the ETH reserve by pool admin ', async () => {
const { configurator, helpersContract, weth } = testEnv;
await configurator.unpauseReserve(weth.address);
const {
decimals,
ltv,
liquidationBonus,
liquidationThreshold,
reserveFactor,
stableBorrowRateEnabled,
borrowingEnabled,
isActive,
isFrozen,
} = await helpersContract.getReserveConfigurationData(weth.address);
const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address);
const isPaused = await helpersContract.getPaused(weth.address);
expect(borrowingEnabled).to.be.equal(true);
expect(isActive).to.be.equal(true);
expect(isPaused).to.be.equal(false);
expect(isFrozen).to.be.equal(false);
expect(decimals).to.be.equal(strategyWETH.reserveDecimals);
expect(ltv).to.be.equal(strategyWETH.baseLTVAsCollateral);
expect(liquidationThreshold).to.be.equal(strategyWETH.liquidationThreshold);
expect(liquidationBonus).to.be.equal(strategyWETH.liquidationBonus);
expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled);
expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor);
expect(borrowCap).to.be.equal(strategyWETH.borrowCap);
expect(supplyCap).to.be.equal(strategyWETH.supplyCap);
});
it('Pauses the ETH reserve by emergency admin', async () => {
const { configurator, weth, helpersContract, addressesProvider, users, emergencyAdmin } =
testEnv;
await configurator.connect(emergencyAdmin.signer).pauseReserve(weth.address);
const {
decimals,
ltv,
liquidationBonus,
liquidationThreshold,
reserveFactor,
stableBorrowRateEnabled,
borrowingEnabled,
isActive,
isFrozen,
} = await helpersContract.getReserveConfigurationData(weth.address);
const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address);
const isPaused = await helpersContract.getPaused(weth.address);
expect(borrowingEnabled).to.be.equal(true);
expect(isActive).to.be.equal(true);
expect(isPaused).to.be.equal(true);
expect(isFrozen).to.be.equal(false);
expect(decimals).to.be.equal(strategyWETH.reserveDecimals);
expect(ltv).to.be.equal(strategyWETH.baseLTVAsCollateral);
expect(liquidationThreshold).to.be.equal(strategyWETH.liquidationThreshold);
expect(liquidationBonus).to.be.equal(strategyWETH.liquidationBonus);
expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled);
expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor);
expect(borrowCap).to.be.equal(strategyWETH.borrowCap);
expect(supplyCap).to.be.equal(strategyWETH.supplyCap);
});
it('Unpauses the ETH reserve by emergency admin ', async () => {
const { configurator, helpersContract, weth, users, emergencyAdmin } = testEnv;
await configurator.connect(emergencyAdmin.signer).unpauseReserve(weth.address);
const {
decimals,
ltv,
liquidationBonus,
liquidationThreshold,
reserveFactor,
stableBorrowRateEnabled,
borrowingEnabled,
isActive,
isFrozen,
} = await helpersContract.getReserveConfigurationData(weth.address);
const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address);
const isPaused = await helpersContract.getPaused(weth.address);
expect(borrowingEnabled).to.be.equal(true);
expect(isActive).to.be.equal(true);
expect(isPaused).to.be.equal(false);
expect(isFrozen).to.be.equal(false);
expect(decimals).to.be.equal(strategyWETH.reserveDecimals);
expect(ltv).to.be.equal(strategyWETH.baseLTVAsCollateral);
expect(liquidationThreshold).to.be.equal(strategyWETH.liquidationThreshold);
expect(liquidationBonus).to.be.equal(strategyWETH.liquidationBonus);
expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled);
expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor);
expect(borrowCap).to.be.equal(strategyWETH.borrowCap);
expect(supplyCap).to.be.equal(strategyWETH.supplyCap);
});
it('Check the only admin or emergency admin can pauseReserve ', async () => {
const { configurator, users, weth, riskAdmin } = testEnv;
await expect(
configurator.connect(riskAdmin.signer).pauseReserve(weth.address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(LPC_CALLER_NOT_EMERGENCY_OR_POOL_ADMIN);
});
it('Check the only admin or emergency admin can unpauseReserve ', async () => {
const { configurator, users, weth, riskAdmin } = testEnv;
await expect(
configurator.connect(riskAdmin.signer).unpauseReserve(weth.address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(LPC_CALLER_NOT_EMERGENCY_OR_POOL_ADMIN);
});
it('Pauses the ETH reserve by the pool admin', async () => {
const { configurator, weth, helpersContract, addressesProvider, users, emergencyAdmin } =
testEnv;
await configurator.pauseReserve(weth.address); await configurator.pauseReserve(weth.address);
const { const {
decimals, decimals,
@ -297,7 +438,6 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => {
}); });
it('Freezes the ETH reserve by Risk Admin', async () => { it('Freezes the ETH reserve by Risk Admin', async () => {
const { configurator, weth, helpersContract, riskAdmin } = testEnv; const { configurator, weth, helpersContract, riskAdmin } = testEnv;
await configurator.connect(riskAdmin.signer).freezeReserve(weth.address); await configurator.connect(riskAdmin.signer).freezeReserve(weth.address);
const { const {
decimals, decimals,
@ -944,6 +1084,36 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => {
expect(exposureCap).to.be.equal(strategyWETH.exposureCap); expect(exposureCap).to.be.equal(strategyWETH.exposureCap);
expect(reserveFactor).to.be.equal(1000); expect(reserveFactor).to.be.equal(1000);
}); });
it('Changes the reserve factor of WETH risk admin', async () => {
const { configurator, helpersContract, weth, riskAdmin } = testEnv;
await configurator.connect(riskAdmin.signer).setReserveFactor(weth.address, '1000');
const {
decimals,
ltv,
liquidationBonus,
liquidationThreshold,
reserveFactor,
stableBorrowRateEnabled,
borrowingEnabled,
isActive,
isFrozen,
} = await helpersContract.getReserveConfigurationData(weth.address);
const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address);
const isPaused = await helpersContract.getPaused(weth.address);
expect(borrowingEnabled).to.be.equal(true);
expect(isActive).to.be.equal(true);
expect(isPaused).to.be.equal(false);
expect(isFrozen).to.be.equal(false);
expect(decimals).to.be.equal(strategyWETH.reserveDecimals);
expect(ltv).to.be.equal(strategyWETH.baseLTVAsCollateral);
expect(liquidationThreshold).to.be.equal(strategyWETH.liquidationThreshold);
expect(liquidationBonus).to.be.equal(strategyWETH.liquidationBonus);
expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled);
expect(borrowCap).to.be.equal(strategyWETH.borrowCap);
expect(supplyCap).to.be.equal(strategyWETH.supplyCap);
expect(reserveFactor).to.be.equal(1000);
});
it('Check that borrowCap cannot be set to value that exceeds the MAX_BORROW_CAP', async () => { it('Check that borrowCap cannot be set to value that exceeds the MAX_BORROW_CAP', async () => {
const { configurator, users, weth } = testEnv; const { configurator, users, weth } = testEnv;
@ -1159,6 +1329,36 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => {
expect(supplyCap).to.be.equal('3000000'); expect(supplyCap).to.be.equal('3000000');
expect(exposureCap).to.be.equal('3000000'); expect(exposureCap).to.be.equal('3000000');
}); });
it('Changes the supply Cap of WETH via risk admin', async () => {
const { configurator, helpersContract, weth, riskAdmin } = testEnv;
await configurator.connect(riskAdmin.signer).setSupplyCap(weth.address, '3000000');
const {
decimals,
ltv,
liquidationBonus,
liquidationThreshold,
reserveFactor,
stableBorrowRateEnabled,
borrowingEnabled,
isActive,
isFrozen,
} = await helpersContract.getReserveConfigurationData(weth.address);
const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address);
const isPaused = await helpersContract.getPaused(weth.address);
expect(borrowingEnabled).to.be.equal(true);
expect(isActive).to.be.equal(true);
expect(isPaused).to.be.equal(false);
expect(isFrozen).to.be.equal(false);
expect(decimals).to.be.equal(strategyWETH.reserveDecimals);
expect(ltv).to.be.equal(strategyWETH.baseLTVAsCollateral);
expect(liquidationThreshold).to.be.equal(strategyWETH.liquidationThreshold);
expect(liquidationBonus).to.be.equal(strategyWETH.liquidationBonus);
expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled);
expect(reserveFactor).to.be.equal(1000);
expect(borrowCap).to.be.equal('3000000');
expect(supplyCap).to.be.equal('3000000');
});
it('Reverts when trying to disable the DAI reserve with liquidity on it', async () => { it('Reverts when trying to disable the DAI reserve with liquidity on it', async () => {
const { dai, pool, configurator } = testEnv; const { dai, pool, configurator } = testEnv;
@ -1196,4 +1396,62 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => {
expect(isNewRegistered).to.be.false; expect(isNewRegistered).to.be.false;
expect(isRiskAdminRegistered).to.be.false; expect(isRiskAdminRegistered).to.be.false;
}); });
it('Checks only pool admin can register/unregister a risk Admins', async () => {
const { dai, pool, configurator, users, riskAdmin, emergencyAdmin } = testEnv;
await expect(
configurator.connect(riskAdmin.signer).registerRiskAdmin(users[3].address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(CALLER_NOT_POOL_ADMIN);
await expect(
configurator.connect(riskAdmin.signer).unregisterRiskAdmin(users[3].address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(CALLER_NOT_POOL_ADMIN);
await expect(
configurator.connect(emergencyAdmin.signer).registerRiskAdmin(users[3].address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(CALLER_NOT_POOL_ADMIN);
await expect(
configurator.connect(emergencyAdmin.signer).unregisterRiskAdmin(users[3].address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(CALLER_NOT_POOL_ADMIN);
});
it('Authorized a new flash borrower', async () => {
const { dai, pool, configurator, users, riskAdmin } = testEnv;
await configurator.authorizeFlashBorrower(users[4].address);
const isFlashBorrowerAuthorized = await pool.isFlashBorrowerAuthorized(users[4].address);
expect(isFlashBorrowerAuthorized).to.be.true;
});
it('Unauthorized flash borrower', async () => {
const { dai, pool, configurator, users } = testEnv;
await configurator.unauthorizeFlashBorrower(users[4].address);
const isFlashBorrowerAuthorized = await pool.isFlashBorrowerAuthorized(users[4].address);
expect(isFlashBorrowerAuthorized).to.be.false;
});
it('Checks only pool admin can authorize/unauthorize a flash borrower', async () => {
const { dai, pool, configurator, users, riskAdmin, emergencyAdmin } = testEnv;
await expect(
configurator.connect(riskAdmin.signer).authorizeFlashBorrower(users[3].address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(CALLER_NOT_POOL_ADMIN);
await expect(
configurator.connect(riskAdmin.signer).authorizeFlashBorrower(users[3].address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(CALLER_NOT_POOL_ADMIN);
await expect(
configurator.connect(emergencyAdmin.signer).unauthorizeFlashBorrower(users[3].address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(CALLER_NOT_POOL_ADMIN);
await expect(
configurator.connect(emergencyAdmin.signer).unauthorizeFlashBorrower(users[3].address),
CALLER_NOT_POOL_ADMIN
).to.be.revertedWith(CALLER_NOT_POOL_ADMIN);
});
}); });

View File

@ -0,0 +1,104 @@
import { makeSuite, TestEnv } from './helpers/make-suite';
import { ProtocolErrors, RateMode } from '../../helpers/types';
import { APPROVAL_AMOUNT_LENDING_POOL, MAX_UINT_AMOUNT, oneEther } from '../../helpers/constants';
import { convertToCurrencyDecimals } from '../../helpers/contracts-helpers';
import { parseEther, parseUnits } from 'ethers/lib/utils';
import { BigNumber } from 'bignumber.js';
import { MockFlashLoanReceiver } from '../../types/MockFlashLoanReceiver';
import { getMockFlashLoanReceiver } from '../../helpers/contracts-getters';
import { domainToUnicode } from 'url';
const { expect } = require('chai');
makeSuite('Drop Reserve', (testEnv: TestEnv) => {
let _mockFlashLoanReceiver = {} as MockFlashLoanReceiver;
const { RL_ATOKEN_SUPPLY_NOT_ZERO, RL_STABLE_DEBT_NOT_ZERO, RL_VARIABLE_DEBT_SUPPLY_NOT_ZERO } =
ProtocolErrors;
before(async () => {
_mockFlashLoanReceiver = await getMockFlashLoanReceiver();
});
it('User 1 deposits Dai, User 2 borrow Dai stable and variable, should fail to drop Dai reserve', async () => {
const {
deployer,
users: [user1],
pool,
dai,
aDai,
weth,
configurator,
} = testEnv;
const depositedAmount = parseEther('1000');
const borrowedAmount = parseEther('100');
// setting reserve factor to 0 to ease tests, no aToken accrued in reserve
await configurator.setReserveFactor(dai.address, 0);
await dai.mint(depositedAmount);
await dai.approve(pool.address, depositedAmount);
await dai.connect(user1.signer).mint(depositedAmount);
await dai.connect(user1.signer).approve(pool.address, depositedAmount);
await weth.connect(user1.signer).mint(depositedAmount);
await weth.connect(user1.signer).approve(pool.address, depositedAmount);
await pool.deposit(dai.address, depositedAmount, deployer.address, 0);
await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(
RL_ATOKEN_SUPPLY_NOT_ZERO
);
await pool.connect(user1.signer).deposit(weth.address, depositedAmount, user1.address, 0);
await pool.connect(user1.signer).borrow(dai.address, borrowedAmount, 2, 0, user1.address);
await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(
RL_VARIABLE_DEBT_SUPPLY_NOT_ZERO
);
await pool.connect(user1.signer).borrow(dai.address, borrowedAmount, 1, 0, user1.address);
await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(RL_STABLE_DEBT_NOT_ZERO);
});
it('User 2 repays debts, drop Dai reserve should fail', async () => {
const {
deployer,
users: [user1],
pool,
dai,
weth,
configurator,
} = testEnv;
await pool.connect(user1.signer).repay(dai.address, MAX_UINT_AMOUNT, 1, user1.address);
await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(
RL_VARIABLE_DEBT_SUPPLY_NOT_ZERO
);
await pool.connect(user1.signer).repay(dai.address, MAX_UINT_AMOUNT, 2, user1.address);
await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(
RL_ATOKEN_SUPPLY_NOT_ZERO
);
});
it('User 1 withdraw Dai, drop Dai reserve should succeed', async () => {
const {
deployer,
users: [user1],
pool,
dai,
aDai,
weth,
configurator,
helpersContract,
} = testEnv;
await pool.withdraw(dai.address, MAX_UINT_AMOUNT, deployer.address);
await configurator.dropReserve(dai.address);
const tokens = await pool.getReservesList();
expect(tokens.includes(dai.address)).to.be.false;
const { isActive } = await helpersContract.getReserveConfigurationData(dai.address);
expect(isActive).to.be.false;
});
});

View File

@ -1,7 +1,7 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { TestEnv, makeSuite } from './helpers/make-suite'; import { TestEnv, makeSuite } from './helpers/make-suite';
import { APPROVAL_AMOUNT_LENDING_POOL, oneRay } from '../../helpers/constants'; import { APPROVAL_AMOUNT_LENDING_POOL, MAX_UINT_AMOUNT, oneRay } from '../../helpers/constants';
import { convertToCurrencyDecimals, getContract } from '../../helpers/contracts-helpers'; import { convertToCurrencyDecimals, getContract } from '../../helpers/contracts-helpers';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import { MockFlashLoanReceiver } from '../../types/MockFlashLoanReceiver'; import { MockFlashLoanReceiver } from '../../types/MockFlashLoanReceiver';
@ -32,7 +32,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
}); });
it('Deposits WETH into the reserve', async () => { it('Deposits WETH into the reserve', async () => {
const { pool, weth } = testEnv; const { pool, weth, aave } = testEnv;
const userAddress = await pool.signer.getAddress(); const userAddress = await pool.signer.getAddress();
const amountToDeposit = ethers.utils.parseEther('1'); const amountToDeposit = ethers.utils.parseEther('1');
@ -41,52 +41,124 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await weth.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); await weth.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.deposit(weth.address, amountToDeposit, userAddress, '0'); await pool.deposit(weth.address, amountToDeposit, userAddress, '0');
await aave.mint(amountToDeposit);
await aave.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.deposit(aave.address, amountToDeposit, userAddress, '0');
}); });
it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => { it('Takes WETH flash loan with mode = 0, returns the funds correctly', async () => {
const { pool, helpersContract, weth } = testEnv; const { pool, helpersContract, weth, aWETH } = testEnv;
const flashBorrowedAmount = ethers.utils.parseEther('0.8');
const fees = new BigNumber(flashBorrowedAmount.mul(9).div(10000).toString());
let reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
await pool.flashLoan( await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
[weth.address], [weth.address],
[ethers.utils.parseEther('0.8')], [flashBorrowedAmount],
[0], [0],
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
'0x10', '0x10',
'0' '0'
); );
ethers.utils.parseUnits('10000'); await pool.mintToTreasury([weth.address]);
const reserveData = await helpersContract.getReserveData(weth.address); reserveData = await helpersContract.getReserveData(weth.address);
const currentLiquidityRate = reserveData.liquidityRate; const currentLiquidityRate = reserveData.liquidityRate;
const currentLiquidityIndex = reserveData.liquidityIndex; const currentLiquidityIndex = reserveData.liquidityIndex;
const totalLiquidity = new BigNumber(reserveData.availableLiquidity.toString()) const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString()) .plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString()); .plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidity.toString()).to.be.equal('1000720000000000000'); expect(totalLiquidityBefore.plus(fees).toString()).to.be.equal(totalLiquidityAfter.toString());
expect(currentLiquidityRate.toString()).to.be.equal('0'); expect(currentLiquidityRate.toString()).to.be.equal('0');
expect(currentLiquidityIndex.toString()).to.be.equal('1000720000000000000000000000'); expect(currentLiquidityIndex.toString()).to.be.equal('1000720000000000000000000000');
}); });
it('Takes an authorized AAVE flash loan with mode = 0, returns the funds correctly', async () => {
const {
pool,
helpersContract,
aave,
configurator,
users: [, , , authorizedUser],
} = testEnv;
await configurator.authorizeFlashBorrower(authorizedUser.address);
const flashBorrowedAmount = ethers.utils.parseEther('0.8');
const fees = new BigNumber(0);
let reserveData = await helpersContract.getReserveData(aave.address);
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
await pool
.connect(authorizedUser.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[aave.address],
[flashBorrowedAmount],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
await pool.mintToTreasury([aave.address]);
ethers.utils.parseUnits('10000');
reserveData = await helpersContract.getReserveData(aave.address);
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidityBefore.plus(fees).toString()).to.be.equal(totalLiquidityAfter.toString());
});
it('Takes an ETH flashloan with mode = 0 as big as the available liquidity', async () => { it('Takes an ETH flashloan with mode = 0 as big as the available liquidity', async () => {
const { pool, helpersContract, weth } = testEnv; const { pool, helpersContract, weth } = testEnv;
const reserveDataBefore = await helpersContract.getReserveData(weth.address); let reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
const flashBorrowedAmount = totalLiquidityBefore.toString();
const fees = new BigNumber(flashBorrowedAmount).multipliedBy(9).dividedBy(10000).toString();
const txResult = await pool.flashLoan( const txResult = await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
[weth.address], [weth.address],
['1000720000000000000'], [totalLiquidityBefore.toString()],
[0], [0],
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
'0x10', '0x10',
'0' '0'
); );
const reserveData = await helpersContract.getReserveData(weth.address); await pool.mintToTreasury([weth.address]);
reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
const currentLiqudityRate = reserveData.liquidityRate; const currentLiqudityRate = reserveData.liquidityRate;
const currentLiquidityIndex = reserveData.liquidityIndex; const currentLiquidityIndex = reserveData.liquidityIndex;
@ -177,6 +249,12 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await _mockFlashLoanReceiver.setFailExecutionTransfer(true); await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
let reserveData = await helpersContract.getReserveData(weth.address);
let totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
await pool await pool
.connect(caller.signer) .connect(caller.signer)
.flashLoan( .flashLoan(
@ -191,14 +269,25 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const { variableDebtTokenAddress } = await helpersContract.getReserveTokensAddresses( const { variableDebtTokenAddress } = await helpersContract.getReserveTokensAddresses(
weth.address weth.address
); );
reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidityAfter.toString()).to.be.equal(
ethers.BigNumber.from(totalLiquidityBefore.toString())
);
const wethDebtToken = await getVariableDebtToken(variableDebtTokenAddress); const wethDebtToken = await getVariableDebtToken(variableDebtTokenAddress);
const callerDebt = await wethDebtToken.balanceOf(caller.address); const callerDebt = await wethDebtToken.balanceOf(caller.address);
expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt'); expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt');
// repays debt for later, so no interest accrue
await weth.connect(caller.signer).mint(await convertToCurrencyDecimals(weth.address, '1000'));
await weth.connect(caller.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(caller.signer).repay(weth.address, MAX_UINT_AMOUNT, 2, caller.address);
}); });
it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => { it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => {
const { pool, weth, users } = testEnv; const { pool, weth, users } = testEnv;
const caller = users[1]; const caller = users[1];

View File

@ -1196,7 +1196,7 @@ const calcLinearInterest = (
return cumulatedInterest; return cumulatedInterest;
}; };
const calcCompoundedInterest = ( export const calcCompoundedInterest = (
rate: BigNumber, rate: BigNumber,
currentTimestamp: BigNumber, currentTimestamp: BigNumber,
lastUpdateTimestamp: BigNumber lastUpdateTimestamp: BigNumber

View File

@ -0,0 +1,90 @@
import { makeSuite, TestEnv } from './helpers/make-suite';
import { RateMode } from '../../helpers/types';
import { APPROVAL_AMOUNT_LENDING_POOL, ONE_YEAR } from '../../helpers/constants';
import { convertToCurrencyDecimals } from '../../helpers/contracts-helpers';
import { BigNumber } from 'bignumber.js';
import { advanceTimeAndBlock, waitForTx } from '../../helpers/misc-utils';
import './helpers/utils/math';
const { expect } = require('chai');
makeSuite('Mint to treasury', (testEnv: TestEnv) => {
it('User 0 deposits 1000 DAI. Borrower borrows 100 DAI. Clock moved forward one year. Calculates and verifies the amount accrued to the treasury', async () => {
const { users, pool, dai, helpersContract } = testEnv;
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
const amountDAItoBorrow = await convertToCurrencyDecimals(dai.address, '100');
await waitForTx(await dai.connect(users[0].signer).mint(amountDAItoDeposit));
// user 0 deposits 1000 DAI
await waitForTx(
await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL)
);
await waitForTx(
await pool
.connect(users[0].signer)
.deposit(dai.address, amountDAItoDeposit, users[0].address, '0')
);
await waitForTx(
await pool
.connect(users[0].signer)
.borrow(dai.address, amountDAItoBorrow, RateMode.Variable, '0', users[0].address)
);
const { reserveFactor } = await helpersContract.getReserveConfigurationData(dai.address);
await advanceTimeAndBlock(parseInt(ONE_YEAR));
await waitForTx(await dai.connect(users[0].signer).mint(amountDAItoDeposit));
await waitForTx(
await pool
.connect(users[0].signer)
.deposit(dai.address, amountDAItoDeposit, users[0].address, '0')
);
const { liquidityIndex, variableBorrowIndex } = await pool.getReserveData(dai.address);
const amountBorrowedBN = new BigNumber(amountDAItoBorrow.toString());
const liquidityIndexBN = new BigNumber(liquidityIndex.toString());
const variableBorrowIndexBN = new BigNumber(variableBorrowIndex.toString());
const expectedAccruedToTreasury = amountBorrowedBN
.rayMul(variableBorrowIndexBN)
.minus(amountBorrowedBN)
.times(reserveFactor.toString())
.div(10000)
.rayDiv(liquidityIndexBN)
.toFixed(0);
const { accruedToTreasury } = await pool.getReserveData(dai.address);
console.log("Accrued to treasury ", accruedToTreasury.toString());
expect(accruedToTreasury.toString()).to.be.bignumber.almostEqual(
expectedAccruedToTreasury,
'Invalid amount accrued to the treasury'
);
});
it('Mints the accrued to the treasury', async () => {
const { users, pool, dai, aDai, helpersContract } = testEnv;
const treasuryAddress = await aDai.RESERVE_TREASURY_ADDRESS();
const { accruedToTreasury } = await pool.getReserveData(dai.address);
await waitForTx(await pool.connect(users[0].signer).mintToTreasury([dai.address]));
const normalizedIncome = await pool.getReserveNormalizedIncome(dai.address);
const treasuryBalance = await aDai.balanceOf(treasuryAddress);
const expectedTreasuryBalance = new BigNumber(accruedToTreasury.toString()).rayMul(
new BigNumber(normalizedIncome.toString())
);
expect(treasuryBalance.toString()).to.be.bignumber.almostEqual(expectedTreasuryBalance, "Invalid treasury balance after minting");
});
});

View File

@ -22,6 +22,7 @@ makeSuite('Pause Reserve', (testEnv: TestEnv) => {
_mockFlashLoanReceiver = await getMockFlashLoanReceiver(); _mockFlashLoanReceiver = await getMockFlashLoanReceiver();
}); });
it('User 0 deposits 1000 DAI. Configurator pauses pool. Transfers to user 1 reverts. Configurator unpauses the network and next transfer succeeds', async () => { it('User 0 deposits 1000 DAI. Configurator pauses pool. Transfers to user 1 reverts. Configurator unpauses the network and next transfer succeeds', async () => {
const { users, pool, dai, aDai, configurator } = testEnv; const { users, pool, dai, aDai, configurator } = testEnv;

View File

@ -1195,7 +1195,7 @@ const calcLinearInterest = (
return cumulatedInterest; return cumulatedInterest;
}; };
const calcCompoundedInterest = ( export const calcCompoundedInterest = (
rate: BigNumber, rate: BigNumber,
currentTimestamp: BigNumber, currentTimestamp: BigNumber,
lastUpdateTimestamp: BigNumber lastUpdateTimestamp: BigNumber