diff --git a/contracts/adapters/FlashLiquidationAdapter.sol b/contracts/adapters/FlashLiquidationAdapter.sol index 5ac66e4f..8c4703ce 100644 --- a/contracts/adapters/FlashLiquidationAdapter.sol +++ b/contracts/adapters/FlashLiquidationAdapter.sol @@ -145,11 +145,11 @@ contract FlashLiquidationAdapter is BaseUniswapAdapter { vars.userCollateralBalance ); - require(coverAmount >= vars.debtAmountNeeded, 'Not enought cover amount requested'); + require(coverAmount >= vars.debtAmountNeeded, 'FLASH_COVER_NOT_ENOUGH'); uint256 flashLoanDebt = coverAmount.add(premium); - require(IERC20(debtAsset).approve(address(LENDING_POOL), debtToCover), 'Approval error'); + IERC20(debtAsset).approve(address(LENDING_POOL), debtToCover); // Liquidate the user position and release the underlying collateral LENDING_POOL.liquidationCall(collateralAsset, debtAsset, user, debtToCover, false); diff --git a/test/__setup.spec.ts b/test/__setup.spec.ts index 53166680..37ff9cfc 100644 --- a/test/__setup.spec.ts +++ b/test/__setup.spec.ts @@ -233,6 +233,7 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => { await waitForTx( await addressesProvider.setLendingPoolCollateralManager(collateralManager.address) ); + await deployMockFlashLoanReceiver(addressesProvider.address); const mockUniswapRouter = await deployMockUniswapRouter(); diff --git a/test/helpers/make-suite.ts b/test/helpers/make-suite.ts index c1bb5b85..4a75e54d 100644 --- a/test/helpers/make-suite.ts +++ b/test/helpers/make-suite.ts @@ -39,7 +39,6 @@ import { solidity } from 'ethereum-waffle'; import { AaveConfig } from '../../markets/aave'; import { FlashLiquidationAdapter } from '../../types'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { hrtime } from 'process'; import { usingTenderly } from '../../helpers/tenderly-utils'; chai.use(bignumberChai()); diff --git a/test/uniswapAdapters.flashLiquidation.spec.ts b/test/uniswapAdapters.flashLiquidation.spec.ts index 2865810d..ff2284e3 100644 --- a/test/uniswapAdapters.flashLiquidation.spec.ts +++ b/test/uniswapAdapters.flashLiquidation.spec.ts @@ -1,34 +1,113 @@ import { makeSuite, TestEnv } from './helpers/make-suite'; import { convertToCurrencyDecimals, - getContract, buildFlashLiquidationAdapterParams, } from '../helpers/contracts-helpers'; import { getMockUniswapRouter } from '../helpers/contracts-getters'; import { deployFlashLiquidationAdapter } from '../helpers/contracts-deployments'; import { MockUniswapV2Router02 } from '../types/MockUniswapV2Router02'; -import { Zero } from '@ethersproject/constants'; import BigNumber from 'bignumber.js'; import { DRE, evmRevert, evmSnapshot, increaseTime } from '../helpers/misc-utils'; import { ethers } from 'ethers'; -import { eContractid, ProtocolErrors, RateMode } from '../helpers/types'; -import { StableDebtToken } from '../types/StableDebtToken'; +import { ProtocolErrors, RateMode } from '../helpers/types'; import { APPROVAL_AMOUNT_LENDING_POOL, MAX_UINT_AMOUNT, oneEther } from '../helpers/constants'; import { getUserData } from './helpers/utils/helpers'; import { calcExpectedStableDebtTokenBalance } from './helpers/utils/calculations'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; -const { parseEther } = ethers.utils; - const { expect } = require('chai'); makeSuite('Uniswap adapters', (testEnv: TestEnv) => { let mockUniswapRouter: MockUniswapV2Router02; + let evmSnapshotId: string; before(async () => { mockUniswapRouter = await getMockUniswapRouter(); }); + const depositAndHFBelowOne = async () => { + const { INVALID_HF } = ProtocolErrors; + const { dai, weth, users, pool, oracle } = testEnv; + const depositor = users[0]; + const borrower = users[1]; + + //mints DAI to depositor + await dai.connect(depositor.signer).mint(await convertToCurrencyDecimals(dai.address, '1000')); + + //approve protocol to access depositor wallet + await dai.connect(depositor.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + //user 1 deposits 1000 DAI + const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await pool + .connect(depositor.signer) + .deposit(dai.address, amountDAItoDeposit, depositor.address, '0'); + //user 2 deposits 1 ETH + const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); + + //mints WETH to borrower + await weth.connect(borrower.signer).mint(await convertToCurrencyDecimals(weth.address, '1000')); + + //approve protocol to access the borrower wallet + await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); + + //user 2 borrows + + const userGlobalDataBefore = await pool.getUserAccountData(borrower.address); + const daiPrice = await oracle.getAssetPrice(dai.address); + + const amountDAIToBorrow = await convertToCurrencyDecimals( + dai.address, + new BigNumber(userGlobalDataBefore.availableBorrowsETH.toString()) + .div(daiPrice.toString()) + .multipliedBy(0.95) + .toFixed(0) + ); + + await pool + .connect(borrower.signer) + .borrow(dai.address, amountDAIToBorrow, RateMode.Stable, '0', borrower.address); + + const userGlobalDataAfter = await pool.getUserAccountData(borrower.address); + + expect(userGlobalDataAfter.currentLiquidationThreshold.toString()).to.be.equal( + '8250', + INVALID_HF + ); + + await oracle.setAssetPrice( + dai.address, + new BigNumber(daiPrice.toString()).multipliedBy(1.18).toFixed(0) + ); + + const userGlobalData = await pool.getUserAccountData(borrower.address); + + expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt( + oneEther.toFixed(0), + INVALID_HF + ); + }; + + beforeEach(async () => { + evmSnapshotId = await evmSnapshot(); + }); + + afterEach(async () => { + await evmRevert(evmSnapshotId); + }); + describe('Flash Liquidation Adapter', () => { + before('Before LendingPool liquidation: set config', () => { + BigNumber.config({ DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN }); + }); + + after('After LendingPool liquidation: reset config', () => { + BigNumber.config({ DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP }); + }); + describe('constructor', () => { it('should deploy with correct parameters', async () => { const { addressesProvider, weth } = testEnv; @@ -51,96 +130,11 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { }); }); - describe('executeOperation', () => { - const { INVALID_HF } = ProtocolErrors; + describe('executeOperation: succesfully liquidateCall and swap via Flash Loan with profits', () => { + it('Liquidates the borrow with profit', async () => { + await depositAndHFBelowOne(); + await increaseTime(100); - before('Before LendingPool liquidation: set config', () => { - BigNumber.config({ DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN }); - }); - - after('After LendingPool liquidation: reset config', () => { - BigNumber.config({ DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP }); - }); - - it('Deposits WETH, borrows DAI', async () => { - const { dai, weth, users, pool, oracle } = testEnv; - const depositor = users[0]; - const borrower = users[1]; - - //mints DAI to depositor - await dai - .connect(depositor.signer) - .mint(await convertToCurrencyDecimals(dai.address, '1000')); - - //approve protocol to access depositor wallet - await dai.connect(depositor.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - - //user 1 deposits 1000 DAI - const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - - await pool - .connect(depositor.signer) - .deposit(dai.address, amountDAItoDeposit, depositor.address, '0'); - //user 2 deposits 1 ETH - const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); - - //mints WETH to borrower - await weth - .connect(borrower.signer) - .mint(await convertToCurrencyDecimals(weth.address, '1000')); - - //approve protocol to access the borrower wallet - await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - - await pool - .connect(borrower.signer) - .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); - - //user 2 borrows - - const userGlobalData = await pool.getUserAccountData(borrower.address); - const daiPrice = await oracle.getAssetPrice(dai.address); - - const amountDAIToBorrow = await convertToCurrencyDecimals( - dai.address, - new BigNumber(userGlobalData.availableBorrowsETH.toString()) - .div(daiPrice.toString()) - .multipliedBy(0.95) - .toFixed(0) - ); - - await pool - .connect(borrower.signer) - .borrow(dai.address, amountDAIToBorrow, RateMode.Stable, '0', borrower.address); - - const userGlobalDataAfter = await pool.getUserAccountData(borrower.address); - - expect(userGlobalDataAfter.currentLiquidationThreshold.toString()).to.be.bignumber.equal( - '8250', - INVALID_HF - ); - }); - - it('Drop the health factor below 1', async () => { - const { dai, weth, users, pool, oracle } = testEnv; - const borrower = users[1]; - - const daiPrice = await oracle.getAssetPrice(dai.address); - - await oracle.setAssetPrice( - dai.address, - new BigNumber(daiPrice.toString()).multipliedBy(1.18).toFixed(0) - ); - - const userGlobalData = await pool.getUserAccountData(borrower.address); - - expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt( - oneEther.toFixed(0), - INVALID_HF - ); - }); - - it('Liquidates the borrow', async () => { const { dai, weth, @@ -150,8 +144,16 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { helpersContract, flashLiquidationAdapter, } = testEnv; + const liquidator = users[3]; const borrower = users[1]; + const expectedSwap = ethers.utils.parseEther('0.4'); + + const liquidatorWethBalanceBefore = await weth.balanceOf(liquidator.address); + + // Set how much ETH will be sold and swapped for DAI at Uniswap mock + await (await mockUniswapRouter.setAmountToSwap(weth.address, expectedSwap)).wait(); + const collateralPrice = await oracle.getAssetPrice(weth.address); const principalPrice = await oracle.getAssetPrice(dai.address); const daiReserveDataBefore = await helpersContract.getReserveData(dai.address); @@ -162,6 +164,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { dai.address, borrower.address ); + const collateralDecimals = ( await helpersContract.getReserveConfigurationData(weth.address) ).decimals.toString(); @@ -181,17 +184,12 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { .div(100) .decimalPlaces(0, BigNumber.ROUND_DOWN); - await increaseTime(100); - const flashLoanDebt = new BigNumber(amountToLiquidate.toString()) .multipliedBy(1.0009) .toFixed(0); - await mockUniswapRouter.setAmountOut( - expectedCollateralLiquidated.toString(), - weth.address, - dai.address, - flashLoanDebt + const expectedProfit = ethers.BigNumber.from(expectedCollateralLiquidated.toString()).sub( + expectedSwap ); const params = buildFlashLiquidationAdapterParams( @@ -212,13 +210,23 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { params, 0 ); - await expect(tx) + + // Expect Swapped event + await expect(Promise.resolve(tx)) .to.emit(flashLiquidationAdapter, 'Swapped') + .withArgs(weth.address, dai.address, expectedSwap.toString(), flashLoanDebt); + + // Expect LiquidationCall event + await expect(Promise.resolve(tx)) + .to.emit(pool, 'LiquidationCall') .withArgs( weth.address, dai.address, + borrower.address, + amountToLiquidate.toString(), expectedCollateralLiquidated.toString(), - flashLoanDebt + flashLiquidationAdapter.address, + false ); const userReserveDataAfter = await getUserData( @@ -227,6 +235,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { dai.address, borrower.address ); + const liquidatorWethBalanceAfter = await weth.balanceOf(liquidator.address); const daiReserveDataAfter = await helpersContract.getReserveData(dai.address); const ethReserveDataAfter = await helpersContract.getReserveData(weth.address); @@ -265,7 +274,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { expect(daiReserveDataAfter.availableLiquidity.toString()).to.be.bignumber.almostEqual( new BigNumber(daiReserveDataBefore.availableLiquidity.toString()) - .plus(amountToLiquidate) + .plus(flashLoanDebt) .toFixed(0), 'Invalid principal available liquidity' ); @@ -276,6 +285,410 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { .toFixed(0), 'Invalid collateral available liquidity' ); + + // Profit after flash loan liquidation + expect(liquidatorWethBalanceAfter).to.be.equal( + liquidatorWethBalanceBefore.add(expectedProfit), + 'Invalid expected WETH profit' + ); + }); + }); + + describe('executeOperation: succesfully liquidateCall and swap via Flash Loan without profits', () => { + it('Liquidates the borrow', async () => { + await depositAndHFBelowOne(); + await increaseTime(100); + + const { + dai, + weth, + users, + pool, + oracle, + helpersContract, + flashLiquidationAdapter, + } = testEnv; + + const liquidator = users[3]; + const borrower = users[1]; + const liquidatorWethBalanceBefore = await weth.balanceOf(liquidator.address); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(dai.address); + const daiReserveDataBefore = await helpersContract.getReserveData(dai.address); + const ethReserveDataBefore = await helpersContract.getReserveData(weth.address); + const userReserveDataBefore = await getUserData( + pool, + helpersContract, + dai.address, + borrower.address + ); + + const collateralDecimals = ( + await helpersContract.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await helpersContract.getReserveConfigurationData(dai.address) + ).decimals.toString(); + const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2).toFixed(0); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToLiquidate).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times( + new BigNumber(10).pow(principalDecimals) + ) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const flashLoanDebt = new BigNumber(amountToLiquidate.toString()) + .multipliedBy(1.0009) + .toFixed(0); + + // Set how much ETH will be sold and swapped for DAI at Uniswap mock + await ( + await mockUniswapRouter.setAmountToSwap( + weth.address, + expectedCollateralLiquidated.toString() + ) + ).wait(); + + const params = buildFlashLiquidationAdapterParams( + weth.address, + dai.address, + borrower.address, + amountToLiquidate, + false + ); + const tx = await pool + .connect(liquidator.signer) + .flashLoan( + flashLiquidationAdapter.address, + [dai.address], + [amountToLiquidate], + [0], + borrower.address, + params, + 0 + ); + + // Expect Swapped event + await expect(Promise.resolve(tx)) + .to.emit(flashLiquidationAdapter, 'Swapped') + .withArgs( + weth.address, + dai.address, + expectedCollateralLiquidated.toString(), + flashLoanDebt + ); + + // Expect LiquidationCall event + await expect(Promise.resolve(tx)) + .to.emit(pool, 'LiquidationCall') + .withArgs( + weth.address, + dai.address, + borrower.address, + amountToLiquidate.toString(), + expectedCollateralLiquidated.toString(), + flashLiquidationAdapter.address, + false + ); + + const userReserveDataAfter = await getUserData( + pool, + helpersContract, + dai.address, + borrower.address + ); + const liquidatorWethBalanceAfter = await weth.balanceOf(liquidator.address); + + const daiReserveDataAfter = await helpersContract.getReserveData(dai.address); + const ethReserveDataAfter = await helpersContract.getReserveData(weth.address); + + if (!tx.blockNumber) { + expect(false, 'Invalid block number'); + return; + } + const txTimestamp = new BigNumber( + (await DRE.ethers.provider.getBlock(tx.blockNumber)).timestamp + ); + + const stableDebtBeforeTx = calcExpectedStableDebtTokenBalance( + userReserveDataBefore.principalStableDebt, + userReserveDataBefore.stableBorrowRate, + userReserveDataBefore.stableRateLastUpdated, + txTimestamp + ); + + expect(userReserveDataAfter.currentStableDebt.toString()).to.be.bignumber.almostEqual( + stableDebtBeforeTx.minus(amountToLiquidate).toFixed(0), + 'Invalid user debt after liquidation' + ); + + //the liquidity index of the principal reserve needs to be bigger than the index before + expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte( + daiReserveDataBefore.liquidityIndex.toString(), + 'Invalid liquidity index' + ); + + //the principal APY after a liquidation needs to be lower than the APY before + expect(daiReserveDataAfter.liquidityRate.toString()).to.be.bignumber.lt( + daiReserveDataBefore.liquidityRate.toString(), + 'Invalid liquidity APY' + ); + + expect(daiReserveDataAfter.availableLiquidity.toString()).to.be.bignumber.almostEqual( + new BigNumber(daiReserveDataBefore.availableLiquidity.toString()) + .plus(flashLoanDebt) + .toFixed(0), + 'Invalid principal available liquidity' + ); + + expect(ethReserveDataAfter.availableLiquidity.toString()).to.be.bignumber.almostEqual( + new BigNumber(ethReserveDataBefore.availableLiquidity.toString()) + .minus(expectedCollateralLiquidated) + .toFixed(0), + 'Invalid collateral available liquidity' + ); + + // Net Profit == 0 after flash loan liquidation + expect(liquidatorWethBalanceAfter).to.be.equal( + liquidatorWethBalanceBefore, + 'Invalid expected WETH profit' + ); + }); + }); + + describe('executeOperation: succesfully liquidateCall all available debt and swap via Flash Loan ', () => { + it('Liquidates the borrow', async () => { + await depositAndHFBelowOne(); + await increaseTime(100); + + const { + dai, + weth, + users, + pool, + oracle, + helpersContract, + flashLiquidationAdapter, + } = testEnv; + + const liquidator = users[3]; + const borrower = users[1]; + const liquidatorWethBalanceBefore = await weth.balanceOf(liquidator.address); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(dai.address); + const daiReserveDataBefore = await helpersContract.getReserveData(dai.address); + const ethReserveDataBefore = await helpersContract.getReserveData(weth.address); + const userReserveDataBefore = await getUserData( + pool, + helpersContract, + dai.address, + borrower.address + ); + + const collateralDecimals = ( + await helpersContract.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await helpersContract.getReserveConfigurationData(dai.address) + ).decimals.toString(); + const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2).toFixed(0); + const extraAmount = new BigNumber(amountToLiquidate).times('1.15').toFixed(0); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToLiquidate).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times( + new BigNumber(10).pow(principalDecimals) + ) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const flashLoanDebt = new BigNumber(extraAmount.toString()).multipliedBy(1.0009).toFixed(0); + + // Set how much ETH will be sold and swapped for DAI at Uniswap mock + await ( + await mockUniswapRouter.setAmountToSwap( + weth.address, + expectedCollateralLiquidated.toString() + ) + ).wait(); + + const params = buildFlashLiquidationAdapterParams( + weth.address, + dai.address, + borrower.address, + MAX_UINT_AMOUNT, + false + ); + const tx = await pool + .connect(liquidator.signer) + .flashLoan( + flashLiquidationAdapter.address, + [dai.address], + [extraAmount], + [0], + borrower.address, + params, + 0 + ); + + // Expect Swapped event + await expect(Promise.resolve(tx)) + .to.emit(flashLiquidationAdapter, 'Swapped') + .withArgs( + weth.address, + dai.address, + expectedCollateralLiquidated.toString(), + flashLoanDebt + ); + + // Expect LiquidationCall event + await expect(Promise.resolve(tx)).to.emit(pool, 'LiquidationCall'); + }); + }); + + describe('executeOperation: invalid params', async () => { + it('Revert if debt asset is different than requested flash loan token', async () => { + await depositAndHFBelowOne(); + + const { dai, weth, users, pool, helpersContract, flashLiquidationAdapter } = testEnv; + + const liquidator = users[3]; + const borrower = users[1]; + const expectedSwap = ethers.utils.parseEther('0.4'); + + // Set how much ETH will be sold and swapped for DAI at Uniswap mock + await (await mockUniswapRouter.setAmountToSwap(weth.address, expectedSwap)).wait(); + + const userReserveDataBefore = await getUserData( + pool, + helpersContract, + dai.address, + borrower.address + ); + + const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2).toFixed(0); + + // Wrong debt asset + const params = buildFlashLiquidationAdapterParams( + weth.address, + weth.address, // intentionally bad + borrower.address, + amountToLiquidate, + false + ); + await expect( + pool + .connect(liquidator.signer) + .flashLoan( + flashLiquidationAdapter.address, + [dai.address], + [amountToLiquidate], + [0], + borrower.address, + params, + 0 + ) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); + }); + + it('Revert if debt asset amount to liquidate is greater than requested flash loan', async () => { + await depositAndHFBelowOne(); + + const { dai, weth, users, pool, helpersContract, flashLiquidationAdapter } = testEnv; + + const liquidator = users[3]; + const borrower = users[1]; + const expectedSwap = ethers.utils.parseEther('0.4'); + + // Set how much ETH will be sold and swapped for DAI at Uniswap mock + await (await mockUniswapRouter.setAmountToSwap(weth.address, expectedSwap)).wait(); + + const userReserveDataBefore = await getUserData( + pool, + helpersContract, + dai.address, + borrower.address + ); + + const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2); + + // Correct params + const params = buildFlashLiquidationAdapterParams( + weth.address, + dai.address, + borrower.address, + amountToLiquidate.toString(), + false + ); + // Bad flash loan params: requested DAI amount below amountToLiquidate + await expect( + pool + .connect(liquidator.signer) + .flashLoan( + flashLiquidationAdapter.address, + [dai.address], + [amountToLiquidate.div(2).toString()], + [0], + borrower.address, + params, + 0 + ) + ).to.be.revertedWith('FLASH_COVER_NOT_ENOUGH'); + }); + + it('Revert if requested multiple assets', async () => { + await depositAndHFBelowOne(); + + const { dai, weth, users, pool, helpersContract, flashLiquidationAdapter } = testEnv; + + const liquidator = users[3]; + const borrower = users[1]; + const expectedSwap = ethers.utils.parseEther('0.4'); + + // Set how much ETH will be sold and swapped for DAI at Uniswap mock + await (await mockUniswapRouter.setAmountToSwap(weth.address, expectedSwap)).wait(); + + const userReserveDataBefore = await getUserData( + pool, + helpersContract, + dai.address, + borrower.address + ); + + const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2); + + // Correct params + const params = buildFlashLiquidationAdapterParams( + weth.address, + dai.address, + borrower.address, + amountToLiquidate.toString(), + false + ); + // Bad flash loan params: requested multiple assets + await expect( + pool + .connect(liquidator.signer) + .flashLoan( + flashLiquidationAdapter.address, + [dai.address, weth.address], + [10, 10], + [0], + borrower.address, + params, + 0 + ) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); }); }); });