diff --git a/contracts/configuration/LendingPoolAddressesProvider.sol b/contracts/configuration/LendingPoolAddressesProvider.sol index 15b37635..38aa1280 100644 --- a/contracts/configuration/LendingPoolAddressesProvider.sol +++ b/contracts/configuration/LendingPoolAddressesProvider.sol @@ -19,16 +19,11 @@ contract LendingPoolAddressesProvider is Ownable, ILendingPoolAddressesProvider mapping(bytes32 => address) private _addresses; bytes32 private constant LENDING_POOL = 'LENDING_POOL'; - bytes32 private constant LENDING_POOL_CORE = 'LENDING_POOL_CORE'; bytes32 private constant LENDING_POOL_CONFIGURATOR = 'LENDING_POOL_CONFIGURATOR'; bytes32 private constant AAVE_ADMIN = 'AAVE_ADMIN'; bytes32 private constant LENDING_POOL_COLLATERAL_MANAGER = 'COLLATERAL_MANAGER'; - bytes32 private constant LENDING_POOL_FLASHLOAN_PROVIDER = 'FLASHLOAN_PROVIDER'; - bytes32 private constant DATA_PROVIDER = 'DATA_PROVIDER'; - bytes32 private constant ETHEREUM_ADDRESS = 'ETHEREUM_ADDRESS'; bytes32 private constant PRICE_ORACLE = 'PRICE_ORACLE'; bytes32 private constant LENDING_RATE_ORACLE = 'LENDING_RATE_ORACLE'; - bytes32 private constant WALLET_BALANCE_PROVIDER = 'WALLET_BALANCE_PROVIDER'; /** * @dev Sets an address for an id, allowing to cover it or not with a proxy diff --git a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol index 784d0fa3..af9e384f 100644 --- a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol +++ b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol @@ -12,6 +12,7 @@ interface IFlashLoanReceiver { address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, + address initiator, bytes calldata params ) external returns (bool); } diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 450def2d..c6da3c32 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -27,16 +27,17 @@ interface ILendingPool { * @dev emitted during a withdraw action. * @param reserve the address of the reserve * @param user the address of the user + * @param to address that will receive the underlying * @param amount the amount to be withdrawn **/ - event Withdraw(address indexed reserve, address indexed user, uint256 amount); + event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount); event BorrowAllowanceDelegated( - address indexed asset, address indexed fromUser, address indexed toUser, - uint256 interestRateMode, - uint256 amount + address[] assets, + uint256[] interestRateModes, + uint256[] amounts ); /** * @dev emitted on borrow @@ -74,7 +75,7 @@ interface ILendingPool { * @param reserve the address of the reserve * @param user the address of the user executing the swap **/ - event Swap(address indexed reserve, address indexed user); + event Swap(address indexed reserve, address indexed user, uint256 rateMode); /** * @dev emitted when a user enables a reserve as collateral @@ -99,20 +100,18 @@ interface ILendingPool { /** * @dev emitted when a flashloan is executed * @param target the address of the flashLoanReceiver - * @param mode Type of the debt to open if the flash loan is not returned. 0 -> Don't open any debt, just revert, 1 -> stable, 2 -> variable - * @param onBehalfOf the address incurring the debt, if borrow mode is not 0 - * @param assets the address of the assets being flashborrowed - * @param amounts the amount requested - * @param premiums the total fee on the amount + * @param initiator the address initiating the flash loan + * @param asset the address of the asset being flashborrowed + * @param amount the amount requested + * @param premium the total fee on the amount * @param referralCode the referral code of the caller **/ event FlashLoan( address indexed target, - uint256 mode, - address indexed onBehalfOf, - address[] assets, - uint256[] amounts, - uint256[] premiums, + address indexed initiator, + address indexed asset, + uint256 amount, + uint256 premium, uint16 referralCode ); @@ -188,21 +187,26 @@ interface ILendingPool { * @dev withdraws the assets of user. * @param reserve the address of the reserve * @param amount the underlying amount to be redeemed + * @param to address that will receive the underlying **/ - function withdraw(address reserve, uint256 amount) external; + function withdraw( + address reserve, + uint256 amount, + address to + ) external; /** - * @dev Sets allowance to borrow on a certain type of debt asset for a certain user address - * @param asset The underlying asset of the debt token + * @dev Sets allowance to borrow on a certain type of debt assets for a certain user address + * @param assets The underlying asset of each debt token * @param user The user to give allowance to - * @param interestRateMode Type of debt: 1 for stable, 2 for variable - * @param amount Allowance amount to borrow + * @param interestRateModes Types of debt: 1 for stable, 2 for variable + * @param amounts Allowance amounts to borrow **/ function delegateBorrowAllowance( - address asset, + address[] calldata assets, address user, - uint256 interestRateMode, - uint256 amount + uint256[] calldata interestRateModes, + uint256[] calldata amounts ) external; function getBorrowAllowance( @@ -289,7 +293,7 @@ interface ILendingPool { * @param receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface. * @param assets the address of the principal reserve * @param amounts the amount requested for this flashloan - * @param mode the flashloan mode + * @param modes the flashloan borrow modes * @param params a bytes array to be sent to the flashloan executor * @param referralCode the referral code of the caller **/ @@ -297,7 +301,7 @@ interface ILendingPool { address receiver, address[] calldata assets, uint256[] calldata amounts, - uint256 mode, + uint256[] calldata modes, address onBehalfOf, bytes calldata params, uint16 referralCode diff --git a/contracts/interfaces/ISwapAdapter.sol b/contracts/interfaces/ISwapAdapter.sol deleted file mode 100644 index ed91f95f..00000000 --- a/contracts/interfaces/ISwapAdapter.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.6.8; - -interface ISwapAdapter { - /** - * @dev Swaps an `amountToSwap` of an asset to another, approving a `fundsDestination` to pull the funds - * @param assetToSwapFrom Origin asset - * @param assetToSwapTo Destination asset - * @param amountToSwap How much `assetToSwapFrom` needs to be swapped - * @param fundsDestination Address that will be pulling the swapped funds - * @param params Additional variadic field to include extra params - */ - function executeOperation( - address assetToSwapFrom, - address assetToSwapTo, - uint256 amountToSwap, - address fundsDestination, - bytes calldata params - ) external; -} diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 5fe926b2..4057f3b9 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -20,7 +20,6 @@ import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol' import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol'; import {DebtTokenBase} from '../tokenization/base/DebtTokenBase.sol'; import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; -import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol'; import {LendingPoolCollateralManager} from './LendingPoolCollateralManager.sol'; import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol'; @@ -120,8 +119,13 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * @dev withdraws the _reserves of user. * @param asset the address of the reserve * @param amount the underlying amount to be redeemed + * @param to address that will receive the underlying **/ - function withdraw(address asset, uint256 amount) external override { + function withdraw( + address asset, + uint256 amount, + address to + ) external override { _whenNotPaused(); ReserveLogic.ReserveData storage reserve = _reserves[asset]; @@ -155,9 +159,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage _usersConfig[msg.sender].setUsingAsCollateral(reserve.id, false); } - IAToken(aToken).burn(msg.sender, msg.sender, amountToWithdraw, reserve.liquidityIndex); + IAToken(aToken).burn(msg.sender, to, amountToWithdraw, reserve.liquidityIndex); - emit Withdraw(asset, msg.sender, amountToWithdraw); + emit Withdraw(asset, msg.sender, to, amountToWithdraw); } /** @@ -179,23 +183,32 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage } /** - * @dev Sets allowance to borrow on a certain type of debt asset for a certain user address - * @param asset The underlying asset of the debt token + * @dev Sets allowance to borrow on a certain type of debt assets for a certain user address + * @param assets The underlying asset of each debt token * @param user The user to give allowance to - * @param interestRateMode Type of debt: 1 for stable, 2 for variable - * @param amount Allowance amount to borrow + * @param interestRateModes Types of debt: 1 for stable, 2 for variable + * @param amounts Allowance amounts to borrow **/ function delegateBorrowAllowance( - address asset, + address[] calldata assets, address user, - uint256 interestRateMode, - uint256 amount + uint256[] calldata interestRateModes, + uint256[] calldata amounts ) external override { _whenNotPaused(); - address debtToken = _reserves[asset].getDebtTokenAddress(interestRateMode); - _borrowAllowance[debtToken][msg.sender][user] = amount; - emit BorrowAllowanceDelegated(asset, msg.sender, user, interestRateMode, amount); + uint256 countAssets = assets.length; + require( + countAssets == interestRateModes.length && countAssets == amounts.length, + Errors.LP_INCONSISTENT_PARAMS_LENGTH + ); + + for (uint256 i = 0; i < countAssets; i++) { + address debtToken = _reserves[assets[i]].getDebtTokenAddress(interestRateModes[i]); + _borrowAllowance[debtToken][msg.sender][user] = amounts[i]; + } + + emit BorrowAllowanceDelegated(msg.sender, user, assets, interestRateModes, amounts); } /** @@ -352,7 +365,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage reserve.updateInterestRates(asset, reserve.aTokenAddress, 0, 0); - emit Swap(asset, msg.sender); + emit Swap(asset, msg.sender, rateMode); } /** @@ -486,13 +499,13 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage struct FlashLoanLocalVars { IFlashLoanReceiver receiver; address oracle; - ReserveLogic.InterestRateMode debtMode; uint256 i; address currentAsset; address currentATokenAddress; uint256 currentAmount; uint256 currentPremium; uint256 currentAmountPlusPremium; + address debtToken; } /** @@ -502,7 +515,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * @param receiverAddress The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface. * @param assets The addresss of the assets being flashborrowed * @param amounts The amounts requested for this flashloan for each asset - * @param mode Type of the debt to open if the flash loan is not returned. 0 -> Don't open any debt, just revert, 1 -> stable, 2 -> variable + * @param modes Types of the debt to open if the flash loan is not returned. 0 -> Don't open any debt, just revert, 1 -> stable, 2 -> variable * @param onBehalfOf If mode is not 0, then the address to take the debt onBehalfOf. The onBehalfOf address must already have approved `msg.sender` to incur the debt on their behalf. * @param params Variadic packed params to pass to the receiver as extra information * @param referralCode Referral code of the flash loan @@ -511,7 +524,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address receiverAddress, address[] calldata assets, uint256[] calldata amounts, - uint256 mode, + uint256[] calldata modes, address onBehalfOf, bytes calldata params, uint16 referralCode @@ -520,13 +533,12 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage FlashLoanLocalVars memory vars; - ValidationLogic.validateFlashloan(assets, amounts, mode); + ValidationLogic.validateFlashloan(assets, amounts); address[] memory aTokenAddresses = new address[](assets.length); uint256[] memory premiums = new uint256[](assets.length); vars.receiver = IFlashLoanReceiver(receiverAddress); - vars.debtMode = ReserveLogic.InterestRateMode(mode); for (vars.i = 0; vars.i < assets.length; vars.i++) { aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress; @@ -539,7 +551,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage //execute action of the receiver require( - vars.receiver.executeOperation(assets, amounts, premiums, params), + vars.receiver.executeOperation(assets, amounts, premiums, msg.sender, params), Errors.LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN ); @@ -548,10 +560,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage vars.currentAmount = amounts[vars.i]; vars.currentPremium = premiums[vars.i]; vars.currentATokenAddress = aTokenAddresses[vars.i]; - vars.currentAmountPlusPremium = vars.currentAmount.add(vars.currentPremium); - if (vars.debtMode == ReserveLogic.InterestRateMode.NONE) { + if (ReserveLogic.InterestRateMode(modes[vars.i]) == ReserveLogic.InterestRateMode.NONE) { _reserves[vars.currentAsset].updateState(); _reserves[vars.currentAsset].cumulateToLiquidityIndex( IERC20(vars.currentATokenAddress).totalSupply(), @@ -571,13 +582,11 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ); } else { if (msg.sender != onBehalfOf) { - address debtToken = _reserves[vars.currentAsset].getDebtTokenAddress(mode); + vars.debtToken = _reserves[vars.currentAsset].getDebtTokenAddress(modes[vars.i]); - _borrowAllowance[debtToken][onBehalfOf][msg - .sender] = _borrowAllowance[debtToken][onBehalfOf][msg.sender].sub( - vars.currentAmount, - Errors.LP_BORROW_ALLOWANCE_ARE_NOT_ENOUGH - ); + _borrowAllowance[vars.debtToken][onBehalfOf][msg.sender] = _borrowAllowance[vars + .debtToken][onBehalfOf][msg.sender] + .sub(vars.currentAmount, Errors.LP_BORROW_ALLOWANCE_ARE_NOT_ENOUGH); } //if the user didn't choose to return the funds, the system checks if there @@ -588,14 +597,21 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage msg.sender, onBehalfOf, vars.currentAmount, - mode, + modes[vars.i], vars.currentATokenAddress, referralCode, false ) ); } - emit FlashLoan(receiverAddress, mode, onBehalfOf, assets, amounts, premiums, referralCode); + emit FlashLoan( + receiverAddress, + msg.sender, + vars.currentAsset, + vars.currentAmount, + vars.currentPremium, + referralCode + ); } } @@ -691,7 +707,13 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * @param asset the address of the reserve * @return the reserve normalized income */ - function getReserveNormalizedIncome(address asset) external override view returns (uint256) { + function getReserveNormalizedIncome(address asset) + external + virtual + override + view + returns (uint256) + { return _reserves[asset].getNormalizedIncome(); } @@ -875,6 +897,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ); ValidationLogic.validateBorrow( + vars.asset, reserve, vars.onBehalfOf, vars.amount, @@ -888,34 +911,34 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage oracle ); - uint256 reserveId = reserve.id; - if (!userConfig.isBorrowing(reserveId)) { - userConfig.setBorrowing(reserveId, true); - } - reserve.updateState(); //caching the current stable borrow rate uint256 currentStableRate = 0; + bool isFirstBorrowing = false; if ( ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE ) { currentStableRate = reserve.currentStableBorrowRate; - IStableDebtToken(reserve.stableDebtTokenAddress).mint( + isFirstBorrowing = IStableDebtToken(reserve.stableDebtTokenAddress).mint( vars.onBehalfOf, vars.amount, currentStableRate ); } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).mint( + isFirstBorrowing = IVariableDebtToken(reserve.variableDebtTokenAddress).mint( vars.onBehalfOf, vars.amount, reserve.variableBorrowIndex ); } + if (isFirstBorrowing) { + userConfig.setBorrowing(reserve.id, true); + } + reserve.updateInterestRates( vars.asset, vars.aTokenAddress, diff --git a/contracts/lendingpool/LendingPoolCollateralManager.sol b/contracts/lendingpool/LendingPoolCollateralManager.sol index 004022a7..993f7fe5 100644 --- a/contracts/lendingpool/LendingPoolCollateralManager.sol +++ b/contracts/lendingpool/LendingPoolCollateralManager.sol @@ -15,7 +15,6 @@ import {Helpers} from '../libraries/helpers/Helpers.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol'; -import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol'; import {Errors} from '../libraries/helpers/Errors.sol'; import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol'; import {LendingPoolStorage} from './LendingPoolStorage.sol'; diff --git a/contracts/lendingpool/LendingPoolConfigurator.sol b/contracts/lendingpool/LendingPoolConfigurator.sol index 8fd7c754..b02b11f5 100644 --- a/contracts/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/lendingpool/LendingPoolConfigurator.sol @@ -47,7 +47,7 @@ contract LendingPoolConfigurator is VersionedInitializable { * @param asset the address of the reserve * @param stableRateEnabled true if stable rate borrowing is enabled, false otherwise **/ - event BorrowingEnabledOnReserve(address asset, bool stableRateEnabled); + event BorrowingEnabledOnReserve(address indexed asset, bool stableRateEnabled); /** * @dev emitted when borrowing is disabled on a reserve @@ -116,42 +116,42 @@ contract LendingPoolConfigurator is VersionedInitializable { * @param asset the address of the reserve * @param ltv the new value for the loan to value **/ - event ReserveBaseLtvChanged(address asset, uint256 ltv); + event ReserveBaseLtvChanged(address indexed asset, uint256 ltv); /** * @dev emitted when a reserve factor is updated * @param asset the address of the reserve * @param factor the new reserve factor **/ - event ReserveFactorChanged(address asset, uint256 factor); + event ReserveFactorChanged(address indexed asset, uint256 factor); /** * @dev emitted when a reserve liquidation threshold is updated * @param asset the address of the reserve * @param threshold the new value for the liquidation threshold **/ - event ReserveLiquidationThresholdChanged(address asset, uint256 threshold); + event ReserveLiquidationThresholdChanged(address indexed asset, uint256 threshold); /** * @dev emitted when a reserve liquidation bonus is updated * @param asset the address of the reserve * @param bonus the new value for the liquidation bonus **/ - event ReserveLiquidationBonusChanged(address asset, uint256 bonus); + event ReserveLiquidationBonusChanged(address indexed asset, uint256 bonus); /** * @dev emitted when the reserve decimals are updated * @param asset the address of the reserve * @param decimals the new decimals **/ - event ReserveDecimalsChanged(address asset, uint256 decimals); + event ReserveDecimalsChanged(address indexed asset, uint256 decimals); /** * @dev emitted when a reserve interest strategy contract is updated * @param asset the address of the reserve * @param strategy the new address of the interest strategy contract **/ - event ReserveInterestRateStrategyChanged(address asset, address strategy); + event ReserveInterestRateStrategyChanged(address indexed asset, address strategy); /** * @dev emitted when an aToken implementation is upgraded @@ -159,7 +159,11 @@ contract LendingPoolConfigurator is VersionedInitializable { * @param proxy the aToken proxy address * @param implementation the new aToken implementation **/ - event ATokenUpgraded(address asset, address proxy, address implementation); + event ATokenUpgraded( + address indexed asset, + address indexed proxy, + address indexed implementation + ); /** * @dev emitted when the implementation of a stable debt token is upgraded @@ -167,7 +171,11 @@ contract LendingPoolConfigurator is VersionedInitializable { * @param proxy the stable debt token proxy address * @param implementation the new aToken implementation **/ - event StableDebtTokenUpgraded(address asset, address proxy, address implementation); + event StableDebtTokenUpgraded( + address indexed asset, + address indexed proxy, + address indexed implementation + ); /** * @dev emitted when the implementation of a variable debt token is upgraded @@ -175,7 +183,11 @@ contract LendingPoolConfigurator is VersionedInitializable { * @param proxy the variable debt token proxy address * @param implementation the new aToken implementation **/ - event VariableDebtTokenUpgraded(address asset, address proxy, address implementation); + event VariableDebtTokenUpgraded( + address indexed asset, + address indexed proxy, + address indexed implementation + ); ILendingPoolAddressesProvider internal addressesProvider; ILendingPool internal pool; diff --git a/contracts/libraries/helpers/Errors.sol b/contracts/libraries/helpers/Errors.sol index 60838c21..7345c5c3 100644 --- a/contracts/libraries/helpers/Errors.sol +++ b/contracts/libraries/helpers/Errors.sol @@ -89,6 +89,8 @@ library Errors { string public constant RC_INVALID_DECIMALS = '70'; string public constant RC_INVALID_RESERVE_FACTOR = '71'; string public constant LPAPR_INVALID_ADDRESSES_PROVIDER_ID = '72'; + string public constant VL_INCONSISTENT_FLASHLOAN_PARAMS = '73'; + string public constant LP_INCONSISTENT_PARAMS_LENGTH = '74'; enum CollateralManagerErrors { NO_ERROR, diff --git a/contracts/libraries/logic/ValidationLogic.sol b/contracts/libraries/logic/ValidationLogic.sol index 76539262..657b1d63 100644 --- a/contracts/libraries/logic/ValidationLogic.sol +++ b/contracts/libraries/logic/ValidationLogic.sol @@ -33,7 +33,7 @@ library ValidationLogic { * @param reserve the reserve state on which the user is depositing * @param amount the amount to be deposited */ - function validateDeposit(ReserveLogic.ReserveData storage reserve, uint256 amount) internal view { + function validateDeposit(ReserveLogic.ReserveData storage reserve, uint256 amount) external view { (bool isActive, bool isFreezed, , ) = reserve.configuration.getFlags(); require(amount > 0, Errors.VL_AMOUNT_NOT_GREATER_THAN_0); @@ -61,7 +61,7 @@ library ValidationLogic { mapping(uint256 => address) storage reserves, uint256 reservesCount, address oracle - ) internal view { + ) external view { require(amount > 0, Errors.VL_AMOUNT_NOT_GREATER_THAN_0); require(amount <= userBalance, Errors.VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE); @@ -104,6 +104,7 @@ library ValidationLogic { /** * @dev validates a borrow. + * @param asset the address of the asset to borrow * @param reserve the reserve state from which the user is borrowing * @param userAddress the address of the user * @param amount the amount to be borrowed @@ -117,6 +118,7 @@ library ValidationLogic { */ function validateBorrow( + address asset, ReserveLogic.ReserveData storage reserve, address userAddress, uint256 amount, @@ -203,6 +205,8 @@ library ValidationLogic { Errors.VL_CALLATERAL_SAME_AS_BORROWING_CURRENCY ); + vars.availableLiquidity = IERC20(asset).balanceOf(reserve.aTokenAddress); + //calculate the max available loan size in stable rate mode as a percentage of the //available liquidity uint256 maxLoanSizeStable = vars.availableLiquidity.percentMul(maxStableLoanPercent); @@ -251,15 +255,15 @@ library ValidationLogic { * @dev validates a swap of borrow rate mode. * @param reserve the reserve state on which the user is swapping the rate * @param userConfig the user reserves configuration - * @param stableBorrowBalance the stable borrow balance of the user - * @param variableBorrowBalance the stable borrow balance of the user + * @param stableDebt the stable debt of the user + * @param variableDebt the variable debt of the user * @param currentRateMode the rate mode of the borrow */ function validateSwapRateMode( ReserveLogic.ReserveData storage reserve, UserConfiguration.Map storage userConfig, - uint256 stableBorrowBalance, - uint256 variableBorrowBalance, + uint256 stableDebt, + uint256 variableDebt, ReserveLogic.InterestRateMode currentRateMode ) external view { (bool isActive, bool isFreezed, , bool stableRateEnabled) = reserve.configuration.getFlags(); @@ -268,9 +272,9 @@ library ValidationLogic { require(!isFreezed, Errors.VL_NO_UNFREEZED_RESERVE); if (currentRateMode == ReserveLogic.InterestRateMode.STABLE) { - require(stableBorrowBalance > 0, Errors.VL_NO_STABLE_RATE_LOAN_IN_RESERVE); + require(stableDebt > 0, Errors.VL_NO_STABLE_RATE_LOAN_IN_RESERVE); } else if (currentRateMode == ReserveLogic.InterestRateMode.VARIABLE) { - require(variableBorrowBalance > 0, Errors.VL_NO_VARIABLE_RATE_LOAN_IN_RESERVE); + require(variableDebt > 0, Errors.VL_NO_VARIABLE_RATE_LOAN_IN_RESERVE); /** * user wants to swap to stable, before swapping we need to ensure that * 1. stable borrow rate is enabled on the reserve @@ -283,8 +287,7 @@ library ValidationLogic { require( !userConfig.isUsingAsCollateral(reserve.id) || reserve.configuration.getLtv() == 0 || - stableBorrowBalance.add(variableBorrowBalance) > - IERC20(reserve.aTokenAddress).balanceOf(msg.sender), + stableDebt.add(variableDebt) > IERC20(reserve.aTokenAddress).balanceOf(msg.sender), Errors.VL_CALLATERAL_SAME_AS_BORROWING_CURRENCY ); } else { @@ -331,20 +334,11 @@ library ValidationLogic { /** * @dev validates a flashloan action - * @param mode the flashloan mode (0 = classic flashloan, 1 = open a stable rate loan, 2 = open a variable rate loan) * @param assets the assets being flashborrowed * @param amounts the amounts for each asset being borrowed **/ - function validateFlashloan( - address[] memory assets, - uint256[] memory amounts, - uint256 mode - ) internal pure { - require( - mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), - Errors.LP_INVALID_FLASHLOAN_MODE - ); - require(assets.length == amounts.length, Errors.LP_INCONSISTENT_FLASHLOAN_PARAMS); + function validateFlashloan(address[] memory assets, uint256[] memory amounts) internal pure { + require(assets.length == amounts.length, Errors.VL_INCONSISTENT_FLASHLOAN_PARAMS); } /** diff --git a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol index 7b72c2cb..215746ae 100644 --- a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol +++ b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol @@ -47,9 +47,11 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { address[] memory assets, uint256[] memory amounts, uint256[] memory premiums, + address initiator, bytes memory params ) public override returns (bool) { params; + initiator; if (_failExecution) { emit ExecutedWithFail(assets, amounts, premiums); diff --git a/contracts/tokenization/StableDebtToken.sol b/contracts/tokenization/StableDebtToken.sol index 346d9719..d7c76857 100644 --- a/contracts/tokenization/StableDebtToken.sol +++ b/contracts/tokenization/StableDebtToken.sol @@ -97,7 +97,7 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { address user, uint256 amount, uint256 rate - ) external override onlyLendingPool { + ) external override onlyLendingPool returns (bool) { MintLocalVars memory vars; //cumulates the user debt @@ -148,6 +148,8 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { vars.newStableRate, vars.currentAvgStableRate ); + + return currentBalance == 0; } /** @@ -286,7 +288,7 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { * @param avgRate the average rate at which calculate the total supply * @return The debt balance of the user since the last burn/mint action **/ - function _calcTotalSupply(uint256 avgRate) internal view returns (uint256) { + function _calcTotalSupply(uint256 avgRate) internal virtual view returns (uint256) { uint256 principalSupply = super.totalSupply(); if (principalSupply == 0) { diff --git a/contracts/tokenization/interfaces/IStableDebtToken.sol b/contracts/tokenization/interfaces/IStableDebtToken.sol index 4c0d5940..7a6e5f5b 100644 --- a/contracts/tokenization/interfaces/IStableDebtToken.sol +++ b/contracts/tokenization/interfaces/IStableDebtToken.sol @@ -62,7 +62,7 @@ interface IStableDebtToken { address user, uint256 amount, uint256 rate - ) external; + ) external returns (bool); /** * @dev burns debt of the target user. diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 3855325f..90561b28 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -145,7 +145,6 @@ export const deployAaveLibraries = async ( return { ['__$5201a97c05ba6aa659e2f36a933dd51801$__']: validationLogic.address, ['__$d3b4366daeb9cadc7528af6145b50b2183$__']: reserveLogic.address, - ['__$4c26be947d349222af871a3168b3fe584b$__']: genericLogic.address, }; }; diff --git a/runStableTokenCLI.sh b/runStableTokenCLI.sh new file mode 100644 index 00000000..8903f2f9 --- /dev/null +++ b/runStableTokenCLI.sh @@ -0,0 +1 @@ +certoraRun specs/harness/StableDebtTokenHarness.sol:StableDebtTokenHarness --solc solc6.8 --verify StableDebtTokenHarness:specs/StableDebtToken.spec --settings -assumeUnwindCond --cache StableDebtToken --staging master \ No newline at end of file diff --git a/runUserConfigCLI.sh b/runUserConfigCLI.sh new file mode 100644 index 00000000..f3ddbacc --- /dev/null +++ b/runUserConfigCLI.sh @@ -0,0 +1 @@ +certoraRun specs/harness/UserConfigurationHarness.sol --solc solc6.8 --verify UserConfigurationHarness:specs/UserConfiguration.spec --settings -useBitVectorTheory --staging master diff --git a/runVariableTokenCLI.sh b/runVariableTokenCLI.sh new file mode 100644 index 00000000..483e152e --- /dev/null +++ b/runVariableTokenCLI.sh @@ -0,0 +1 @@ +certoraRun contracts/tokenization/VariableDebtToken.sol:VariableDebtToken specs/harness/LendingPoolHarnessForVariableDebtToken.sol --solc solc6.8 --link VariableDebtToken:POOL=LendingPoolHarnessForVariableDebtToken --verify VariableDebtToken:specs/VariableDebtToken.spec --settings -assumeUnwindCond,-useNonLinearArithmetic --cache VariableDebtToken --staging \ No newline at end of file diff --git a/specs/StableDebtToken.spec b/specs/StableDebtToken.spec new file mode 100644 index 00000000..0d6e0c5d --- /dev/null +++ b/specs/StableDebtToken.spec @@ -0,0 +1,155 @@ +methods { + getUserLastUpdated(address) returns uint40 envfree +} + +rule integrityTimeStamp(address user, method f) { + env e; + require sinvoke getIncentivesController(e) == 0; + require getUserLastUpdated(user) <= e.block.timestamp; + calldataarg arg; + sinvoke f(e,arg); + assert getUserLastUpdated(user) <= e.block.timestamp; +} + +/** +TotalSupply is the sum of all users’ balances + +totalSupply(t) = Σaddress u. balanceOf(u,t) + +Check that each possible opertaion changes the balance of at most one user +*/ +rule balanceOfChange(address a, address b, method f ) +{ + env e; + require a!=b; + require sinvoke getIncentivesController(e) == 0; + uint256 balanceABefore = sinvoke balanceOf(e,a); + uint256 balanceBBefore = sinvoke balanceOf(e,b); + + calldataarg arg; + sinvoke f(e, arg); + + uint256 balanceAAfter = sinvoke balanceOf(e,a); + uint256 balanceBAfter = sinvoke balanceOf(e,b); + + assert ( balanceABefore == balanceAAfter || balanceBBefore == balanceBAfter ); +} + +/** +Check that the change to total supply is coherent with the changes to balance +*/ +rule integirtyBalanceOfTotalSupply(address a, method f ) +{ + env e; + require sinvoke getIncentivesController(e) == 0; + uint256 balanceABefore = sinvoke balanceOf(e,a); + uint256 totalSupplyBefore = sinvoke totalSupply(e); + + calldataarg arg; + sinvoke f(e, arg); + require (f.selector != burn(address,uint256).selector ); + uint256 balanceAAfter = sinvoke balanceOf(e,a); + uint256 totalSupplyAfter = sinvoke totalSupply(e); + + assert (balanceAAfter != balanceABefore => ( balanceAAfter - balanceABefore == totalSupplyAfter - totalSupplyBefore)); +} + +/* Burn behaves differently and due to accumulation errors might have less total supply than the balance +*/ +rule integirtyBalanceOfTotalSupplyOnBurn(address a, method f) +{ + env e; + require sinvoke getIncentivesController(e) == 0; + uint256 balanceABefore = sinvoke balanceOf(e,a); + uint256 totalSupplyBefore = sinvoke totalSupply(e); + + uint256 x; + sinvoke burn(e, a, x); + uint256 balanceAAfter = sinvoke balanceOf(e,a); + uint256 totalSupplyAfter = sinvoke totalSupply(e); + if (totalSupplyBefore > x) + assert (balanceAAfter != balanceABefore => ( balanceAAfter - balanceABefore == totalSupplyAfter - totalSupplyBefore)); + else + assert (totalSupplyAfter == 0 ); +} + +/** +Mint inceases the balanceOf user a as expected +*/ +rule integrityMint(address a, uint256 x) { + env e; + require sinvoke getIncentivesController(e) == 0; + uint256 index; + uint256 balancebefore = sinvoke balanceOf(e,a); + sinvoke mint(e,a,x,index); + + uint256 balanceAfter = sinvoke balanceOf(e,a); + assert balanceAfter == balancebefore+x; +} + +/** +Mint is additive, can performed either all at once or gradually +mint(u,x); mint(u,y) ~ mint(u,x+y) at the same timestamp + +Note: We assume that the stable rate of the user is 0. +The case where the rate is non-zero takes much more time to prove, +and therefore it is currently excluded from the CI. +*/ +rule additiveMint(address a, uint256 x, uint256 y) { + env e; + require sinvoke getIncentivesController(e) == 0; + require getUserStableRate(e,a) == 0; + uint256 index; + storage initialStorage = lastStorage; + sinvoke mint(e,a,x,index); + sinvoke mint(e,a,y,index); + uint256 balanceScenario1 = sinvoke balanceOf(e,a); + + uint256 t = x + y; + sinvoke mint(e,a, t ,index) at initialStorage; + + uint256 balanceScenario2 = sinvoke balanceOf(e,a); + assert balanceScenario1 == balanceScenario2, "mint is not additive"; +} + +rule integrityBurn(address a, uint256 x) { + env e; + require sinvoke getIncentivesController(e) == 0; + uint256 index; + uint256 balancebefore = sinvoke balanceOf(e,a); + sinvoke burn(e,a,x); + + uint256 balanceAfter = sinvoke balanceOf(e,a); + assert balanceAfter == balancebefore - x; +} + +rule additiveBurn(address a, uint256 x, uint256 y) { + env e; + require sinvoke getIncentivesController(e) == 0; + storage initialStorage = lastStorage; + sinvoke burn(e, a, x); + sinvoke burn(e, a, y); + uint256 balanceScenario1 = balanceOf(e, a); + uint256 t = x + y; + sinvoke burn(e, a, t) at initialStorage; + + uint256 balanceScenario2 = balanceOf(e, a); + assert balanceScenario1 == balanceScenario2, "burn is not additive"; +} + + +/** +mint and burn are inverse operations +Thus, totalSupply is back to initial state +BalanceOf user is back to initial state */ +rule inverseMintBurn(address a, uint256 x) { + env e; + require sinvoke getIncentivesController(e) == 0; + uint256 index; + uint256 balancebefore = sinvoke balanceOf(e,a); + sinvoke mint(e,a,x,index); + sinvoke burn(e,a,x); + uint256 balanceAfter = sinvoke balanceOf(e,a); + assert balancebefore == balanceAfter, "burn is not inverse of mint"; +} + diff --git a/specs/UserConfiguration.spec b/specs/UserConfiguration.spec new file mode 100644 index 00000000..14fce3a6 --- /dev/null +++ b/specs/UserConfiguration.spec @@ -0,0 +1,66 @@ +methods { + setBorrowing(address, uint256, bool) envfree + setUsingAsCollateral(address, uint256, bool) envfree + isUsingAsCollateralOrBorrowing(address, uint256) returns bool envfree + isBorrowing(address, uint256) returns bool envfree + isUsingAsCollateral(address, uint256) returns bool envfree + isBorrowingAny(address ) returns bool envfree + isEmpty(address ) returns bool envfree +} + +invariant empty(address user, uint256 reserveIndex ) + isEmpty(user) => !isBorrowingAny(user) && !isUsingAsCollateralOrBorrowing(user, reserveIndex) + +invariant notEmpty(address user, uint256 reserveIndex ) + ( isBorrowingAny(user) || isUsingAsCollateral(user, reserveIndex)) => !isEmpty(user) + + +invariant borrowing(address user, uint256 reserveIndex ) + isBorrowing(user, reserveIndex) => isBorrowingAny(user) + +invariant collateralOrBorrowing(address user, uint256 reserveIndex ) + ( isUsingAsCollateral(user, reserveIndex) || isBorrowing(user, reserveIndex) ) <=> isUsingAsCollateralOrBorrowing(user, reserveIndex) + + + +rule setBorrowing(address user, uint256 reserveIndex, bool borrowing) +{ + require reserveIndex < 128; + + setBorrowing(user, reserveIndex, borrowing); + assert isBorrowing(user, reserveIndex) == borrowing, "unexpected result"; +} + +rule setBorrowingNoChangeToOther(address user, uint256 reserveIndex, uint256 reserveIndexOther, bool borrowing) +{ + require reserveIndexOther != reserveIndex; + require reserveIndexOther < 128 && reserveIndex < 128; + bool otherReserveBorrowing = isBorrowing(user, reserveIndexOther); + bool otherReserveCollateral = isUsingAsCollateral(user,reserveIndexOther); + + setBorrowing(user, reserveIndex, borrowing); + assert otherReserveBorrowing == isBorrowing(user, reserveIndexOther) && + otherReserveCollateral == isUsingAsCollateral(user,reserveIndexOther), "changed to other reserve"; +} + + +rule setUsingAsCollateral(address user, uint256 reserveIndex, bool usingAsCollateral) +{ + require reserveIndex < 128; + + setUsingAsCollateral(user, reserveIndex, usingAsCollateral); + assert isUsingAsCollateral(user, reserveIndex) == usingAsCollateral, "unexpected result"; +} + + +rule setUsingAsCollateralNoChangeToOther(address user, uint256 reserveIndex, uint256 reserveIndexOther, bool usingAsCollateral) +{ + require reserveIndexOther != reserveIndex; + require reserveIndexOther < 128 && reserveIndex < 128; + bool otherReserveBorrowing = isBorrowing(user, reserveIndexOther); + bool otherReserveCollateral = isUsingAsCollateral(user,reserveIndexOther); + + setUsingAsCollateral(user, reserveIndex, usingAsCollateral); + assert otherReserveBorrowing == isBorrowing(user, reserveIndexOther) && + otherReserveCollateral == isUsingAsCollateral(user,reserveIndexOther), "changed to other reserve"; +} diff --git a/specs/VariableDebtToken.spec b/specs/VariableDebtToken.spec new file mode 100644 index 00000000..73e7e39c --- /dev/null +++ b/specs/VariableDebtToken.spec @@ -0,0 +1,173 @@ +using LendingPoolHarnessForVariableDebtToken as POOL +/** +TotalSupply is the sum of all users’ balances + +totalSupply(t) = Σaddress u. balanceOf(u,t) + +Check that each possible opertaion changes the balance of at most one user +*/ +rule balanceOfChange(address a, address b, method f) +{ + env e; + require a!=b ; + uint256 balanceABefore = sinvoke balanceOf(e, a); + uint256 balanceBBefore = sinvoke balanceOf(e, b); + + calldataarg arg; + sinvoke f(e, arg); + + uint256 balanceAAfter = sinvoke balanceOf(e, a); + uint256 balanceBAfter = sinvoke balanceOf(e, b); + + assert ( balanceABefore == balanceAAfter || balanceBBefore == balanceBAfter ); +} + +/* +Check that the changed to total supply is coherent with the changes to balance +*/ + +rule integirtyBalanceOfTotalSupply(address a, method f ) +{ + env e; + + uint256 balanceABefore = balanceOf(e, a); + uint256 totalSupplyBefore = totalSupply(e); + + calldataarg arg; + sinvoke f(e, arg); + require (f.selector != burn(address, uint256, uint256).selector && + f.selector != mint(address, uint256, uint256).selector ) ; + uint256 balanceAAfter = balanceOf(e, a); + uint256 totalSupplyAfter = totalSupply(e); + + assert (balanceAAfter != balanceABefore => ( balanceAAfter - balanceABefore == totalSupplyAfter - totalSupplyBefore)); +} + +/* Burn behaves deferently and due to accumulation errors might hace less total supply then the balance +*/ + +rule integirtyBalanceOfTotalSupplyOnBurn(address a, method f ) +{ + env e; + + uint256 balanceABefore = balanceOf(e, a); + uint256 totalSupplyBefore = totalSupply(e); + + uint256 x; + address asset; + uint256 index = POOL.getReserveNormalizedVariableDebt(e, asset); + sinvoke burn(e, a, x, index); + uint256 balanceAAfter = balanceOf(e, a); + uint256 totalSupplyAfter = totalSupply(e); + assert (balanceAAfter != balanceABefore => ( balanceAAfter - balanceABefore == totalSupplyAfter - totalSupplyBefore)); +} + +rule integirtyBalanceOfTotalSupplyOnMint(address a, method f ) +{ + env e; + + uint256 balanceABefore = balanceOf(e, a); + uint256 totalSupplyBefore = totalSupply(e); + + uint256 x; + address asset; + uint256 index = POOL.getReserveNormalizedVariableDebt(e, asset); + sinvoke mint(e, a, x, index); + uint256 balanceAAfter = balanceOf(e, a); + uint256 totalSupplyAfter = totalSupply(e); + assert (balanceAAfter != balanceABefore => ( balanceAAfter - balanceABefore == totalSupplyAfter - totalSupplyBefore)); +} + +/** +Minting an amount of x tokens for user u increases their balance by x, up to rounding errors. +{ b= balanceOf(u,t) } +mint(u,x,index) +{ balanceOf(u,t) = b + x } + +*/ +rule integrityMint(address a, uint256 x) { + env e; + address asset; + uint256 index = POOL.getReserveNormalizedVariableDebt(e,asset); + uint256 balancebefore = balanceOf(e, a); + sinvoke mint(e, a, x, index); + + uint256 balanceAfter = balanceOf(e, a); + assert balanceAfter == balancebefore+x; +} + +/** +Mint is additive, can performed either all at once or gradually +mint(u,x); mint(u,y) ~ mint(u,x+y) at the same timestamp +*/ +rule additiveMint(address a, uint256 x, uint256 y) { + env e; + address asset; + uint256 index = POOL.getReserveNormalizedVariableDebt(e, asset); + storage initialStorage = lastStorage; + sinvoke mint(e, a, x, index); + sinvoke mint(e, a, y, index); + uint256 balanceScenario1 = balanceOf(e, a); + uint t = x + y; + sinvoke mint(e, a, t ,index) at initialStorage; + + uint256 balanceScenario2 = balanceOf(e, a); + assert balanceScenario1 == balanceScenario2, "mint is not additive"; +} + +/** +Transfer of x amount of tokens from user u where receiver is user u’ +{bu = balanceOf(u) } + burn(u, u’, x) +{balanceOf(u) = bu - x } +*/ +rule integrityBurn(address a, uint256 x) { + env e; + address asset; + uint256 index = POOL.getReserveNormalizedVariableDebt(e, asset); + uint256 balancebefore = balanceOf(e, a); + sinvoke burn(e, a, x, index); + + uint256 balanceAfter = balanceOf(e, a); + assert balanceAfter == balancebefore - x; +} +/** +Minting is additive, i.e., it can be performed either all at once or in steps. + +burn(u, u’, x); burn(u, u’, y) ~ burn(u, u’, x+y) +*/ +rule additiveBurn(address a, uint256 x, uint256 y) { + env e; + address asset; + uint256 index = POOL.getReserveNormalizedVariableDebt(e, asset); + storage initialStorage = lastStorage; + sinvoke burn(e, a, x, index); + sinvoke burn(e, a, y, index); + uint256 balanceScenario1 = balanceOf(e, a); + uint t = x + y; + sinvoke burn(e, a, t ,index) at initialStorage; + + uint256 balanceScenario2 = balanceOf(e, a); + assert balanceScenario1 == balanceScenario2, "burn is not additive"; +} + +/** +Minting and burning are inverse operations. + +{bu = balanceOf(u) } +mint(u,x); burn(u, u, x) + {balanceOf(u) = bu } +*/ +rule inverseMintBurn(address a, uint256 x) { + env e; + address asset; + uint256 index = POOL.getReserveNormalizedVariableDebt(e, asset); + uint256 balancebefore = balanceOf(e, a); + sinvoke mint(e, a, x, index); + sinvoke burn(e, a, x, index); + uint256 balanceAfter = balanceOf(e, a); + assert balancebefore == balanceAfter, "burn is not inverse of mint"; +} + + + diff --git a/specs/harness/LendingPoolHarnessForVariableDebtToken.sol b/specs/harness/LendingPoolHarnessForVariableDebtToken.sol new file mode 100644 index 00000000..a6ffb6f2 --- /dev/null +++ b/specs/harness/LendingPoolHarnessForVariableDebtToken.sol @@ -0,0 +1,214 @@ +pragma solidity ^0.6.8; +pragma experimental ABIEncoderV2; + +import { + ReserveConfiguration +} from '../../contracts/libraries/configuration/ReserveConfiguration.sol'; +import {UserConfiguration} from '../../contracts/libraries/configuration/UserConfiguration.sol'; +import {ReserveLogic} from '../../contracts/libraries/logic/ReserveLogic.sol'; +import {ILendingPool} from '../../contracts/interfaces/ILendingPool.sol'; +import {LendingPool} from '../../contracts/lendingpool/LendingPool.sol'; + +/* +Certora: Harness that delegates calls to the original LendingPool. +Used for the verification of the VariableDebtToken contract. +*/ +contract LendingPoolHarnessForVariableDebtToken is ILendingPool { + LendingPool private originalPool; + + function deposit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) external override { + originalPool.deposit(asset, amount, onBehalfOf, referralCode); + } + + function withdraw(address asset, uint256 amount) external override { + originalPool.withdraw(asset, amount); + } + + function getBorrowAllowance( + address fromUser, + address toUser, + address asset, + uint256 interestRateMode + ) external override view returns (uint256) { + return originalPool.getBorrowAllowance(fromUser, toUser, asset, interestRateMode); + } + + function delegateBorrowAllowance( + address asset, + address user, + uint256 interestRateMode, + uint256 amount + ) external override { + originalPool.delegateBorrowAllowance(asset, user, interestRateMode, amount); + } + + function borrow( + address asset, + uint256 amount, + uint256 interestRateMode, + uint16 referralCode, + address onBehalfOf + ) external override { + originalPool.borrow(asset, amount, interestRateMode, referralCode, onBehalfOf); + } + + function repay( + address asset, + uint256 amount, + uint256 rateMode, + address onBehalfOf + ) external override { + originalPool.repay(asset, amount, rateMode, onBehalfOf); + } + + function swapBorrowRateMode(address asset, uint256 rateMode) external override { + originalPool.swapBorrowRateMode(asset, rateMode); + } + + function rebalanceStableBorrowRate(address asset, address user) external override { + originalPool.rebalanceStableBorrowRate(asset, user); + } + + function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override { + originalPool.setUserUseReserveAsCollateral(asset, useAsCollateral); + } + + function liquidationCall( + address collateral, + address asset, + address user, + uint256 purchaseAmount, + bool receiveAToken + ) external override { + originalPool.liquidationCall(collateral, asset, user, purchaseAmount, receiveAToken); + } + + function getReservesList() external override view returns (address[] memory) { + return originalPool.getReservesList(); + } + + function getReserveData(address asset) + external + override + view + returns (ReserveLogic.ReserveData memory) + { + return originalPool.getReserveData(asset); + } + + function getUserConfiguration(address user) + external + override + view + returns (UserConfiguration.Map memory) + { + return originalPool.getUserConfiguration(user); + } + + function getUserAccountData(address user) + external + override + view + returns ( + uint256 totalCollateralETH, + uint256 totalBorrowsETH, + uint256 availableBorrowsETH, + uint256 currentLiquidationThreshold, + uint256 ltv, + uint256 healthFactor + ) + { + return originalPool.getUserAccountData(user); + } + + function initReserve( + address asset, + address aTokenAddress, + address stableDebtAddress, + address variableDebtAddress, + address interestRateStrategyAddress + ) external override { + originalPool.initReserve( + asset, + aTokenAddress, + stableDebtAddress, + variableDebtAddress, + interestRateStrategyAddress + ); + } + + function setReserveInterestRateStrategyAddress(address asset, address rateStrategyAddress) + external + override + { + originalPool.setReserveInterestRateStrategyAddress(asset, rateStrategyAddress); + } + + function setConfiguration(address asset, uint256 configuration) external override { + originalPool.setConfiguration(asset, configuration); + } + + function getConfiguration(address asset) + external + override + view + returns (ReserveConfiguration.Map memory) + { + return originalPool.getConfiguration(asset); + } + + mapping(uint256 => uint256) private reserveNormalizedIncome; + + function getReserveNormalizedIncome(address asset) external override view returns (uint256) { + require(reserveNormalizedIncome[block.timestamp] == 1e27); + return reserveNormalizedIncome[block.timestamp]; + } + + mapping(uint256 => uint256) private reserveNormalizedVariableDebt; + + function getReserveNormalizedVariableDebt(address asset) + external + override + view + returns (uint256) + { + require(reserveNormalizedVariableDebt[block.timestamp] == 1e27); + return reserveNormalizedVariableDebt[block.timestamp]; + } + + function setPause(bool val) external override { + originalPool.setPause(val); + } + + function paused() external override view returns (bool) { + return originalPool.paused(); + } + + function flashLoan( + address receiver, + address[] calldata assets, + uint256[] calldata amounts, + uint256 mode, + address onBehalfOf, + bytes calldata params, + uint16 referralCode + ) external override { + originalPool.flashLoan(receiver, assets, amounts, mode, onBehalfOf, params, referralCode); + } + + function finalizeTransfer( + address asset, + address from, + address to, + uint256 amount, + uint256 balanceFromAfter, + uint256 balanceToBefore + ) external override { + originalPool.finalizeTransfer(asset, from, to, amount, balanceFromAfter, balanceToBefore); + } +} diff --git a/specs/harness/StableDebtTokenHarness.sol b/specs/harness/StableDebtTokenHarness.sol new file mode 100644 index 00000000..3daf2261 --- /dev/null +++ b/specs/harness/StableDebtTokenHarness.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.6.8; + +import {StableDebtToken} from '../../contracts/tokenization/StableDebtToken.sol'; +import {IncentivizedERC20} from '../../contracts/tokenization/IncentivizedERC20.sol'; + +contract StableDebtTokenHarness is StableDebtToken { + constructor( + address pool, + address underlyingAsset, + string memory name, + string memory symbol, + address incentivesController + ) public StableDebtToken(pool, underlyingAsset, name, symbol, incentivesController) {} + + function balanceOf(address account) public override view returns (uint256) { + return IncentivizedERC20.balanceOf(account); + } + + function _calcTotalSupply(uint256 avgRate) internal override view returns (uint256) { + return IncentivizedERC20.totalSupply(); + } + + function getIncentivesController() public view returns (address) { + return address(_incentivesController); + } +} diff --git a/specs/harness/UserConfigurationHarness.sol b/specs/harness/UserConfigurationHarness.sol new file mode 100644 index 00000000..0ab1c495 --- /dev/null +++ b/specs/harness/UserConfigurationHarness.sol @@ -0,0 +1,56 @@ +pragma solidity ^0.6.8; +pragma experimental ABIEncoderV2; + +import {UserConfiguration} from '../../contracts/libraries/configuration/UserConfiguration.sol'; + +/* +A wrapper contract for calling functions from the library UserConfiguration. +*/ +contract UserConfigurationHarness { + UserConfiguration.Map internal usersConfig; + + function setBorrowing( + address user, + uint256 reserveIndex, + bool borrowing + ) public { + UserConfiguration.setBorrowing(usersConfig, reserveIndex, borrowing); + } + + function setUsingAsCollateral( + address user, + uint256 reserveIndex, + bool _usingAsCollateral + ) public { + UserConfiguration.setUsingAsCollateral(usersConfig, reserveIndex, _usingAsCollateral); + } + + function isUsingAsCollateralOrBorrowing(address user, uint256 reserveIndex) + public + view + returns (bool) + { + return UserConfiguration.isUsingAsCollateralOrBorrowing(usersConfig, reserveIndex); + } + + function isBorrowing(address user, uint256 reserveIndex) public view returns (bool) { + return UserConfiguration.isBorrowing(usersConfig, reserveIndex); + } + + function isUsingAsCollateral(address user, uint256 reserveIndex) public view returns (bool) { + return UserConfiguration.isUsingAsCollateral(usersConfig, reserveIndex); + } + + function isBorrowingAny(address user) public view returns (bool) { + return UserConfiguration.isBorrowingAny(usersConfig); + } + + function isEmpty(address user) public view returns (bool) { + return UserConfiguration.isEmpty(usersConfig); + } + + /* + Mimics the original constructor of the contract. + */ + function init_state() public {} +} diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index bf3e09b5..387a091a 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -50,7 +50,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [ethers.utils.parseEther('0.8')], - 0, + [0], _mockFlashLoanReceiver.address, '0x10', '0' @@ -80,7 +80,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], ['1000720000000000000'], - 0, + [0], _mockFlashLoanReceiver.address, '0x10', '0' @@ -112,7 +112,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [ethers.utils.parseEther('0.8')], - 0, + [0], caller.address, '0x10', '0' @@ -133,7 +133,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [ethers.utils.parseEther('0.8')], - 0, + [0], caller.address, '0x10', '0' @@ -154,12 +154,12 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [ethers.utils.parseEther('0.8')], - 4, + [4], caller.address, '0x10', '0' ) - ).to.be.revertedWith(LP_INVALID_FLASHLOAN_MODE); + ).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 () => { @@ -183,7 +183,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [ethers.utils.parseEther('0.8')], - 2, + [2], caller.address, '0x10', '0' @@ -208,7 +208,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], ['1004415000000000000'], //slightly higher than the available liquidity - 2, + [2], caller.address, '0x10', '0' @@ -226,7 +226,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { deployer.address, [weth.address], ['1000000000000000000'], - 2, + [2], caller.address, '0x10', '0' @@ -258,7 +258,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [usdc.address], [flashloanAmount], - 0, + [0], _mockFlashLoanReceiver.address, '0x10', '0' @@ -301,7 +301,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [usdc.address], [flashloanAmount], - 2, + [2], caller.address, '0x10', '0' @@ -332,7 +332,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [usdc.address], [flashloanAmount], - 2, + [2], caller.address, '0x10', '0' @@ -372,7 +372,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [flashAmount], - 0, + [0], caller.address, '0x10', '0' @@ -395,7 +395,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [flashAmount], - 1, + [1], caller.address, '0x10', '0' @@ -438,7 +438,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [flashAmount], - 1, + [1], onBehalfOf.address, '0x10', '0' @@ -457,7 +457,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { // Deposited for onBehalfOf user already, delegate borrow allowance await pool .connect(onBehalfOf.signer) - .delegateBorrowAllowance(weth.address, caller.address, 1, flashAmount); + .delegateBorrowAllowance([weth.address], caller.address, [1], [flashAmount]); await _mockFlashLoanReceiver.setFailExecutionTransfer(true); @@ -467,7 +467,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [flashAmount], - 1, + [1], onBehalfOf.address, '0x10', '0' diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 422b0455..d998962b 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -231,7 +231,7 @@ export const withdraw = async ( if (expectedResult === 'success') { const txResult = await waitForTx( - await pool.connect(user.signer).withdraw(reserve, amountToWithdraw) + await pool.connect(user.signer).withdraw(reserve, amountToWithdraw, user.address) ); const { @@ -269,15 +269,17 @@ export const withdraw = async ( // ); // }); } else if (expectedResult === 'revert') { - await expect(pool.connect(user.signer).withdraw(reserve, amountToWithdraw), revertMessage).to.be - .reverted; + await expect( + pool.connect(user.signer).withdraw(reserve, amountToWithdraw, user.address), + revertMessage + ).to.be.reverted; } }; export const delegateBorrowAllowance = async ( - reserveSymbol: string, - amount: string, - interestRateMode: string, + reserveSymbols: string[], + amounts: string[], + interestRateModes: string[], user: SignerWithAddress, receiver: tEthereumAddress, expectedResult: string, @@ -286,20 +288,32 @@ export const delegateBorrowAllowance = async ( ) => { const {pool} = testEnv; - const reserve = await getReserveAddressFromSymbol(reserveSymbol); - const amountToDelegate = await convertToCurrencyDecimals(reserve, amount); + const reserves: tEthereumAddress[] = []; + const amountsToDelegate: tEthereumAddress[] = []; + for (const reserveSymbol of reserveSymbols) { + const newLength = reserves.push(await getReserveAddressFromSymbol(reserveSymbol)); + amountsToDelegate.push( + await ( + await convertToCurrencyDecimals(reserves[newLength - 1], amounts[newLength - 1]) + ).toString() + ); + } const delegateAllowancePromise = pool .connect(user.signer) - .delegateBorrowAllowance(reserve, receiver, interestRateMode, amountToDelegate.toString()); + .delegateBorrowAllowance(reserves, receiver, interestRateModes, amountsToDelegate); if (expectedResult === 'revert') { await expect(delegateAllowancePromise, revertMessage).to.be.reverted; return; } else { await delegateAllowancePromise; - expect( - (await pool.getBorrowAllowance(user.address, receiver, reserve, interestRateMode)).toString() - ).to.be.equal(amountToDelegate.toString(), 'borrowAllowance are set incorrectly'); + for (const [i, reserve] of reserves.entries()) { + expect( + ( + await pool.getBorrowAllowance(user.address, receiver, reserve, interestRateModes[i]) + ).toString() + ).to.be.equal(amountsToDelegate[i], 'borrowAllowance are set incorrectly'); + } } }; diff --git a/test/helpers/scenario-engine.ts b/test/helpers/scenario-engine.ts index bb4f82f8..fe2e302a 100644 --- a/test/helpers/scenario-engine.ts +++ b/test/helpers/scenario-engine.ts @@ -121,9 +121,9 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv } await delegateBorrowAllowance( - reserve, - amount, - rateMode, + [reserve], + [amount], + [rateMode], user, toUser, expected, diff --git a/test/pausable-functions.spec.ts b/test/pausable-functions.spec.ts index 6a8c81a6..63334497 100644 --- a/test/pausable-functions.spec.ts +++ b/test/pausable-functions.spec.ts @@ -115,7 +115,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => { // user tries to burn await expect( - pool.connect(users[0].signer).withdraw(dai.address, amountDAItoDeposit) + pool.connect(users[0].signer).withdraw(dai.address, amountDAItoDeposit, users[0].address) ).to.revertedWith(P_IS_PAUSED); // Configurator unpauses the pool @@ -132,7 +132,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => { // Try to execute liquidation await expect( - pool.connect(user.signer).delegateBorrowAllowance(dai.address, toUser.address, '1', '1') + pool.connect(user.signer).delegateBorrowAllowance([dai.address], toUser.address, ['1'], ['1']) ).revertedWith(P_IS_PAUSED); // Unpause the pool @@ -190,7 +190,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, [weth.address], [flashAmount], - 1, + [1], caller.address, '0x10', '0'