mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Add test cases for FlashLiquidationAdapter
This commit is contained in:
parent
7040f9ea2e
commit
94dd996666
|
@ -145,11 +145,11 @@ contract FlashLiquidationAdapter is BaseUniswapAdapter {
|
||||||
vars.userCollateralBalance
|
vars.userCollateralBalance
|
||||||
);
|
);
|
||||||
|
|
||||||
require(coverAmount >= vars.debtAmountNeeded, 'Not enought cover amount requested');
|
require(coverAmount >= vars.debtAmountNeeded, 'FLASH_COVER_NOT_ENOUGH');
|
||||||
|
|
||||||
uint256 flashLoanDebt = coverAmount.add(premium);
|
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
|
// Liquidate the user position and release the underlying collateral
|
||||||
LENDING_POOL.liquidationCall(collateralAsset, debtAsset, user, debtToCover, false);
|
LENDING_POOL.liquidationCall(collateralAsset, debtAsset, user, debtToCover, false);
|
||||||
|
|
|
@ -233,6 +233,7 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
|
||||||
await waitForTx(
|
await waitForTx(
|
||||||
await addressesProvider.setLendingPoolCollateralManager(collateralManager.address)
|
await addressesProvider.setLendingPoolCollateralManager(collateralManager.address)
|
||||||
);
|
);
|
||||||
|
await deployMockFlashLoanReceiver(addressesProvider.address);
|
||||||
|
|
||||||
const mockUniswapRouter = await deployMockUniswapRouter();
|
const mockUniswapRouter = await deployMockUniswapRouter();
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ import { solidity } from 'ethereum-waffle';
|
||||||
import { AaveConfig } from '../../markets/aave';
|
import { AaveConfig } from '../../markets/aave';
|
||||||
import { FlashLiquidationAdapter } from '../../types';
|
import { FlashLiquidationAdapter } from '../../types';
|
||||||
import { HardhatRuntimeEnvironment } from 'hardhat/types';
|
import { HardhatRuntimeEnvironment } from 'hardhat/types';
|
||||||
import { hrtime } from 'process';
|
|
||||||
import { usingTenderly } from '../../helpers/tenderly-utils';
|
import { usingTenderly } from '../../helpers/tenderly-utils';
|
||||||
|
|
||||||
chai.use(bignumberChai());
|
chai.use(bignumberChai());
|
||||||
|
|
|
@ -1,34 +1,113 @@
|
||||||
import { makeSuite, TestEnv } from './helpers/make-suite';
|
import { makeSuite, TestEnv } from './helpers/make-suite';
|
||||||
import {
|
import {
|
||||||
convertToCurrencyDecimals,
|
convertToCurrencyDecimals,
|
||||||
getContract,
|
|
||||||
buildFlashLiquidationAdapterParams,
|
buildFlashLiquidationAdapterParams,
|
||||||
} from '../helpers/contracts-helpers';
|
} from '../helpers/contracts-helpers';
|
||||||
import { getMockUniswapRouter } from '../helpers/contracts-getters';
|
import { getMockUniswapRouter } from '../helpers/contracts-getters';
|
||||||
import { deployFlashLiquidationAdapter } from '../helpers/contracts-deployments';
|
import { deployFlashLiquidationAdapter } from '../helpers/contracts-deployments';
|
||||||
import { MockUniswapV2Router02 } from '../types/MockUniswapV2Router02';
|
import { MockUniswapV2Router02 } from '../types/MockUniswapV2Router02';
|
||||||
import { Zero } from '@ethersproject/constants';
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { DRE, evmRevert, evmSnapshot, increaseTime } from '../helpers/misc-utils';
|
import { DRE, evmRevert, evmSnapshot, increaseTime } from '../helpers/misc-utils';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { eContractid, ProtocolErrors, RateMode } from '../helpers/types';
|
import { ProtocolErrors, RateMode } from '../helpers/types';
|
||||||
import { StableDebtToken } from '../types/StableDebtToken';
|
|
||||||
import { APPROVAL_AMOUNT_LENDING_POOL, MAX_UINT_AMOUNT, oneEther } from '../helpers/constants';
|
import { APPROVAL_AMOUNT_LENDING_POOL, MAX_UINT_AMOUNT, oneEther } from '../helpers/constants';
|
||||||
import { getUserData } from './helpers/utils/helpers';
|
import { getUserData } from './helpers/utils/helpers';
|
||||||
import { calcExpectedStableDebtTokenBalance } from './helpers/utils/calculations';
|
import { calcExpectedStableDebtTokenBalance } from './helpers/utils/calculations';
|
||||||
import { HardhatRuntimeEnvironment } from 'hardhat/types';
|
|
||||||
const { parseEther } = ethers.utils;
|
|
||||||
|
|
||||||
const { expect } = require('chai');
|
const { expect } = require('chai');
|
||||||
|
|
||||||
makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
let mockUniswapRouter: MockUniswapV2Router02;
|
let mockUniswapRouter: MockUniswapV2Router02;
|
||||||
|
let evmSnapshotId: string;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
mockUniswapRouter = await getMockUniswapRouter();
|
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', () => {
|
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', () => {
|
describe('constructor', () => {
|
||||||
it('should deploy with correct parameters', async () => {
|
it('should deploy with correct parameters', async () => {
|
||||||
const { addressesProvider, weth } = testEnv;
|
const { addressesProvider, weth } = testEnv;
|
||||||
|
@ -51,96 +130,11 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('executeOperation', () => {
|
describe('executeOperation: succesfully liquidateCall and swap via Flash Loan with profits', () => {
|
||||||
const { INVALID_HF } = ProtocolErrors;
|
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 {
|
const {
|
||||||
dai,
|
dai,
|
||||||
weth,
|
weth,
|
||||||
|
@ -150,8 +144,16 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
helpersContract,
|
helpersContract,
|
||||||
flashLiquidationAdapter,
|
flashLiquidationAdapter,
|
||||||
} = testEnv;
|
} = testEnv;
|
||||||
|
|
||||||
const liquidator = users[3];
|
const liquidator = users[3];
|
||||||
const borrower = users[1];
|
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 collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
const principalPrice = await oracle.getAssetPrice(dai.address);
|
const principalPrice = await oracle.getAssetPrice(dai.address);
|
||||||
const daiReserveDataBefore = await helpersContract.getReserveData(dai.address);
|
const daiReserveDataBefore = await helpersContract.getReserveData(dai.address);
|
||||||
|
@ -162,6 +164,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
dai.address,
|
dai.address,
|
||||||
borrower.address
|
borrower.address
|
||||||
);
|
);
|
||||||
|
|
||||||
const collateralDecimals = (
|
const collateralDecimals = (
|
||||||
await helpersContract.getReserveConfigurationData(weth.address)
|
await helpersContract.getReserveConfigurationData(weth.address)
|
||||||
).decimals.toString();
|
).decimals.toString();
|
||||||
|
@ -181,17 +184,12 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
.div(100)
|
.div(100)
|
||||||
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
await increaseTime(100);
|
|
||||||
|
|
||||||
const flashLoanDebt = new BigNumber(amountToLiquidate.toString())
|
const flashLoanDebt = new BigNumber(amountToLiquidate.toString())
|
||||||
.multipliedBy(1.0009)
|
.multipliedBy(1.0009)
|
||||||
.toFixed(0);
|
.toFixed(0);
|
||||||
|
|
||||||
await mockUniswapRouter.setAmountOut(
|
const expectedProfit = ethers.BigNumber.from(expectedCollateralLiquidated.toString()).sub(
|
||||||
expectedCollateralLiquidated.toString(),
|
expectedSwap
|
||||||
weth.address,
|
|
||||||
dai.address,
|
|
||||||
flashLoanDebt
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const params = buildFlashLiquidationAdapterParams(
|
const params = buildFlashLiquidationAdapterParams(
|
||||||
|
@ -212,13 +210,23 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
params,
|
params,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
await expect(tx)
|
|
||||||
|
// Expect Swapped event
|
||||||
|
await expect(Promise.resolve(tx))
|
||||||
.to.emit(flashLiquidationAdapter, 'Swapped')
|
.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(
|
.withArgs(
|
||||||
weth.address,
|
weth.address,
|
||||||
dai.address,
|
dai.address,
|
||||||
|
borrower.address,
|
||||||
|
amountToLiquidate.toString(),
|
||||||
expectedCollateralLiquidated.toString(),
|
expectedCollateralLiquidated.toString(),
|
||||||
flashLoanDebt
|
flashLiquidationAdapter.address,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const userReserveDataAfter = await getUserData(
|
const userReserveDataAfter = await getUserData(
|
||||||
|
@ -227,6 +235,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
dai.address,
|
dai.address,
|
||||||
borrower.address
|
borrower.address
|
||||||
);
|
);
|
||||||
|
const liquidatorWethBalanceAfter = await weth.balanceOf(liquidator.address);
|
||||||
|
|
||||||
const daiReserveDataAfter = await helpersContract.getReserveData(dai.address);
|
const daiReserveDataAfter = await helpersContract.getReserveData(dai.address);
|
||||||
const ethReserveDataAfter = await helpersContract.getReserveData(weth.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(
|
expect(daiReserveDataAfter.availableLiquidity.toString()).to.be.bignumber.almostEqual(
|
||||||
new BigNumber(daiReserveDataBefore.availableLiquidity.toString())
|
new BigNumber(daiReserveDataBefore.availableLiquidity.toString())
|
||||||
.plus(amountToLiquidate)
|
.plus(flashLoanDebt)
|
||||||
.toFixed(0),
|
.toFixed(0),
|
||||||
'Invalid principal available liquidity'
|
'Invalid principal available liquidity'
|
||||||
);
|
);
|
||||||
|
@ -276,6 +285,410 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
.toFixed(0),
|
.toFixed(0),
|
||||||
'Invalid collateral available liquidity'
|
'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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user