mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Merge branch 'protocol-2.5' into feat/2.5-exposure-ceiling
This commit is contained in:
commit
9727c0ab79
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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
16
package-lock.json
generated
|
@ -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": {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
501
test-suites/test-aave/authorized-flashloan.spec.ts
Normal file
501
test-suites/test-aave/authorized-flashloan.spec.ts
Normal 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'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
104
test-suites/test-aave/drop-reserve.spec.ts
Normal file
104
test-suites/test-aave/drop-reserve.spec.ts
Normal 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;
|
||||||
|
});
|
||||||
|
});
|
|
@ -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];
|
||||||
|
|
|
@ -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
|
||||||
|
|
90
test-suites/test-aave/mint-to-treasury.spec.ts
Normal file
90
test-suites/test-aave/mint-to-treasury.spec.ts
Normal 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");
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user