initial refactoring of liquidation-atoken.spec

This commit is contained in:
andyk 2020-06-18 17:36:37 +03:00
parent 9e76bcf765
commit d1f21118c1
4 changed files with 419 additions and 545 deletions

View File

@ -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;

View File

@ -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");

View File

@ -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 () => {

View File

@ -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'
);
}
);
});