From a517d5d11df6e38ce62ce594f428b565d59e3d5a Mon Sep 17 00:00:00 2001 From: David Truong Date: Tue, 27 Oct 2020 13:10:10 +0100 Subject: [PATCH] - implement test for onBehalfOf flashloan borrow debt --- contracts/interfaces/ILendingPool.sol | 3 + contracts/lendingpool/LendingPool.sol | 14 +++- helpers/types.ts | 1 + test/flashloan.spec.ts | 107 ++++++++++++++++++++++++-- 4 files changed, 117 insertions(+), 8 deletions(-) diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 4078f511..84a29a57 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -99,6 +99,8 @@ 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 @@ -107,6 +109,7 @@ interface ILendingPool { event FlashLoan( address indexed target, uint256 mode, + address indexed onBehalfOf, address[] assets, uint256[] amounts, uint256[] premiums, diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 6c12b364..04cf84d8 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -503,7 +503,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * @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 onBehalfOf If mode is not 0, then the address to take the debt onBehalfOf. The address must already have approved `msg.sender` to incur the debt on their behalf. + * @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 **/ @@ -570,6 +570,16 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage vars.currentAmountPlusPremium ); } else { + if (msg.sender != onBehalfOf) { + address debtToken = _reserves[vars.currentAsset].getDebtTokenAddress(mode); + + _borrowAllowance[debtToken][onBehalfOf][msg + .sender] = _borrowAllowance[debtToken][onBehalfOf][msg.sender].sub( + vars.currentAmount, + Errors.BORROW_ALLOWANCE_ARE_NOT_ENOUGH + ); + } + //if the user didn't choose to return the funds, the system checks if there //is enough collateral and eventually open a position _executeBorrow( @@ -585,7 +595,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ) ); } - emit FlashLoan(receiverAddress, mode, assets, amounts, premiums, referralCode); + emit FlashLoan(receiverAddress, mode, onBehalfOf, assets, amounts, premiums, referralCode); } } diff --git a/helpers/types.ts b/helpers/types.ts index 305ec335..bdd44e11 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -89,6 +89,7 @@ export enum ProtocolErrors { REQUESTED_AMOUNT_TOO_SMALL = '25', // 'The requested amount is too small for a FlashLoan.' INCONSISTENT_PROTOCOL_ACTUAL_BALANCE = '26', // 'The actual balance of the protocol is inconsistent' CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27', // 'The actual balance of the protocol is inconsistent' + BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54', // User borrows on behalf, but allowance are too small INVALID_FLASH_LOAN_EXECUTOR_RETURN = '60', // The flash loan received returned 0 (EOA) // require error messages - aToken diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 15f75d61..2538f4fb 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -25,6 +25,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { SAFEERC20_LOWLEVEL_CALL, IS_PAUSED, INVALID_FLASH_LOAN_EXECUTOR_RETURN, + BORROW_ALLOWANCE_ARE_NOT_ENOUGH, } = ProtocolErrors; before(async () => { @@ -113,7 +114,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { [weth.address], [ethers.utils.parseEther('0.8')], 0, - _mockFlashLoanReceiver.address, + caller.address, '0x10', '0' ) @@ -134,7 +135,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { [weth.address], [ethers.utils.parseEther('0.8')], 0, - _mockFlashLoanReceiver.address, + caller.address, '0x10', '0' ) @@ -155,7 +156,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { [weth.address], [ethers.utils.parseEther('0.8')], 4, - _mockFlashLoanReceiver.address, + caller.address, '0x10', '0' ) @@ -297,7 +298,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { [usdc.address], [flashloanAmount], 2, - _mockFlashLoanReceiver.address, + caller.address, '0x10', '0' ) @@ -358,7 +359,15 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await expect( pool .connect(caller.signer) - .flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 0, _mockFlashLoanReceiver.address, '0x10', '0') + .flashLoan( + _mockFlashLoanReceiver.address, + [weth.address], + [flashAmount], + 0, + caller.address, + '0x10', + '0' + ) ).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL); }); @@ -373,7 +382,15 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await pool .connect(caller.signer) - .flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 1, caller.address, '0x10', '0'); + .flashLoan( + _mockFlashLoanReceiver.address, + [weth.address], + [flashAmount], + 1, + caller.address, + '0x10', + '0' + ); const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address); @@ -386,4 +403,82 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt'); }); + + it('Caller takes a WETH flashloan with mode = 1 onBehalfOf user without allowance', async () => { + const {dai, pool, weth, users, helpersContract} = testEnv; + + const caller = users[5]; + const onBehalfOf = users[4]; + + // Deposit 1000 dai for onBehalfOf user + await dai.connect(onBehalfOf.signer).mint(await convertToCurrencyDecimals(dai.address, '1000')); + + await dai.connect(onBehalfOf.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await pool + .connect(onBehalfOf.signer) + .deposit(dai.address, amountToDeposit, onBehalfOf.address, '0'); + + const flashAmount = ethers.utils.parseEther('0.8'); + + await _mockFlashLoanReceiver.setFailExecutionTransfer(true); + + await expect( + pool + .connect(caller.signer) + .flashLoan( + _mockFlashLoanReceiver.address, + [weth.address], + [flashAmount], + 1, + onBehalfOf.address, + '0x10', + '0' + ) + ).to.be.revertedWith(BORROW_ALLOWANCE_ARE_NOT_ENOUGH); + }); + + it('Caller takes a WETH flashloan with mode = 1 onBehalfOf user with allowance. A loan for onBehalfOf is creatd.', async () => { + const {dai, pool, weth, users, helpersContract} = testEnv; + + const caller = users[5]; + const onBehalfOf = users[4]; + + const flashAmount = ethers.utils.parseEther('0.8'); + + // Deposited for onBehalfOf user already, delegate borrow allowance + await pool + .connect(onBehalfOf.signer) + .delegateBorrowAllowance(weth.address, caller.address, 1, flashAmount); + + await _mockFlashLoanReceiver.setFailExecutionTransfer(true); + + await pool + .connect(caller.signer) + .flashLoan( + _mockFlashLoanReceiver.address, + [weth.address], + [flashAmount], + 1, + onBehalfOf.address, + '0x10', + '0' + ); + + const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address); + + const wethDebtToken = await getContract( + eContractid.VariableDebtToken, + stableDebtTokenAddress + ); + + const onBehalfOfDebt = await wethDebtToken.balanceOf(onBehalfOf.address); + + expect(onBehalfOfDebt.toString()).to.be.equal( + '800000000000000000', + 'Invalid onBehalfOf user debt' + ); + }); });