From d1f21118c19ed0c0d38141278bb7c7369026c3b6 Mon Sep 17 00:00:00 2001 From: andyk <kyzia.ru@gmail.com> Date: Thu, 18 Jun 2020 17:36:37 +0300 Subject: [PATCH] initial refactoring of liquidation-atoken.spec --- helpers/types.ts | 4 + test/__setup.spec.ts | 2 + test/helpers/make-suite.ts | 112 +++-- test/liquidation-atoken.spec.ts | 846 +++++++++++++------------------- 4 files changed, 419 insertions(+), 545 deletions(-) diff --git a/helpers/types.ts b/helpers/types.ts index e623a95a..14568310 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -58,6 +58,10 @@ export enum ProtocolErrors { INCONSISTENT_PROTOCOL_BALANCE = "The actual balance of the protocol is inconsistent", TOO_SMALL_FLASH_LOAN = "The requested amount is too small for a flashLoan.", NOT_ENOUGH_LIQUIDITY_TO_BORROW = "There is not enough liquidity available to borrow", + HF_IS_NOT_BELLOW_THRESHOLD = "Health factor is not below the threshold", + INVALID_HF = "Invalid health factor", + USER_DID_NOT_BORROW_SPECIFIED = "User did not borrow the specified currency", + INVALID_COLLATERAL_TO_LIQUIDATE = "Invalid collateral to liquidate", } export type tEthereumAddress = string; diff --git a/test/__setup.spec.ts b/test/__setup.spec.ts index ffefdbab..aedd6e97 100644 --- a/test/__setup.spec.ts +++ b/test/__setup.spec.ts @@ -63,6 +63,7 @@ import {MockAggregator} from "../types/MockAggregator"; import {LendingRateOracle} from "../types/LendingRateOracle"; import {LendingPoolCore} from "../types/LendingPoolCore"; import {LendingPoolConfigurator} from "../types/LendingPoolConfigurator"; +import { initializeMakeSuite } from './helpers/make-suite'; const deployAllMockTokens = async (deployer: Signer) => { const tokens: {[symbol: string]: MockContract | MintableErc20} = {}; @@ -642,6 +643,7 @@ before(async () => { const [deployer, secondaryWallet] = await getEthersSigners(); console.log("-> Deploying test environment..."); await buildTestEnv(deployer, secondaryWallet); + await initializeMakeSuite(); console.log("\n***************"); console.log("Setup and snapshot finished"); console.log("***************\n"); diff --git a/test/helpers/make-suite.ts b/test/helpers/make-suite.ts index 89a9e8f8..e70cb56c 100644 --- a/test/helpers/make-suite.ts +++ b/test/helpers/make-suite.ts @@ -1,5 +1,4 @@ import {evmRevert, evmSnapshot, BRE} from "../../helpers/misc-utils"; -import {TEST_SNAPSHOT_ID} from "../../helpers/constants"; import {Signer} from "ethers"; import { getEthersSigners, @@ -9,8 +8,8 @@ import { getAaveProtocolTestHelpers, getAToken, getMintableErc20, - getLendingPoolConfiguratorProxy, -} from "../../helpers/contracts-helpers"; + getLendingPoolConfiguratorProxy, getPriceOracle, +} from '../../helpers/contracts-helpers'; import {tEthereumAddress} from "../../helpers/types"; import {LendingPool} from "../../types/LendingPool"; import {LendingPoolCore} from "../../types/LendingPoolCore"; @@ -23,6 +22,7 @@ import {LendingPoolConfigurator} from "../../types/LendingPoolConfigurator"; import chai from "chai"; // @ts-ignore import bignumberChai from "chai-bignumber"; +import { PriceOracle } from '../../types/PriceOracle'; chai.use(bignumberChai()); export interface SignerWithAddress { @@ -36,9 +36,11 @@ export interface TestEnv { core: LendingPoolCore; configurator: LendingPoolConfigurator; addressesProvider: LendingPoolAddressesProvider; + oracle: PriceOracle; helpersContract: AaveProtocolTestHelpers; dai: MintableErc20; aDai: AToken; + usdc: MintableErc20; } let buidlerevmSnapshotId: string = "0x1"; @@ -48,59 +50,65 @@ const setBuidlerevmSnapshotId = (id: string) => { } }; +const testEnv: TestEnv = { + deployer: {} as SignerWithAddress, + users: [] as SignerWithAddress[], + pool: {} as LendingPool, + core: {} as LendingPoolCore, + configurator: {} as LendingPoolConfigurator, + addressesProvider: {} as LendingPoolAddressesProvider, + helpersContract: {} as AaveProtocolTestHelpers, + oracle: {} as PriceOracle, + dai: {} as MintableErc20, + aDai: {} as AToken, + usdc: {} as MintableErc20, +} as TestEnv; + +export async function initializeMakeSuite() { + const [_deployer, ...restSigners] = await getEthersSigners(); + const deployer: SignerWithAddress = { + address: await _deployer.getAddress(), + signer: _deployer, + }; + + for (const signer of restSigners) { + testEnv.users.push({ + signer, + address: await signer.getAddress(), + }); + } + testEnv.deployer = deployer; + testEnv.pool = await getLendingPool(); + testEnv.core = await getLendingPoolCore(); + testEnv.configurator = await getLendingPoolConfiguratorProxy(); + testEnv.oracle = await getPriceOracle(); + testEnv.addressesProvider = await getLendingPoolAddressesProvider(); + testEnv.helpersContract = await getAaveProtocolTestHelpers(); + const aDaiAddress = (await testEnv.helpersContract.getAllATokens()).find( + (aToken) => aToken.symbol === "aDAI" + )?.tokenAddress; + + const reservesTokens = await testEnv.helpersContract.getAllReservesTokens(); + const daiAddress = reservesTokens.find(token => token.symbol === "DAI")?.tokenAddress; + const usdcAddress = reservesTokens.find(token => token.symbol === "USDC")?.tokenAddress; + if (!aDaiAddress) { + console.log(`atoken-modifiers.spec: aDAI not correctly initialized`); + process.exit(1); + } + if (!daiAddress || !usdcAddress) { + console.log(`atoken-modifiers.spec: USDC or DAI not correctly initialized`); + process.exit(1); + } + + testEnv.aDai = await getAToken(aDaiAddress); + testEnv.dai = await getMintableErc20(daiAddress); + testEnv.usdc = await getMintableErc20(usdcAddress); +} + export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) { describe(name, () => { - const testEnv: TestEnv = { - deployer: {} as SignerWithAddress, - users: [] as SignerWithAddress[], - pool: {} as LendingPool, - core: {} as LendingPoolCore, - configurator: {} as LendingPoolConfigurator, - addressesProvider: {} as LendingPoolAddressesProvider, - helpersContract: {} as AaveProtocolTestHelpers, - dai: {} as MintableErc20, - aDai: {} as AToken, - } as TestEnv; before(async () => { - console.time("makeSuite"); setBuidlerevmSnapshotId(await evmSnapshot()); - const [_deployer, ...restSigners] = await getEthersSigners(); - const deployer: SignerWithAddress = { - address: await _deployer.getAddress(), - signer: _deployer, - }; - - for (const signer of restSigners) { - testEnv.users.push({ - signer, - address: await signer.getAddress(), - }); - } - testEnv.deployer = deployer; - testEnv.pool = await getLendingPool(); - testEnv.core = await getLendingPoolCore(); - testEnv.configurator = await getLendingPoolConfiguratorProxy(); - testEnv.addressesProvider = await getLendingPoolAddressesProvider(); - testEnv.helpersContract = await getAaveProtocolTestHelpers(); - const aDaiAddress = (await testEnv.helpersContract.getAllATokens()).find( - (aToken) => aToken.symbol === "aDAI" - )?.tokenAddress; - - const daiAddress = ( - await await testEnv.helpersContract.getAllReservesTokens() - ).find((token) => token.symbol === "DAI")?.tokenAddress; - if (!aDaiAddress) { - console.log(`atoken-modifiers.spec: aDAI not correctly initialized`); - process.exit(1); - } - if (!daiAddress) { - console.log(`atoken-modifiers.spec: DAI not correctly initialized`); - process.exit(1); - } - - testEnv.aDai = await getAToken(aDaiAddress); - testEnv.dai = await getMintableErc20(daiAddress); - console.timeEnd("makeSuite"); }); tests(testEnv); after(async () => { diff --git a/test/liquidation-atoken.spec.ts b/test/liquidation-atoken.spec.ts index fd0c03a7..62b610fe 100644 --- a/test/liquidation-atoken.spec.ts +++ b/test/liquidation-atoken.spec.ts @@ -1,493 +1,353 @@ -// import { -// LendingPoolInstance, -// LendingPoolCoreInstance, -// IPriceOracleInstance, -// ATokenInstance, -// LendingPoolAddressesProviderInstance, -// MintableERC20Instance, -// } from '../utils/typechain-types/truffle-contracts'; -// import { -// ContractId, -// IReserveParams, -// iATokenBase, -// iAavePoolAssets, -// iAssetsWithoutETH, -// ITestEnvWithoutInstances, -// RateMode, -// } from '../utils/types'; -// import BigNumber from 'bignumber.js'; -// import { -// APPROVAL_AMOUNT_LENDING_POOL_CORE, -// oneEther, -// ETHEREUM_ADDRESS, -// } from '../utils/constants'; -// import {testEnvProviderWithoutInstances} from '../utils/truffle/dlp-tests-env'; -// import {convertToCurrencyDecimals} from '../utils/misc-utils'; -// import {getTruffleContractInstance} from '../utils/truffle/truffle-core-utils'; - -// const expectRevert = require('@openzeppelin/test-helpers').expectRevert; - -// const {expect} = require('chai'); - -// const almostEqual: any = function(this: any, expected: any, actual: any): any { -// this.assert( -// expected.plus(new BigNumber(1)).eq(actual) || -// expected.plus(new BigNumber(2)).eq(actual) || -// actual.plus(new BigNumber(1)).eq(expected) || -// actual.plus(new BigNumber(2)).eq(expected) || -// expected.eq(actual), -// 'expected #{act} to be almost equal #{exp}', -// 'expected #{act} to be different from #{exp}', -// expected.toString(), -// actual.toString() -// ); -// }; - -// require('chai').use(function(chai: any, utils: any) { -// chai.Assertion.overwriteMethod('almostEqual', function(original: any) { -// return function(this: any, value: any) { -// if (utils.flag(this, 'bignumber')) { -// var expected = new BigNumber(value); -// var actual = new BigNumber(this._obj); -// almostEqual.apply(this, [expected, actual]); -// } else { -// original.apply(this, arguments); -// } -// }; -// }); -// }); - -// contract('LendingPool liquidation - liquidator receiving aToken', async ([deployer, ...users]) => { -// let _testEnvProvider: ITestEnvWithoutInstances; -// let _lendingPoolInstance: LendingPoolInstance; -// let _lendingPoolCoreInstance: LendingPoolCoreInstance; -// let _lendingPoolAddressesProviderInstance: LendingPoolAddressesProviderInstance; -// let _priceOracleInstance: IPriceOracleInstance; -// let _aTokenInstances: iATokenBase<ATokenInstance>; -// let _tokenInstances: iAssetsWithoutETH<MintableERC20Instance>; - -// let _daiAddress: string; - -// let _reservesParams: iAavePoolAssets<IReserveParams>; - -// let _depositorAddress: string; -// let _borrowerAddress: string; - -// let _web3: Web3; - -// let _initialDepositorETHBalance: string; - -// before('Initializing LendingPool test variables', async () => { -// console.time('setup-test'); -// _testEnvProvider = await testEnvProviderWithoutInstances(artifacts, [deployer, ...users]); - -// const { -// getWeb3, -// getAllAssetsInstances, -// getFirstBorrowerAddressOnTests, -// getFirstDepositorAddressOnTests, -// getAavePoolReservesParams, -// getLendingPoolInstance, -// getLendingPoolCoreInstance, -// getPriceOracleInstance, -// getATokenInstances, -// getLendingPoolAddressesProviderInstance, -// } = _testEnvProvider; - -// const instances = await Promise.all([ -// getLendingPoolInstance(), -// getLendingPoolCoreInstance(), -// getPriceOracleInstance(), -// getATokenInstances(), -// getLendingPoolAddressesProviderInstance(), -// getAllAssetsInstances(), -// ]); - -// _reservesParams = await getAavePoolReservesParams(); -// _lendingPoolInstance = instances[0]; -// _lendingPoolCoreInstance = instances[1]; -// _priceOracleInstance = instances[2]; -// _aTokenInstances = instances[3]; -// _lendingPoolAddressesProviderInstance = instances[4]; -// _tokenInstances = instances[5]; -// _daiAddress = _tokenInstances.DAI.address; -// _depositorAddress = await getFirstDepositorAddressOnTests(); -// _borrowerAddress = await getFirstBorrowerAddressOnTests(); - -// _web3 = await getWeb3(); -// _initialDepositorETHBalance = await _web3.eth.getBalance(_depositorAddress); -// console.timeEnd('setup-test'); -// }); - -// it('LIQUIDATION - Deposits ETH, borrows DAI/Check liquidation fails because health factor is above 1', async () => { -// const {DAI: daiInstance} = _tokenInstances; - -// const aEthInstance: ATokenInstance = await getTruffleContractInstance( -// artifacts, -// ContractId.AToken, -// await _lendingPoolCoreInstance.getReserveATokenAddress(ETHEREUM_ADDRESS) -// ); - -// //mints DAI to depositor -// await daiInstance.mint(await convertToCurrencyDecimals(daiInstance.address, '1000'), { -// from: _depositorAddress, -// }); - -// //approve protocol to access depositor wallet -// await daiInstance.approve(_lendingPoolCoreInstance.address, APPROVAL_AMOUNT_LENDING_POOL_CORE, { -// from: _depositorAddress, -// }); - -// //user 1 deposits 1000 DAI -// const amountDAItoDeposit = await convertToCurrencyDecimals(_daiAddress, '1000'); - -// await _lendingPoolInstance.deposit(_daiAddress, amountDAItoDeposit, '0', { -// from: _depositorAddress, -// }); - -// //user 2 deposits 1 ETH -// const amountETHtoDeposit = await convertToCurrencyDecimals(ETHEREUM_ADDRESS, '1'); - -// await _lendingPoolInstance.deposit(ETHEREUM_ADDRESS, amountETHtoDeposit, '0', { -// from: _borrowerAddress, -// value: amountETHtoDeposit, -// }); - -// //user 2 borrows - -// const userGlobalData: any = await _lendingPoolInstance.getUserAccountData(_borrowerAddress); -// const daiPrice = await _priceOracleInstance.getAssetPrice(_daiAddress); - -// const amountDAIToBorrow = await convertToCurrencyDecimals( -// _daiAddress, -// new BigNumber(userGlobalData.availableBorrowsETH) -// .div(daiPrice) -// .multipliedBy(0.95) -// .toFixed(0) -// ); - -// await _lendingPoolInstance.borrow(_daiAddress, amountDAIToBorrow, RateMode.Stable, '0', { -// from: _borrowerAddress, -// }); - -// const userGlobalDataAfter: any = await _lendingPoolInstance.getUserAccountData( -// _borrowerAddress -// ); - -// expect(userGlobalDataAfter.currentLiquidationThreshold).to.be.bignumber.equal( -// '80', -// 'Invalid liquidation threshold' -// ); - -// //user 2 tries to borrow -// await expectRevert( -// _lendingPoolInstance.liquidationCall( -// ETHEREUM_ADDRESS, -// _daiAddress, -// _borrowerAddress, -// amountDAIToBorrow, -// true -// ), -// 'Health factor is not below the threshold' -// ); -// }); - -// it('LIQUIDATION - Drop the health factor below 1', async () => { -// const daiPrice = await _priceOracleInstance.getAssetPrice(_daiAddress); - -// //halving the price of ETH - means doubling the DAIETH exchange rate - -// await _priceOracleInstance.setAssetPrice( -// _daiAddress, -// new BigNumber(daiPrice).multipliedBy(1.15).toFixed(0) -// ); - -// const userGlobalData: any = await _lendingPoolInstance.getUserAccountData(_borrowerAddress); - -// expect(userGlobalData.healthFactor).to.be.bignumber.lt( -// oneEther.toFixed(0), -// 'Invalid health factor' -// ); -// }); - -// it('LIQUIDATION - Tries to liquidate a different currency than the loan principal', async () => { -// //user 2 tries to borrow -// await expectRevert( -// _lendingPoolInstance.liquidationCall( -// ETHEREUM_ADDRESS, -// ETHEREUM_ADDRESS, -// _borrowerAddress, -// oneEther, -// true -// ), -// 'User did not borrow the specified currency' -// ); -// }); - -// it('LIQUIDATION - Tries to liquidate a different collateral than the borrower collateral', async () => { -// //user 2 tries to borrow -// await expectRevert( -// _lendingPoolInstance.liquidationCall( -// _daiAddress, -// _daiAddress, -// _borrowerAddress, -// oneEther, -// true -// ), -// 'Invalid collateral to liquidate' -// ); -// }); - -// it('LIQUIDATION - Liquidates the borrow', async () => { -// const {DAI: daiInstance} = _tokenInstances; - -// //mints dai to the caller - -// await daiInstance.mint(await convertToCurrencyDecimals(daiInstance.address, '1000')); - -// //approve protocol to access depositor wallet -// await daiInstance.approve(_lendingPoolCoreInstance.address, APPROVAL_AMOUNT_LENDING_POOL_CORE); - -// const userReserveDataBefore: any = await _lendingPoolInstance.getUserReserveData( -// _daiAddress, -// _borrowerAddress -// ); - -// const daiReserveDataBefore: any = await _lendingPoolInstance.getReserveData(_daiAddress); -// const ethReserveDataBefore: any = await _lendingPoolInstance.getReserveData(ETHEREUM_ADDRESS); - -// const amountToLiquidate = new BigNumber(userReserveDataBefore.currentBorrowBalance) -// .div(2) -// .toFixed(0); - -// await _lendingPoolInstance.liquidationCall( -// ETHEREUM_ADDRESS, -// _daiAddress, -// _borrowerAddress, -// amountToLiquidate, -// true -// ); - -// const userReserveDataAfter: any = await _lendingPoolInstance.getUserReserveData( -// _daiAddress, -// _borrowerAddress -// ); - -// const userGlobalDataAfter: any = await _lendingPoolInstance.getUserAccountData( -// _borrowerAddress -// ); - -// const daiReserveDataAfter: any = await _lendingPoolInstance.getReserveData(_daiAddress); -// const ethReserveDataAfter: any = await _lendingPoolInstance.getReserveData(ETHEREUM_ADDRESS); - -// const feeAddress = await _lendingPoolAddressesProviderInstance.getTokenDistributor(); - -// const feeAddressBalance = await web3.eth.getBalance(feeAddress); - -// const collateralPrice = await _priceOracleInstance.getAssetPrice(ETHEREUM_ADDRESS); -// const principalPrice = await _priceOracleInstance.getAssetPrice(_daiAddress); - -// const collateralDecimals = await _lendingPoolCoreInstance.getReserveDecimals(ETHEREUM_ADDRESS); -// const principalDecimals = await _lendingPoolCoreInstance.getReserveDecimals(_daiAddress); - -// const expectedCollateralLiquidated = new BigNumber(principalPrice) -// .times(new BigNumber(amountToLiquidate).times(105)) -// .times(new BigNumber(10).pow(collateralDecimals)) -// .div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals))) -// .decimalPlaces(0, BigNumber.ROUND_DOWN); - -// const expectedFeeLiquidated = new BigNumber(principalPrice) -// .times(new BigNumber(userReserveDataBefore.originationFee).times(105)) -// .times(new BigNumber(10).pow(collateralDecimals)) -// .div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals))) -// .div(100) -// .decimalPlaces(0, BigNumber.ROUND_DOWN); - -// expect(userGlobalDataAfter.healthFactor).to.be.bignumber.gt( -// oneEther.toFixed(0), -// 'Invalid health factor' -// ); - -// expect(userReserveDataAfter.originationFee).to.be.bignumber.eq( -// '0', -// 'Origination fee should be repaid' -// ); - -// expect(feeAddressBalance).to.be.bignumber.gt('0'); - -// expect(userReserveDataAfter.principalBorrowBalance).to.be.bignumber.almostEqual( -// new BigNumber(userReserveDataBefore.currentBorrowBalance).minus(amountToLiquidate).toFixed(0), -// 'Invalid user borrow balance after liquidation' -// ); - -// expect(daiReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual( -// new BigNumber(daiReserveDataBefore.availableLiquidity).plus(amountToLiquidate).toFixed(0), -// 'Invalid principal available liquidity' -// ); - -// expect(ethReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual( -// new BigNumber(ethReserveDataBefore.availableLiquidity) -// .minus(expectedFeeLiquidated) -// .toFixed(0), -// 'Invalid collateral available liquidity' -// ); -// }); - -// it('User 3 deposits 1000 USDC, user 4 1 ETH, user 4 borrows - drops HF, liquidates the borrow', async () => { -// const {USDC: usdcInstance} = _tokenInstances; - -// //mints USDC to depositor -// await usdcInstance.mint(await convertToCurrencyDecimals(usdcInstance.address, '1000'), { -// from: users[3], -// }); - -// //approve protocol to access depositor wallet -// await usdcInstance.approve( -// _lendingPoolCoreInstance.address, -// APPROVAL_AMOUNT_LENDING_POOL_CORE, -// { -// from: users[3], -// } -// ); - -// //user 3 deposits 1000 USDC -// const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdcInstance.address, '1000'); - -// await _lendingPoolInstance.deposit(usdcInstance.address, amountUSDCtoDeposit, '0', { -// from: users[3], -// }); - -// //user 4 deposits 1 ETH -// const amountETHtoDeposit = await convertToCurrencyDecimals(ETHEREUM_ADDRESS, '1'); - -// await _lendingPoolInstance.deposit(ETHEREUM_ADDRESS, amountETHtoDeposit, '0', { -// from: users[4], -// value: amountETHtoDeposit, -// }); - -// //user 4 borrows -// const userGlobalData: any = await _lendingPoolInstance.getUserAccountData(users[4]); - -// const usdcPrice = await _priceOracleInstance.getAssetPrice(usdcInstance.address); - -// const amountUSDCToBorrow = await convertToCurrencyDecimals( -// usdcInstance.address, -// new BigNumber(userGlobalData.availableBorrowsETH) -// .div(usdcPrice) -// .multipliedBy(0.95) -// .toFixed(0) -// ); - -// await _lendingPoolInstance.borrow( -// usdcInstance.address, -// amountUSDCToBorrow, -// RateMode.Stable, -// '0', -// { -// from: users[4], -// } -// ); - -// //drops HF below 1 - -// await _priceOracleInstance.setAssetPrice( -// usdcInstance.address, -// new BigNumber(usdcPrice).multipliedBy(1.2).toFixed(0) -// ); - -// //mints dai to the liquidator - -// await usdcInstance.mint(await convertToCurrencyDecimals(usdcInstance.address, '1000')); - -// //approve protocol to access depositor wallet -// await usdcInstance.approve(_lendingPoolCoreInstance.address, APPROVAL_AMOUNT_LENDING_POOL_CORE); - -// const userReserveDataBefore: any = await _lendingPoolInstance.getUserReserveData( -// usdcInstance.address, -// users[4] -// ); - -// const usdcReserveDataBefore: any = await _lendingPoolInstance.getReserveData( -// usdcInstance.address -// ); -// const ethReserveDataBefore: any = await _lendingPoolInstance.getReserveData(ETHEREUM_ADDRESS); - -// const amountToLiquidate = new BigNumber(userReserveDataBefore.currentBorrowBalance) -// .div(2) -// .toFixed(0); - -// await _lendingPoolInstance.liquidationCall( -// ETHEREUM_ADDRESS, -// usdcInstance.address, -// users[4], -// amountToLiquidate, -// true -// ); - -// const userReserveDataAfter: any = await _lendingPoolInstance.getUserReserveData( -// usdcInstance.address, -// users[4] -// ); - -// const userGlobalDataAfter: any = await _lendingPoolInstance.getUserAccountData(users[4]); - -// const usdcReserveDataAfter: any = await _lendingPoolInstance.getReserveData( -// usdcInstance.address -// ); -// const ethReserveDataAfter: any = await _lendingPoolInstance.getReserveData(ETHEREUM_ADDRESS); - -// const feeAddress = await _lendingPoolAddressesProviderInstance.getTokenDistributor(); - -// const feeAddressBalance = await web3.eth.getBalance(feeAddress); - -// const collateralPrice = await _priceOracleInstance.getAssetPrice(ETHEREUM_ADDRESS); -// const principalPrice = await _priceOracleInstance.getAssetPrice(usdcInstance.address); - -// const collateralDecimals = await _lendingPoolCoreInstance.getReserveDecimals(ETHEREUM_ADDRESS); -// const principalDecimals = await _lendingPoolCoreInstance.getReserveDecimals( -// usdcInstance.address -// ); - -// const expectedCollateralLiquidated = new BigNumber(principalPrice) -// .times(new BigNumber(amountToLiquidate).times(105)) -// .times(new BigNumber(10).pow(collateralDecimals)) -// .div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals))) -// .decimalPlaces(0, BigNumber.ROUND_DOWN); - -// const expectedFeeLiquidated = new BigNumber(principalPrice) -// .times(new BigNumber(userReserveDataBefore.originationFee).times(105)) -// .times(new BigNumber(10).pow(collateralDecimals)) -// .div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals))) -// .div(100) -// .decimalPlaces(0, BigNumber.ROUND_DOWN); - -// expect(userGlobalDataAfter.healthFactor).to.be.bignumber.gt( -// oneEther.toFixed(0), -// 'Invalid health factor' -// ); - -// expect(userReserveDataAfter.originationFee).to.be.bignumber.eq( -// '0', -// 'Origination fee should be repaid' -// ); - -// expect(feeAddressBalance).to.be.bignumber.gt('0'); - -// expect(userReserveDataAfter.principalBorrowBalance).to.be.bignumber.almostEqual( -// new BigNumber(userReserveDataBefore.currentBorrowBalance).minus(amountToLiquidate).toFixed(0), -// 'Invalid user borrow balance after liquidation' -// ); - -// expect(usdcReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual( -// new BigNumber(usdcReserveDataBefore.availableLiquidity).plus(amountToLiquidate).toFixed(0), -// 'Invalid principal available liquidity' -// ); - -// expect(ethReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual( -// new BigNumber(ethReserveDataBefore.availableLiquidity) -// .minus(expectedFeeLiquidated) -// .toFixed(0), -// 'Invalid collateral available liquidity' -// ); -// }); -// }); +import BigNumber from 'bignumber.js'; + +import {BRE} from '../helpers/misc-utils'; +import {APPROVAL_AMOUNT_LENDING_POOL_CORE, MOCK_ETH_ADDRESS, oneEther} from '../helpers/constants'; +import {convertToCurrencyDecimals} from '../helpers/contracts-helpers'; +import {makeSuite} from './helpers/make-suite'; +import {ProtocolErrors, RateMode} from '../helpers/types'; + +const {expect} = require('chai'); + +makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => { + const { + HF_IS_NOT_BELLOW_THRESHOLD, + INVALID_HF, + USER_DID_NOT_BORROW_SPECIFIED, + INVALID_COLLATERAL_TO_LIQUIDATE, + } = ProtocolErrors; + + it('LIQUIDATION - Deposits ETH, borrows DAI/Check liquidation fails because health factor is above 1', async () => { + const {dai, users, core, 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(core.address, APPROVAL_AMOUNT_LENDING_POOL_CORE); + + //user 1 deposits 1000 DAI + const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + await pool.connect(depositor.signer).deposit(dai.address, amountDAItoDeposit, '0'); + + //user 2 deposits 1 ETH + const amountETHtoDeposit = await convertToCurrencyDecimals(MOCK_ETH_ADDRESS, '1'); + await pool + .connect(borrower.signer) + .deposit(MOCK_ETH_ADDRESS, amountETHtoDeposit, '0', {value: amountETHtoDeposit}); + + await pool.connect(borrower.signer).deposit(MOCK_ETH_ADDRESS, amountETHtoDeposit, '0', { + value: amountETHtoDeposit, + }); + + //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.Variable, '0'); + + const userGlobalDataAfter = await pool.getUserAccountData(borrower.address); + console.log('userGlobalDataAfter.healthFactor', userGlobalDataAfter.healthFactor.toString()); + + expect(userGlobalDataAfter.currentLiquidationThreshold).to.be.bignumber.equal( + '80', + 'Invalid liquidation threshold' + ); + + //someone tries to liquidate user 2 + await expect( + pool.liquidationCall(MOCK_ETH_ADDRESS, dai.address, borrower.address, 1, true) + ).to.be.revertedWith(HF_IS_NOT_BELLOW_THRESHOLD); + }); + + it('LIQUIDATION - Drop the health factor below 1', async () => { + const {dai, users, pool, oracle} = testEnv; + const borrower = users[1]; + + const daiPrice = await oracle.getAssetPrice(dai.address); + + //halving the price of ETH - means doubling the DAIETH exchange rate + console.log('DAI price before', daiPrice.toString()); + + await oracle.setAssetPrice( + dai.address, + new BigNumber(daiPrice.toString()).multipliedBy(1.15).toFixed(0) + ); + console.log('DAI price after', (await oracle.getAssetPrice(dai.address)).toString()); + + const userGlobalData = await pool.getUserAccountData(borrower.address); + + expect(userGlobalData.healthFactor).to.be.bignumber.lt(oneEther.toFixed(0), INVALID_HF); + }); + + it('LIQUIDATION - Tries to liquidate a different currency than the loan principal', async () => { + const {pool, users} = testEnv; + const borrower = users[1]; + //user 2 tries to borrow + await expect( + pool.liquidationCall( + MOCK_ETH_ADDRESS, + MOCK_ETH_ADDRESS, + borrower.address, + oneEther.toString(), + true + ) + ).revertedWith(USER_DID_NOT_BORROW_SPECIFIED); + }); + + it( + 'LIQUIDATION - Tries to liquidate a different ' + 'collateral than the borrower collateral', + async () => { + const {pool, dai, users} = testEnv; + const borrower = users[1]; + + await expect( + pool.liquidationCall(dai.address, dai.address, borrower.address, oneEther.toString(), true) + ).revertedWith(INVALID_COLLATERAL_TO_LIQUIDATE); + } + ); + + it('LIQUIDATION - Liquidates the borrow', async () => { + const {pool, dai, core, users, addressesProvider, oracle} = testEnv; + const borrower = users[1]; + + //mints dai to the caller + + await dai.mint(await convertToCurrencyDecimals(dai.address, '1000')); + + //approve protocol to access depositor wallet + await dai.approve(core.address, APPROVAL_AMOUNT_LENDING_POOL_CORE); + + const userReserveDataBefore = await pool.getUserReserveData(dai.address, borrower.address); + + const daiReserveDataBefore = await pool.getReserveData(dai.address); + const ethReserveDataBefore = await pool.getReserveData(MOCK_ETH_ADDRESS); + + const amountToLiquidate = new BigNumber(userReserveDataBefore.currentBorrowBalance.toString()) + .div(2) + .toFixed(0); + + await pool.liquidationCall( + MOCK_ETH_ADDRESS, + dai.address, + borrower.address, + amountToLiquidate, + true + ); + + const userReserveDataAfter = await pool.getUserReserveData(dai.address, borrower.address); + + const userGlobalDataAfter = await pool.getUserAccountData(borrower.address); + + const daiReserveDataAfter = await pool.getReserveData(dai.address); + const ethReserveDataAfter = await pool.getReserveData(MOCK_ETH_ADDRESS); + + const feeAddress = await addressesProvider.getTokenDistributor(); + + const feeAddressBalance = await BRE.ethers.provider.getBalance(feeAddress); + + const collateralPrice = (await oracle.getAssetPrice(MOCK_ETH_ADDRESS)).toString(); + const principalPrice = (await oracle.getAssetPrice(dai.address)).toString(); + + const collateralDecimals = (await core.getReserveDecimals(MOCK_ETH_ADDRESS)).toString(); + const principalDecimals = (await core.getReserveDecimals(dai.address)).toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice) + .times(new BigNumber(amountToLiquidate).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals))) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedFeeLiquidated = new BigNumber(principalPrice) + .times(new BigNumber(userReserveDataBefore.originationFee.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals))) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + expect(userGlobalDataAfter.healthFactor).to.be.bignumber.gt( + oneEther.toFixed(0), + 'Invalid health factor' + ); + + expect(userReserveDataAfter.originationFee).to.be.bignumber.eq( + '0', + 'Origination fee should be repaid' + ); + + expect(feeAddressBalance).to.be.bignumber.gt('0'); + + expect(userReserveDataAfter.principalBorrowBalance).to.be.bignumber.almostEqual( + new BigNumber(userReserveDataBefore.currentBorrowBalance.toString()) + .minus(amountToLiquidate) + .toFixed(0), + 'Invalid user borrow balance after liquidation' + ); + + expect(daiReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual( + new BigNumber(daiReserveDataBefore.availableLiquidity.toString()) + .plus(amountToLiquidate) + .toFixed(0), + 'Invalid principal available liquidity' + ); + + expect(ethReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual( + new BigNumber(ethReserveDataBefore.availableLiquidity.toString()) + .minus(expectedFeeLiquidated) + .toFixed(0), + 'Invalid collateral available liquidity' + ); + }); + + it( + 'User 3 deposits 1000 USDC, user 4 1 ETH,' + + ' user 4 borrows - drops HF, liquidates the borrow', + async () => { + const {users, core, pool, usdc, oracle, addressesProvider} = testEnv; + const depositor = users[3]; + const borrower = users[4]; + //mints USDC to depositor + await usdc + .connect(depositor.signer) + .mint(await convertToCurrencyDecimals(usdc.address, '1000')); + + //approve protocol to access depositor wallet + await usdc.connect(depositor.signer).approve(core.address, APPROVAL_AMOUNT_LENDING_POOL_CORE); + + //user 3 deposits 1000 USDC + const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); + + await pool.connect(depositor.signer).deposit(usdc.address, amountUSDCtoDeposit, '0'); + + //user 4 deposits 1 ETH + const amountETHtoDeposit = await convertToCurrencyDecimals(MOCK_ETH_ADDRESS, '1'); + + await pool.connect(borrower.signer).deposit(MOCK_ETH_ADDRESS, amountETHtoDeposit, '0', { + value: amountETHtoDeposit, + }); + + //user 4 borrows + const userGlobalData = await pool.getUserAccountData(borrower.address); + + const usdcPrice = await oracle.getAssetPrice(usdc.address); + + const amountUSDCToBorrow = await convertToCurrencyDecimals( + usdc.address, + new BigNumber(userGlobalData.availableBorrowsETH.toString()) + .div(usdcPrice.toString()) + .multipliedBy(0.95) + .toFixed(0) + ); + + await pool + .connect(borrower.signer) + .borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0'); + + //drops HF below 1 + + await oracle.setAssetPrice( + usdc.address, + new BigNumber(usdcPrice.toString()).multipliedBy(1.2).toFixed(0) + ); + + //mints dai to the liquidator + + await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000')); + + //approve protocol to access depositor wallet + await usdc.approve(core.address, APPROVAL_AMOUNT_LENDING_POOL_CORE); + + const userReserveDataBefore = await pool.getUserReserveData(usdc.address, borrower.address); + + const usdcReserveDataBefore = await pool.getReserveData(usdc.address); + const ethReserveDataBefore = await pool.getReserveData(MOCK_ETH_ADDRESS); + + const amountToLiquidate = new BigNumber(userReserveDataBefore.currentBorrowBalance.toString()) + .div(2) + .toFixed(0); + + await pool.liquidationCall( + MOCK_ETH_ADDRESS, + usdc.address, + borrower.address, + amountToLiquidate, + true + ); + + const userReserveDataAfter = await pool.getUserReserveData(usdc.address, borrower.address); + + const userGlobalDataAfter = await pool.getUserAccountData(borrower.address); + + const usdcReserveDataAfter = await pool.getReserveData(usdc.address); + const ethReserveDataAfter = await pool.getReserveData(MOCK_ETH_ADDRESS); + + const feeAddress = await addressesProvider.getTokenDistributor(); + + const feeAddressBalance = await BRE.ethers.provider.getBalance(feeAddress); + + const collateralPrice = (await oracle.getAssetPrice(MOCK_ETH_ADDRESS)).toString(); + const principalPrice = (await oracle.getAssetPrice(usdc.address)).toString(); + + const collateralDecimals = (await core.getReserveDecimals(MOCK_ETH_ADDRESS)).toString(); + const principalDecimals = (await core.getReserveDecimals(usdc.address)).toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice) + .times(new BigNumber(amountToLiquidate).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals))) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedFeeLiquidated = new BigNumber(principalPrice) + .times(new BigNumber(userReserveDataBefore.originationFee.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals))) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + expect(userGlobalDataAfter.healthFactor).to.be.bignumber.gt( + oneEther.toFixed(0), + 'Invalid health factor' + ); + + expect(userReserveDataAfter.originationFee).to.be.bignumber.eq( + '0', + 'Origination fee should be repaid' + ); + + expect(feeAddressBalance).to.be.bignumber.gt('0'); + + expect(userReserveDataAfter.principalBorrowBalance).to.be.bignumber.almostEqual( + new BigNumber(userReserveDataBefore.currentBorrowBalance.toString()) + .minus(amountToLiquidate) + .toFixed(0), + 'Invalid user borrow balance after liquidation' + ); + + expect(usdcReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual( + new BigNumber(usdcReserveDataBefore.availableLiquidity.toString()) + .plus(amountToLiquidate) + .toFixed(0), + 'Invalid principal available liquidity' + ); + + expect(ethReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual( + new BigNumber(ethReserveDataBefore.availableLiquidity.toString()) + .minus(expectedFeeLiquidated) + .toFixed(0), + 'Invalid collateral available liquidity' + ); + } + ); +});