From 8873b9cdacf9cf6f91327af84abb8a25fe751cb7 Mon Sep 17 00:00:00 2001 From: The3D Date: Thu, 22 Oct 2020 18:15:56 +0200 Subject: [PATCH 1/5] initial implementation --- contracts/interfaces/ILendingPool.sol | 8 +- contracts/lendingpool/LendingPool.sol | 91 +++++++++++-------- contracts/libraries/logic/ValidationLogic.sol | 8 +- 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 2b4ead37..5271d6bd 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -270,10 +270,10 @@ interface ILendingPool { * @param referralCode the referral code of the caller **/ function flashLoan( - address receiver, - address reserve, - uint256 amount, - uint256 debtType, + address receiverAddress, + address[] calldata assets, + uint256[] calldata amounts, + uint256 mode, bytes calldata params, uint16 referralCode ) external; diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index c1ff20bb..cdf2be48 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -483,11 +483,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage } struct FlashLoanLocalVars { - uint256 premium; - uint256 amountPlusPremium; IFlashLoanReceiver receiver; - address aTokenAddress; address oracle; + ReserveLogic.InterestRateMode debtMode; } /** @@ -495,68 +493,83 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * 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 assets The address of the principal reserve + * @param amounts The amount requested for this flashloan * @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 **/ function flashLoan( address receiverAddress, - address asset, - uint256 amount, + address[] calldata assets, + uint256[] calldata amounts, uint256 mode, bytes calldata params, uint16 referralCode ) external override { _whenNotPaused(); - ReserveLogic.ReserveData storage reserve = _reserves[asset]; + FlashLoanLocalVars memory vars; - vars.aTokenAddress = reserve.aTokenAddress; + ValidationLogic.validateFlashloan(assets, amounts, mode, vars.premium); - vars.premium = amount.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000); - - ValidationLogic.validateFlashloan(mode, vars.premium); - - ReserveLogic.InterestRateMode debtMode = ReserveLogic.InterestRateMode(mode); + address[] memory aTokenAddresses = new address[](assets.length); + uint256[] memory premiums = new uint256[](assets.length); vars.receiver = IFlashLoanReceiver(receiverAddress); + vars.debtMode = ReserveLogic.InterestRateMode(mode); - //transfer funds to the receiver - IAToken(vars.aTokenAddress).transferUnderlyingTo(receiverAddress, amount); + for (uint256 i = 0; i < assets.length; i++) { + ReserveLogic.ReserveData storage reserve = _reserves[assets[i]]; + + aTokenAddresses[i] = reserve.aTokenAddress; + + premiums[i] = amounts[i].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000); + + //transfer funds to the receiver + IAToken(vars.aTokenAddress).transferUnderlyingTo(receiverAddress, amounts[i]); + } //execute action of the receiver require( - vars.receiver.executeOperation(asset, amount, vars.premium, params), + vars.receiver.executeOperation(assets, amounts, premiums, params), Errors.INVALID_FLASH_LOAN_EXECUTOR_RETURN ); - vars.amountPlusPremium = amount.add(vars.premium); + for (uint256 i = 0; i < assets.length; i++) { + uint256 amountPlusPremium = amounts[i].add(premiums[i]); - if (debtMode == ReserveLogic.InterestRateMode.NONE) { - IERC20(asset).safeTransferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium); + if (vars.debtMode == ReserveLogic.InterestRateMode.NONE) { + _reserves[assets[i]].updateState(); + _reserves[assets[i]].cumulateToLiquidityIndex( + IERC20(aTokenAddresses[i]).totalSupply(), + vars.premium + ); + _reserves[assets[i]].updateInterestRates(assets[i], aTokenAddresses[i], vars.premium, 0); - reserve.updateState(); - reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium); - reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0); + IERC20(assets[i]).safeTransferFrom( + receiverAddress, + vars.aTokenAddresses[i], + vars.amountPlusPremium + ); - emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode); - } else { - //if the user didn't choose to return the funds, the system checks if there - //is enough collateral and eventually open a position - _executeBorrow( - ExecuteBorrowParams( - asset, - msg.sender, - msg.sender, - vars.amountPlusPremium, - mode, - vars.aTokenAddress, - referralCode, - false - ) - ); + emit FlashLoan(receiverAddress, assets[i], amounts[i], vars.premium, referralCode); + } else { + //if the user didn't choose to return the funds, the system checks if there + //is enough collateral and eventually open a position + _executeBorrow( + ExecuteBorrowParams( + assets[i], + msg.sender, + msg.sender, + amountPlusPremium, + vars.mode, + aTokenAddresses[i], + referralCode, + false + ) + ); + } } } diff --git a/contracts/libraries/logic/ValidationLogic.sol b/contracts/libraries/logic/ValidationLogic.sol index 93219ce8..838d43b7 100644 --- a/contracts/libraries/logic/ValidationLogic.sol +++ b/contracts/libraries/logic/ValidationLogic.sol @@ -329,9 +329,15 @@ library ValidationLogic { * @param mode the flashloan mode (0 = classic flashloan, 1 = open a stable rate loan, 2 = open a variable rate loan) * @param premium the premium paid on the flashloan **/ - function validateFlashloan(uint256 mode, uint256 premium) internal pure { + function validateFlashloan( + address[] memory assets, + address[] memory amounts, + uint256 mode, + uint256 premium + ) internal pure { require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL); require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE); + require(assets.length == amounts.length, Errors.INCONSISTENT_FLASHLOAN_PARAMS); } /** From af99c88b70956eccfec0cea8f16cfe7a6e238d64 Mon Sep 17 00:00:00 2001 From: The3D Date: Thu, 22 Oct 2020 19:20:19 +0200 Subject: [PATCH 2/5] Added error constant --- contracts/libraries/helpers/Errors.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/libraries/helpers/Errors.sol b/contracts/libraries/helpers/Errors.sol index ee71efaa..6e13143a 100644 --- a/contracts/libraries/helpers/Errors.sol +++ b/contracts/libraries/helpers/Errors.sol @@ -45,6 +45,7 @@ library Errors { string public constant INVALID_EQUAL_ASSETS_TO_SWAP = '56'; string public constant NO_MORE_RESERVES_ALLOWED = '59'; string public constant INVALID_FLASH_LOAN_EXECUTOR_RETURN = '60'; + string public constant INCONSISTENT_FLASHLOAN_PARAMS = '69'; // require error messages - aToken - DebtTokens string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool' From a2e2450bb351844086f749ee24c005df03bc0e4a Mon Sep 17 00:00:00 2001 From: The3D Date: Thu, 22 Oct 2020 20:37:50 +0200 Subject: [PATCH 3/5] Finalized implementation, fixed tests --- .../interfaces/IFlashLoanReceiver.sol | 6 +- contracts/interfaces/ILendingPool.sol | 19 +++--- contracts/lendingpool/LendingPool.sol | 59 ++++++++++------- contracts/libraries/logic/ValidationLogic.sol | 9 ++- .../mocks/flashloan/MockFlashLoanReceiver.sol | 43 +++++++------ test/flashloan.spec.ts | 64 ++++++++----------- test/pausable-functions.spec.ts | 2 +- 7 files changed, 107 insertions(+), 95 deletions(-) diff --git a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol index 5c92236a..8d4353f3 100644 --- a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol +++ b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol @@ -9,9 +9,9 @@ pragma solidity ^0.6.8; **/ interface IFlashLoanReceiver { function executeOperation( - address reserve, - uint256 amount, - uint256 fee, + address[] calldata reserve, + uint256[] calldata amounts, + uint256[] calldata premiums, bytes calldata params ) external returns (bool); } diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 5271d6bd..4e36c87b 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -99,16 +99,16 @@ interface ILendingPool { /** * @dev emitted when a flashloan is executed * @param target the address of the flashLoanReceiver - * @param reserve the address of the reserve - * @param amount the amount requested - * @param totalPremium the total fee on the amount + * @param assets the address of the assets being flashborrowed + * @param amounts the amount requested + * @param premiums the total fee on the amount * @param referralCode the referral code of the caller **/ event FlashLoan( address indexed target, - address indexed reserve, - uint256 amount, - uint256 totalPremium, + address[] assets, + uint256[] amounts, + uint256[] premiums, uint16 referralCode ); /** @@ -264,13 +264,14 @@ interface ILendingPool { * 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 receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface. - * @param reserve the address of the principal reserve - * @param amount the amount requested for this flashloan + * @param assets the address of the principal reserve + * @param amounts the amount requested for this flashloan + * @param mode the flashloan mode * @param params a bytes array to be sent to the flashloan executor * @param referralCode the referral code of the caller **/ function flashLoan( - address receiverAddress, + address receiver, address[] calldata assets, uint256[] calldata amounts, uint256 mode, diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index cdf2be48..fc9413ab 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -486,6 +486,12 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage IFlashLoanReceiver receiver; address oracle; ReserveLogic.InterestRateMode debtMode; + uint256 i; + address currentAsset; + address currentATokenAddress; + uint256 currentAmount; + uint256 currentPremium; + uint256 currentAmountPlusPremium; } /** @@ -511,7 +517,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage FlashLoanLocalVars memory vars; - ValidationLogic.validateFlashloan(assets, amounts, mode, vars.premium); + ValidationLogic.validateFlashloan(assets, amounts, mode); address[] memory aTokenAddresses = new address[](assets.length); uint256[] memory premiums = new uint256[](assets.length); @@ -519,15 +525,13 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage vars.receiver = IFlashLoanReceiver(receiverAddress); vars.debtMode = ReserveLogic.InterestRateMode(mode); - for (uint256 i = 0; i < assets.length; i++) { - ReserveLogic.ReserveData storage reserve = _reserves[assets[i]]; + for (vars.i = 0; vars.i < assets.length; vars.i++) { + aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress; - aTokenAddresses[i] = reserve.aTokenAddress; - - premiums[i] = amounts[i].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000); + premiums[vars.i] = amounts[vars.i].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000); //transfer funds to the receiver - IAToken(vars.aTokenAddress).transferUnderlyingTo(receiverAddress, amounts[i]); + IAToken(aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]); } //execute action of the receiver @@ -536,40 +540,49 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage Errors.INVALID_FLASH_LOAN_EXECUTOR_RETURN ); - for (uint256 i = 0; i < assets.length; i++) { - uint256 amountPlusPremium = amounts[i].add(premiums[i]); + for (vars.i = 0; vars.i < assets.length; vars.i++) { + vars.currentAsset = assets[vars.i]; + vars.currentAmount = amounts[vars.i]; + vars.currentPremium = premiums[vars.i]; + vars.currentATokenAddress = aTokenAddresses[vars.i]; + + vars.currentAmountPlusPremium = amounts[vars.i].add(premiums[vars.i]); if (vars.debtMode == ReserveLogic.InterestRateMode.NONE) { - _reserves[assets[i]].updateState(); - _reserves[assets[i]].cumulateToLiquidityIndex( - IERC20(aTokenAddresses[i]).totalSupply(), - vars.premium + _reserves[vars.currentAsset].updateState(); + _reserves[vars.currentAsset].cumulateToLiquidityIndex( + IERC20(vars.currentATokenAddress).totalSupply(), + vars.currentPremium + ); + _reserves[vars.currentAsset].updateInterestRates( + assets[vars.i], + vars.currentATokenAddress, + vars.currentPremium, + 0 ); - _reserves[assets[i]].updateInterestRates(assets[i], aTokenAddresses[i], vars.premium, 0); - IERC20(assets[i]).safeTransferFrom( + IERC20(vars.currentAsset).safeTransferFrom( receiverAddress, - vars.aTokenAddresses[i], - vars.amountPlusPremium + vars.currentATokenAddress, + vars.currentAmountPlusPremium ); - - emit FlashLoan(receiverAddress, assets[i], amounts[i], vars.premium, referralCode); } else { //if the user didn't choose to return the funds, the system checks if there //is enough collateral and eventually open a position _executeBorrow( ExecuteBorrowParams( - assets[i], + vars.currentAsset, msg.sender, msg.sender, - amountPlusPremium, - vars.mode, - aTokenAddresses[i], + vars.currentAmount, + mode, + vars.currentATokenAddress, referralCode, false ) ); } + emit FlashLoan(receiverAddress, assets, amounts, premiums, referralCode); } } diff --git a/contracts/libraries/logic/ValidationLogic.sol b/contracts/libraries/logic/ValidationLogic.sol index 838d43b7..947d84ea 100644 --- a/contracts/libraries/logic/ValidationLogic.sol +++ b/contracts/libraries/logic/ValidationLogic.sol @@ -327,15 +327,14 @@ 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 premium the premium paid on the flashloan + * @param assets the assets being flashborrowed + * @param amounts the amounts for each asset being borrowed **/ function validateFlashloan( address[] memory assets, - address[] memory amounts, - uint256 mode, - uint256 premium + uint256[] memory amounts, + uint256 mode ) internal pure { - require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL); require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE); require(assets.length == amounts.length, Errors.INCONSISTENT_FLASHLOAN_PARAMS); } diff --git a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol index 0a767712..7b72c2cb 100644 --- a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol +++ b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol @@ -14,8 +14,8 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { ILendingPoolAddressesProvider internal _provider; - event ExecutedWithFail(address _reserve, uint256 _amount, uint256 _fee); - event ExecutedWithSuccess(address _reserve, uint256 _amount, uint256 _fee); + event ExecutedWithFail(address[] _assets, uint256[] _amounts, uint256[] _premiums); + event ExecutedWithSuccess(address[] _assets, uint256[] _amounts, uint256[] _premiums); bool _failExecution; uint256 _amountToApprove; @@ -44,33 +44,40 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { } function executeOperation( - address reserve, - uint256 amount, - uint256 fee, + address[] memory assets, + uint256[] memory amounts, + uint256[] memory premiums, bytes memory params ) public override returns (bool) { params; - //mint to this contract the specific amount - MintableERC20 token = MintableERC20(reserve); - - //check the contract has the specified balance - require(amount <= IERC20(reserve).balanceOf(address(this)), 'Invalid balance for the contract'); - - uint256 amountToReturn = (_amountToApprove != 0) ? _amountToApprove : amount.add(fee); if (_failExecution) { - emit ExecutedWithFail(reserve, amount, fee); + emit ExecutedWithFail(assets, amounts, premiums); return !_simulateEOA; } - //execution does not fail - mint tokens and return them to the _destination - //note: if the reserve is eth, the mock contract must receive at least _fee ETH before calling executeOperation + for (uint256 i = 0; i < assets.length; i++) { + //mint to this contract the specific amount + MintableERC20 token = MintableERC20(assets[i]); - token.mint(fee); + //check the contract has the specified balance + require( + amounts[i] <= IERC20(assets[i]).balanceOf(address(this)), + 'Invalid balance for the contract' + ); - IERC20(reserve).approve(_addressesProvider.getLendingPool(), amountToReturn); + uint256 amountToReturn = (_amountToApprove != 0) + ? _amountToApprove + : amounts[i].add(premiums[i]); + //execution does not fail - mint tokens and return them to the _destination + //note: if the reserve is eth, the mock contract must receive at least _fee ETH before calling executeOperation - emit ExecutedWithSuccess(reserve, amount, fee); + token.mint(premiums[i]); + + IERC20(assets[i]).approve(_addressesProvider.getLendingPool(), amountToReturn); + } + + emit ExecutedWithSuccess(assets, amounts, premiums); return true; } diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 1ca080f9..c34c37b1 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -48,8 +48,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await pool.flashLoan( _mockFlashLoanReceiver.address, - weth.address, - ethers.utils.parseEther('0.8'), + [weth.address], + [ethers.utils.parseEther('0.8')], 0, '0x10', '0' @@ -77,8 +77,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const reserveDataBefore = await helpersContract.getReserveData(weth.address); const txResult = await pool.flashLoan( _mockFlashLoanReceiver.address, - weth.address, - '1000720000000000000', + [weth.address], + ['1000720000000000000'], 0, '0x10', '0' @@ -108,8 +108,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { .connect(caller.signer) .flashLoan( _mockFlashLoanReceiver.address, - weth.address, - ethers.utils.parseEther('0.8'), + [weth.address], + [ethers.utils.parseEther('0.8')], 0, '0x10', '0' @@ -128,8 +128,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { .connect(caller.signer) .flashLoan( _mockFlashLoanReceiver.address, - weth.address, - ethers.utils.parseEther('0.8'), + [weth.address], + [ethers.utils.parseEther('0.8')], 0, '0x10', '0' @@ -148,8 +148,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { .connect(caller.signer) .flashLoan( _mockFlashLoanReceiver.address, - weth.address, - ethers.utils.parseEther('0.8'), + [weth.address], + [ethers.utils.parseEther('0.8')], 4, '0x10', '0' @@ -176,8 +176,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { .connect(caller.signer) .flashLoan( _mockFlashLoanReceiver.address, - weth.address, - ethers.utils.parseEther('0.8'), + [weth.address], + [ethers.utils.parseEther('0.8')], 2, '0x10', '0' @@ -196,29 +196,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt'); }); - it('tries to take a very small flashloan, which would result in 0 fees (revert expected)', async () => { - const {pool, weth} = testEnv; - - await expect( - pool.flashLoan( - _mockFlashLoanReceiver.address, - weth.address, - '1', //1 wei loan - 2, - '0x10', - '0' - ) - ).to.be.revertedWith(REQUESTED_AMOUNT_TOO_SMALL); - }); - it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => { const {pool, weth} = testEnv; await expect( pool.flashLoan( _mockFlashLoanReceiver.address, - weth.address, - '1004415000000000000', //slightly higher than the available liquidity + [weth.address], + ['1004415000000000000'], //slightly higher than the available liquidity 2, '0x10', '0' @@ -231,7 +216,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const {pool, deployer, weth} = testEnv; await expect( - pool.flashLoan(deployer.address, weth.address, '1000000000000000000', 2, '0x10', '0') + pool.flashLoan(deployer.address, [weth.address], ['1000000000000000000'], 2, '0x10', '0') ).to.be.reverted; }); @@ -257,8 +242,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await pool.flashLoan( _mockFlashLoanReceiver.address, - usdc.address, - flashloanAmount, + [usdc.address], + [flashloanAmount], 0, '0x10', '0' @@ -297,7 +282,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await expect( pool .connect(caller.signer) - .flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0') + .flashLoan( + _mockFlashLoanReceiver.address, + [usdc.address], + [flashloanAmount], + 2, + '0x10', + '0' + ) ).to.be.revertedWith(COLLATERAL_BALANCE_IS_0); }); @@ -320,7 +312,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await pool .connect(caller.signer) - .flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0'); + .flashLoan(_mockFlashLoanReceiver.address, [usdc.address], [flashloanAmount], 2, '0x10', '0'); const {variableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses( usdc.address ); @@ -355,7 +347,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await expect( pool .connect(caller.signer) - .flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 0, '0x10', '0') + .flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 0, '0x10', '0') ).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL); }); @@ -370,7 +362,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await pool .connect(caller.signer) - .flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0'); + .flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 1, '0x10', '0'); const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address); diff --git a/test/pausable-functions.spec.ts b/test/pausable-functions.spec.ts index 8e7cc2a7..cc8fb0d4 100644 --- a/test/pausable-functions.spec.ts +++ b/test/pausable-functions.spec.ts @@ -186,7 +186,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => { await expect( pool .connect(caller.signer) - .flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0') + .flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 1, '0x10', '0') ).revertedWith(IS_PAUSED); // Unpause pool From 6398fe4260a10beff1dd299fb539e11f54ca71ff Mon Sep 17 00:00:00 2001 From: The3D Date: Fri, 23 Oct 2020 18:41:08 +0200 Subject: [PATCH 4/5] Fix comments, tests --- contracts/flashloan/interfaces/IFlashLoanReceiver.sol | 2 +- contracts/lendingpool/LendingPool.sol | 8 ++++---- test/flashloan.spec.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol index 8d4353f3..784d0fa3 100644 --- a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol +++ b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol @@ -9,7 +9,7 @@ pragma solidity ^0.6.8; **/ interface IFlashLoanReceiver { function executeOperation( - address[] calldata reserve, + address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, bytes calldata params diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index fc9413ab..a16e9cd6 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -499,8 +499,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * 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 assets The address of the principal reserve - * @param amounts The amount requested for this flashloan + * @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 params Variadic packed params to pass to the receiver as extra information * @param referralCode Referral code of the flash loan @@ -546,7 +546,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage vars.currentPremium = premiums[vars.i]; vars.currentATokenAddress = aTokenAddresses[vars.i]; - vars.currentAmountPlusPremium = amounts[vars.i].add(premiums[vars.i]); + vars.currentAmountPlusPremium = vars.currentAmount.add(vars.currentPremium); if (vars.debtMode == ReserveLogic.InterestRateMode.NONE) { _reserves[vars.currentAsset].updateState(); @@ -555,7 +555,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage vars.currentPremium ); _reserves[vars.currentAsset].updateInterestRates( - assets[vars.i], + vars.currentAsset, vars.currentATokenAddress, vars.currentPremium, 0 diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index c34c37b1..4edd4848 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -193,7 +193,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const callerDebt = await wethDebtToken.balanceOf(caller.address); - expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt'); + 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 () => { @@ -324,7 +324,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const callerDebt = await usdcDebtToken.balanceOf(caller.address); - expect(callerDebt.toString()).to.be.equal('500450000', 'Invalid user debt'); + 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 () => { @@ -373,6 +373,6 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const callerDebt = await wethDebtToken.balanceOf(caller.address); - expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt'); + expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt'); }); }); From c44ed2c0c7c54535456b7c88176efeeda97dce3e Mon Sep 17 00:00:00 2001 From: The3D Date: Mon, 26 Oct 2020 10:35:44 +0100 Subject: [PATCH 5/5] Updated flashloan event --- contracts/interfaces/ILendingPool.sol | 1 + contracts/lendingpool/LendingPool.sol | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 4e36c87b..891781dc 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -106,6 +106,7 @@ interface ILendingPool { **/ event FlashLoan( address indexed target, + uint256 mode, address[] assets, uint256[] amounts, uint256[] premiums, diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index a16e9cd6..22dd7454 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -582,7 +582,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ) ); } - emit FlashLoan(receiverAddress, assets, amounts, premiums, referralCode); + emit FlashLoan(receiverAddress, mode, assets, amounts, premiums, referralCode); } }