From 16fc0d49711c39ab6d4a061f66ec2207c51bb515 Mon Sep 17 00:00:00 2001 From: emilio Date: Thu, 3 Sep 2020 15:17:46 +0200 Subject: [PATCH] Updated flashloans --- contracts/lendingpool/LendingPool.sol | 252 ++++++++---------- contracts/libraries/logic/ValidationLogic.sol | 10 + test/flashloan.spec.ts | 42 +-- 3 files changed, 131 insertions(+), 173 deletions(-) diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 265b9ce6..aa483faf 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -153,21 +153,13 @@ contract LendingPool is VersionedInitializable, ILendingPool { emit Withdraw(asset, msg.sender, amount); } - struct BorrowLocalVars { - address asset; - address user; - uint256 amount; - uint256 interestRateMode; - bool releaseUnderlying; - uint16 referralCode; - } - /** * @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower * already deposited enough collateral. * @param asset the address of the reserve * @param amount the amount to be borrowed * @param interestRateMode the interest rate mode at which the user wants to borrow. Can be 0 (STABLE) or 1 (VARIABLE) + * @param referralCode a referral code for integrators **/ function borrow( address asset, @@ -176,71 +168,15 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint16 referralCode ) external override { _executeBorrow( - BorrowLocalVars(asset, msg.sender, amount, interestRateMode, true, referralCode) - ); - } - - /** - * @dev Internal function to execute a borrowing action, allowing to transfer or not the underlying - * @param vars Input struct for the borrowing action, in order to avoid STD errors - **/ - function _executeBorrow(BorrowLocalVars memory vars) internal { - ReserveLogic.ReserveData storage reserve = _reserves[vars.asset]; - UserConfiguration.Map storage userConfig = _usersConfig[vars.user]; - - address oracle = _addressesProvider.getPriceOracle(); - uint256 amountInETH = IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div( - 10**reserve.configuration.getDecimals() - ); - - ValidationLogic.validateBorrow( - reserve, - vars.asset, - vars.amount, - amountInETH, - vars.interestRateMode, - MAX_STABLE_RATE_BORROW_SIZE_PERCENT, - _reserves, - _usersConfig[vars.user], - _reservesList, - oracle - ); - - //caching the current stable borrow rate - uint256 userStableRate = reserve.currentStableBorrowRate; - - reserve.updateCumulativeIndexesAndTimestamp(); - - if ( - ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE - ) { - IStableDebtToken(reserve.stableDebtTokenAddress).mint(vars.user, vars.amount, userStableRate); - } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.user, vars.amount); - } - - address aToken = reserve.aTokenAddress; - reserve.updateInterestRates(vars.asset, aToken, 0, vars.amount); - - uint256 reserveIndex = reserve.index; - if (!userConfig.isBorrowing(reserveIndex)) { - userConfig.setBorrowing(reserveIndex, true); - } - - //if we reached this point and we need to, we can transfer - if (vars.releaseUnderlying) { - IAToken(aToken).transferUnderlyingTo(vars.user, vars.amount); - } - - emit Borrow( - vars.asset, - vars.user, - vars.amount, - vars.interestRateMode, - ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE - ? userStableRate - : reserve.currentVariableBorrowRate, - vars.referralCode + ExecuteBorrowParams( + asset, + msg.sender, + amount, + interestRateMode, + _reserves[asset].aTokenAddress, + referralCode, + true + ) ); } @@ -396,10 +332,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param asset the address of the reserve * @param useAsCollateral true if the user wants to user the deposit as collateral, false otherwise. **/ - function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) - external - override - { + function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateSetUseReserveAsCollateral( @@ -473,13 +406,13 @@ contract LendingPool is VersionedInitializable, ILendingPool { } /** - * @dev allows smartcontracts to access the liquidity of the pool within one transaction, + * @dev allows smart contracts to access the liquidity of the pool within one transaction, * as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts * that must be kept into consideration. For further details please visit https://developers.aave.com * @param receiverAddress The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface. * @param asset The address of the principal reserve * @param amount The amount requested for this flashloan - * @param debtType 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 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 params Variadic packed params to pass to the receiver as extra information * @param referralCode Referral code of the flash loan **/ @@ -487,7 +420,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { address receiverAddress, address asset, uint256 amount, - uint256 debtType, + uint256 mode, bytes calldata params, uint16 referralCode ) external override { @@ -498,13 +431,12 @@ contract LendingPool is VersionedInitializable, ILendingPool { vars.premium = amount.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000); - require(vars.premium > 0, 'The requested amount is too small for a FlashLoan.'); + ReserveLogic.InterestRateMode debtMode = ReserveLogic.InterestRateMode(mode); + + ValidationLogic.validateFlashloan(debtMode, vars.premium); vars.receiver = IFlashLoanReceiver(receiverAddress); - // Update of the indexes until the current moment - reserve.updateCumulativeIndexesAndTimestamp(); - //transfer funds to the receiver IAToken(vars.aTokenAddress).transferUnderlyingTo(receiverAddress, amount); @@ -513,60 +445,30 @@ contract LendingPool is VersionedInitializable, ILendingPool { vars.amountPlusPremium = amount.add(vars.premium); - if (debtType == 0) { // To not fetch balance/allowance if no debt needs to be opened + if (debtMode == ReserveLogic.InterestRateMode.NONE) { + IERC20(asset).transferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium); + + reserve.updateCumulativeIndexesAndTimestamp(); reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium); reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0); + + emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode); + } else { - vars.receiverBalance = IERC20(asset).balanceOf(receiverAddress); - vars.receiverAllowance = IERC20(asset).allowance(receiverAddress, address(this)); - if (vars.receiverBalance >= vars.amountPlusPremium && vars.receiverAllowance >= vars.amountPlusPremium) { - IERC20(asset).transferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium); - reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium); - reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0); - } else { - if (debtType == 1 || debtType == 2) { - // If the transfer didn't succeed, the receiver either didn't return the funds, or didn't approve the transfer. - // We will try to pull all the available funds from the receiver and create a debt position with the rest owed - // if it has collateral enough - vars.availableBalance = (vars.receiverBalance > vars.receiverAllowance) - ? vars.receiverAllowance - : vars.receiverBalance; - - if (vars.availableBalance > 0) { - // If not enough premium, include as premium all the funds available to pull - if (vars.availableBalance < vars.premium) { - vars.premium = vars.availableBalance; - } - IERC20(asset).transferFrom(receiverAddress, vars.aTokenAddress, vars.availableBalance); - reserve.cumulateToLiquidityIndex( - IERC20(vars.aTokenAddress).totalSupply(), - vars.premium - ); - reserve.updateInterestRates( - asset, - vars.aTokenAddress, - vars.premium, - 0 - ); - } - - _executeBorrow( - BorrowLocalVars( - asset, - msg.sender, - vars.amountPlusPremium.sub(vars.availableBalance), - debtType, - false, - referralCode - ) - ); - } else { - revert("INSUFFICIENT_FUNDS_TO_PULL"); - } - } + // If the transfer didn't succeed, the receiver either didn't return the funds, or didn't approve the transfer. + _executeBorrow( + ExecuteBorrowParams( + asset, + msg.sender, + vars.amountPlusPremium.sub(vars.availableBalance), + mode, + vars.aTokenAddress, + referralCode, + false + ) + ); } - emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode); } /** @@ -783,9 +685,89 @@ contract LendingPool is VersionedInitializable, ILendingPool { return _reserves[asset].configuration; } + // internal functions + + struct ExecuteBorrowParams { + address asset; + address user; + uint256 amount; + uint256 interestRateMode; + address aTokenAddress; + uint16 referralCode; + bool releaseUnderlying; + } + /** - * @notice internal functions + * @dev Internal function to execute a borrowing action, allowing to transfer or not the underlying + * @param vars Input struct for the borrowing action, in order to avoid STD errors **/ + function _executeBorrow(ExecuteBorrowParams memory vars) internal { + ReserveLogic.ReserveData storage reserve = _reserves[vars.asset]; + UserConfiguration.Map storage userConfig = _usersConfig[msg.sender]; + + address oracle = _addressesProvider.getPriceOracle(); + + uint256 amountInETH = IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div( + 10**reserve.configuration.getDecimals() + ); + + ValidationLogic.validateBorrow( + reserve, + vars.asset, + vars.amount, + amountInETH, + vars.interestRateMode, + MAX_STABLE_RATE_BORROW_SIZE_PERCENT, + _reserves, + userConfig, + _reservesList, + oracle + ); + + + uint256 reserveIndex = reserve.index; + if (!userConfig.isBorrowing(reserveIndex)) { + userConfig.setBorrowing(reserveIndex, true); + } + + + reserve.updateCumulativeIndexesAndTimestamp(); + + //caching the current stable borrow rate + uint256 currentStableRate = 0; + + if ( + ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE + ) { + currentStableRate = reserve.currentStableBorrowRate; + + IStableDebtToken(reserve.stableDebtTokenAddress).mint( + vars.user, + vars.amount, + currentStableRate + ); + } else { + IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.user, vars.amount); + } + + reserve.updateInterestRates(vars.asset, vars.aTokenAddress, 0, vars.releaseUnderlying ? vars.amount : 0); + + if(vars.releaseUnderlying){ + IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount); + } + + + emit Borrow( + vars.asset, + msg.sender, + vars.amount, + vars.interestRateMode, + ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE + ? currentStableRate + : reserve.currentVariableBorrowRate, + vars.referralCode + ); + } /** * @dev adds a reserve to the array of the _reserves address diff --git a/contracts/libraries/logic/ValidationLogic.sol b/contracts/libraries/logic/ValidationLogic.sol index dd3ef25a..b4edbf2d 100644 --- a/contracts/libraries/logic/ValidationLogic.sol +++ b/contracts/libraries/logic/ValidationLogic.sol @@ -324,4 +324,14 @@ library ValidationLogic { 'User deposit is already being used as collateral' ); } + + /** + * @dev validates a flashloan action + * @param mode the flashloan mode (NONE = classic flashloan, STABLE = open a stable rate loan, VARIABLE = open a variable rate loan) + * @param premium the premium paid on the flashloan + **/ + function validateFlashloan(ReserveLogic.InterestRateMode mode, uint256 premium) internal pure { + require(premium > 0, 'The requested amount is too small for a FlashLoan.'); + require(mode <= ReserveLogic.InterestRateMode.VARIABLE, 'Invalid flashloan mode selected'); + } } diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 93cb2fcd..ced409e2 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -36,14 +36,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await pool.deposit(weth.address, amountToDeposit, '0'); }); - it('Takes ETH flashloan, returns the funds correctly', async () => { + it('Takes WETH flashloan, returns the funds correctly', async () => { const {pool, deployer, weth} = testEnv; await pool.flashLoan( _mockFlashLoanReceiver.address, weth.address, ethers.utils.parseEther('0.8'), - 2, + 0, '0x10', '0' ); @@ -72,7 +72,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, weth.address, '1000720000000000000', - 2, + 0, '0x10', '0' ); @@ -209,7 +209,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, usdc.address, flashloanAmount, - 2, + 0, '0x10', '0' ); @@ -283,40 +283,6 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { expect(callerDebt.toString()).to.be.equal('500450000', 'Invalid user debt'); }); - it('Caller deposits 5 ETH as collateral, Takes a USDC flashloan, approves only partially funds. A loan for caller is created', async () => { - const {usdc, pool, weth, users} = testEnv; - - const caller = users[3]; - - 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, '0'); - - const flashloanAmount = await convertToCurrencyDecimals(usdc.address, '500'); - - await _mockFlashLoanReceiver.setFailExecutionTransfer(false); - - await _mockFlashLoanReceiver.setAmountToApprove(flashloanAmount.div(2)); - - await pool - .connect(caller.signer) - .flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0'); - const {variableDebtTokenAddress} = await pool.getReserveTokensAddresses(usdc.address); - - const usdcDebtToken = await getContract( - eContractid.VariableDebtToken, - variableDebtTokenAddress - ); - - const callerDebt = await usdcDebtToken.balanceOf(caller.address); - - expect(callerDebt.toString()).to.be.equal('250450000', 'Invalid user debt'); - }); - it('Caller deposits 1000 DAI as collateral, Takes WETH flashloan, does not return the funds and selects revert as result', async () => { const {dai, pool, weth, users} = testEnv;