test: multiple flashoborrow asset, test extensively fees to reserve, fees to protocol

This commit is contained in:
Hadrien Charlanes 2021-06-21 08:13:20 +02:00
parent 6a69a00372
commit 7e6565fed4
3 changed files with 168 additions and 66 deletions

View File

@ -27,12 +27,24 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
LP_BORROW_ALLOWANCE_NOT_ENOUGH, LP_BORROW_ALLOWANCE_NOT_ENOUGH,
} = ProtocolErrors; } = ProtocolErrors;
const TOTAL_PREMIUM = 9;
const PREMIUM_TO_PROTOCOL = 3;
const PREMIUM_TO_LP = TOTAL_PREMIUM - PREMIUM_TO_PROTOCOL;
before(async () => { before(async () => {
_mockFlashLoanReceiver = await getMockFlashLoanReceiver(); _mockFlashLoanReceiver = await getMockFlashLoanReceiver();
}); });
it('Configurator sets total premium = 9 bps, premium to protocol = 3 bps', async () => {
const { configurator, pool } = testEnv;
await configurator.updateFlashloanPremiumTotal(TOTAL_PREMIUM);
await configurator.updateFlashloanPremiumToProtocol(PREMIUM_TO_PROTOCOL);
expect(await pool.FLASHLOAN_PREMIUM_TOTAL()).to.be.equal(TOTAL_PREMIUM);
expect(await pool.FLASHLOAN_PREMIUM_TO_PROTOCOL()).to.be.equal(PREMIUM_TO_PROTOCOL);
});
it('Deposits WETH into the reserve', async () => { it('Deposits WETH into the reserve', async () => {
const { pool, weth, aave } = testEnv; const { pool, weth, aave, dai } = testEnv;
const userAddress = await pool.signer.getAddress(); const userAddress = await pool.signer.getAddress();
const amountToDeposit = ethers.utils.parseEther('1'); const amountToDeposit = ethers.utils.parseEther('1');
@ -47,44 +59,102 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await aave.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); await aave.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.deposit(aave.address, amountToDeposit, userAddress, '0'); await pool.deposit(aave.address, amountToDeposit, userAddress, '0');
await dai.mint(amountToDeposit);
await dai.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.deposit(dai.address, amountToDeposit, userAddress, '0');
}); });
it('Takes WETH flash loan with mode = 0, returns the funds correctly', async () => { it('Takes WETH + Dai flash loan with mode = 0, returns the funds correctly', async () => {
const { pool, helpersContract, weth, aWETH } = testEnv; const { pool, helpersContract, weth, aWETH, dai, aDai } = testEnv;
const flashBorrowedAmount = ethers.utils.parseEther('0.8'); const wethFlashBorrowedAmount = ethers.utils.parseEther('0.8');
const fees = new BigNumber(flashBorrowedAmount.mul(9).div(10000).toString()); const daiFlashBorrowedAmount = ethers.utils.parseEther('0.3');
const wethTotalFees = new BigNumber(
wethFlashBorrowedAmount.mul(TOTAL_PREMIUM).div(10000).toString()
);
const wethFeesToProtocol = wethFlashBorrowedAmount.mul(PREMIUM_TO_PROTOCOL).div(10000);
const wethFeesToLp = wethFlashBorrowedAmount.mul(PREMIUM_TO_LP).div(10000);
const daiTotalFees = new BigNumber(
daiFlashBorrowedAmount.mul(TOTAL_PREMIUM).div(10000).toString()
);
const daiFeesToProtocol = daiFlashBorrowedAmount.mul(PREMIUM_TO_PROTOCOL).div(10000);
const daiFeesToLp = daiFlashBorrowedAmount.mul(PREMIUM_TO_LP).div(10000);
let reserveData = await helpersContract.getReserveData(weth.address); const wethLiquidityIndexAdded = wethFeesToLp
.mul(ethers.BigNumber.from(10).pow(27))
.div((await aWETH.totalSupply()).toString());
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString()) const daiLiquidityIndexAdded = daiFeesToLp
.plus(reserveData.totalStableDebt.toString()) .mul(ethers.BigNumber.from(10).pow(27))
.plus(reserveData.totalVariableDebt.toString()); .div((await aDai.totalSupply()).toString());
let wethReserveData = await helpersContract.getReserveData(weth.address);
let daiReserveData = await helpersContract.getReserveData(dai.address);
const wethLiquidityIndexBefore = wethReserveData.liquidityIndex;
const daiLiquidityIndexBefore = daiReserveData.liquidityIndex;
const wethTotalLiquidityBefore = new BigNumber(wethReserveData.availableLiquidity.toString())
.plus(wethReserveData.totalStableDebt.toString())
.plus(wethReserveData.totalVariableDebt.toString());
const daiTotalLiquidityBefore = new BigNumber(daiReserveData.availableLiquidity.toString())
.plus(daiReserveData.totalStableDebt.toString())
.plus(daiReserveData.totalVariableDebt.toString());
const wethReservesBefore = await aWETH.balanceOf(await aWETH.RESERVE_TREASURY_ADDRESS());
const daiReservesBefore = await aDai.balanceOf(await aDai.RESERVE_TREASURY_ADDRESS());
await pool.flashLoan( await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
[weth.address], [weth.address, dai.address],
[flashBorrowedAmount], [wethFlashBorrowedAmount, daiFlashBorrowedAmount],
[0], [0, 0],
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
'0x10', '0x10',
'0' '0'
); );
await pool.mintToTreasury([weth.address]); await pool.mintToTreasury([weth.address, dai.address]);
reserveData = await helpersContract.getReserveData(weth.address); wethReserveData = await helpersContract.getReserveData(weth.address);
daiReserveData = await helpersContract.getReserveData(dai.address);
const currentLiquidityRate = reserveData.liquidityRate; const wethCurrentLiquidityRate = wethReserveData.liquidityRate;
const currentLiquidityIndex = reserveData.liquidityIndex; const wethCurrentLiquidityIndex = wethReserveData.liquidityIndex;
const daiCurrentLiquidityRate = daiReserveData.liquidityRate;
const daiCurrentLiquidityIndex = daiReserveData.liquidityIndex;
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString()) const wethTotalLiquidityAfter = new BigNumber(wethReserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString()) .plus(wethReserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString()); .plus(wethReserveData.totalVariableDebt.toString());
expect(totalLiquidityBefore.plus(fees).toString()).to.be.equal(totalLiquidityAfter.toString()); const daiTotalLiquidityAfter = new BigNumber(daiReserveData.availableLiquidity.toString())
expect(currentLiquidityRate.toString()).to.be.equal('0'); .plus(daiReserveData.totalStableDebt.toString())
expect(currentLiquidityIndex.toString()).to.be.equal('1000720000000000000000000000'); .plus(daiReserveData.totalVariableDebt.toString());
const wethReservesAfter = await aWETH.balanceOf(await aWETH.RESERVE_TREASURY_ADDRESS());
const daiReservesAfter = await aDai.balanceOf(await aDai.RESERVE_TREASURY_ADDRESS());
expect(wethTotalLiquidityBefore.plus(wethTotalFees).toString()).to.be.equal(
wethTotalLiquidityAfter.toString()
);
expect(wethCurrentLiquidityRate.toString()).to.be.equal('0');
expect(wethCurrentLiquidityIndex.toString()).to.be.equal(
wethLiquidityIndexBefore.add(wethLiquidityIndexAdded.toString()).toString()
);
expect(wethReservesAfter).to.be.equal(wethReservesBefore.add(wethFeesToProtocol));
expect(daiTotalLiquidityBefore.plus(daiTotalFees).toString()).to.be.equal(
daiTotalLiquidityAfter.toString()
);
expect(daiCurrentLiquidityRate.toString()).to.be.equal('0');
expect(daiCurrentLiquidityIndex.toString()).to.be.equal(
daiLiquidityIndexBefore.add(daiLiquidityIndexAdded.toString()).toString()
);
expect(daiReservesAfter).to.be.equal(daiReservesBefore.add(daiFeesToProtocol));
}); });
it('Takes an authorized AAVE flash loan with mode = 0, returns the funds correctly', async () => { it('Takes an authorized AAVE flash loan with mode = 0, returns the funds correctly', async () => {
const { const {
@ -97,7 +167,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await configurator.authorizeFlashBorrower(authorizedUser.address); await configurator.authorizeFlashBorrower(authorizedUser.address);
const flashBorrowedAmount = ethers.utils.parseEther('0.8'); const flashBorrowedAmount = ethers.utils.parseEther('0.8');
const fees = new BigNumber(0); const totalFees = new BigNumber(0);
let reserveData = await helpersContract.getReserveData(aave.address); let reserveData = await helpersContract.getReserveData(aave.address);
@ -119,33 +189,43 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool.mintToTreasury([aave.address]); await pool.mintToTreasury([aave.address]);
ethers.utils.parseUnits('10000');
reserveData = await helpersContract.getReserveData(aave.address); reserveData = await helpersContract.getReserveData(aave.address);
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString()) const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString()) .plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString()); .plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidityBefore.plus(fees).toString()).to.be.equal(totalLiquidityAfter.toString()); expect(totalLiquidityBefore.plus(totalFees).toString()).to.be.equal(
totalLiquidityAfter.toString()
);
}); });
it('Takes an ETH flashloan with mode = 0 as big as the available liquidity', async () => { it('Takes an ETH flashloan with mode = 0 as big as the available liquidity', async () => {
const { pool, helpersContract, weth } = testEnv; const { pool, helpersContract, weth, aWETH } = testEnv;
let reserveData = await helpersContract.getReserveData(weth.address); let reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString()) const totalLiquidityBefore = reserveData.availableLiquidity
.plus(reserveData.totalStableDebt.toString()) .add(reserveData.totalStableDebt)
.plus(reserveData.totalVariableDebt.toString()); .add(reserveData.totalVariableDebt);
const flashBorrowedAmount = totalLiquidityBefore.toString(); const flashBorrowedAmount = totalLiquidityBefore;
const fees = new BigNumber(flashBorrowedAmount).multipliedBy(9).dividedBy(10000).toString(); const totalFees = new BigNumber(flashBorrowedAmount.mul(TOTAL_PREMIUM).div(10000).toString());
const feesToProtocol = flashBorrowedAmount.mul(PREMIUM_TO_PROTOCOL).div(10000);
const feesToLp = flashBorrowedAmount.mul(PREMIUM_TO_LP).div(10000);
const liquidityIndexBefore = reserveData.liquidityIndex;
const liquidityIndexAdded = feesToLp
.mul(ethers.BigNumber.from(10).pow(27))
.div((await aWETH.totalSupply()).toString())
.mul(liquidityIndexBefore)
.div(ethers.BigNumber.from(10).pow(27));
const reservesBefore = await aWETH.balanceOf(await aWETH.RESERVE_TREASURY_ADDRESS());
const txResult = await pool.flashLoan( const txResult = await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
[weth.address], [weth.address],
[totalLiquidityBefore.toString()], [flashBorrowedAmount],
[0], [0],
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
'0x10', '0x10',
@ -156,22 +236,25 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
reserveData = await helpersContract.getReserveData(weth.address); reserveData = await helpersContract.getReserveData(weth.address);
const currentLiquidityRate = reserveData.liquidityRate;
const currentLiquidityIndex = reserveData.liquidityIndex;
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString()) const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString()) .plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString()); .plus(reserveData.totalVariableDebt.toString());
const currentLiqudityRate = reserveData.liquidityRate; const reservesAfter = await aWETH.balanceOf(await aWETH.RESERVE_TREASURY_ADDRESS());
const currentLiquidityIndex = reserveData.liquidityIndex; expect(new BigNumber(totalLiquidityBefore.toString()).plus(totalFees).toString()).to.be.equal(
totalLiquidityAfter.toString()
const totalLiquidity = new BigNumber(reserveData.availableLiquidity.toString()) );
.plus(reserveData.totalStableDebt.toString()) expect(currentLiquidityRate.toString()).to.be.equal('0');
.plus(reserveData.totalVariableDebt.toString()); expect(currentLiquidityIndex.toString()).to.be.equal(
liquidityIndexBefore.add(liquidityIndexAdded.toString()).toString()
expect(totalLiquidity.toString()).to.be.equal('1001620648000000000'); );
expect(currentLiqudityRate.toString()).to.be.equal('0'); expect(
expect(currentLiquidityIndex.toString()).to.be.equal('1001620648000000000000000000'); reservesAfter.sub(feesToProtocol).mul(liquidityIndexBefore).div(currentLiquidityIndex)
).to.be.equal(reservesBefore);
}); });
it('Takes WETH flashloan, does not return the funds with mode = 0. (revert expected)', async () => { it('Takes WETH flashloan, does not return the funds with mode = 0. (revert expected)', async () => {
const { pool, weth, users } = testEnv; const { pool, weth, users } = testEnv;
const caller = users[1]; const caller = users[1];
@ -337,46 +420,59 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
}); });
it('Takes out a 500 USDC flashloan, returns the funds correctly', async () => { it('Takes out a 500 USDC flashloan, returns the funds correctly', async () => {
const { usdc, pool, helpersContract, deployer: depositor } = testEnv; const { usdc, aUsdc, pool, helpersContract, deployer: depositor } = testEnv;
await _mockFlashLoanReceiver.setFailExecutionTransfer(false); await _mockFlashLoanReceiver.setFailExecutionTransfer(false);
const reserveDataBefore = await helpersContract.getReserveData(usdc.address); const flashBorrowedAmount = await convertToCurrencyDecimals(usdc.address, '500');
const totalFees = new BigNumber(flashBorrowedAmount.mul(TOTAL_PREMIUM).div(10000).toString());
const feesToProtocol = flashBorrowedAmount.mul(PREMIUM_TO_PROTOCOL).div(10000);
const feesToLp = flashBorrowedAmount.mul(PREMIUM_TO_LP).div(10000);
const liquidityIndexAdded = feesToLp
.mul(ethers.BigNumber.from(10).pow(27))
.div((await aUsdc.totalSupply()).toString());
const flashloanAmount = await convertToCurrencyDecimals(usdc.address, '500'); let reserveData = await helpersContract.getReserveData(usdc.address);
const liquidityIndexBefore = reserveData.liquidityIndex;
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
const reservesBefore = await aUsdc.balanceOf(await aUsdc.RESERVE_TREASURY_ADDRESS());
await pool.flashLoan( await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
[usdc.address], [usdc.address],
[flashloanAmount], [flashBorrowedAmount],
[0], [0],
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
'0x10', '0x10',
'0' '0'
); );
const reserveDataAfter = helpersContract.getReserveData(usdc.address); await pool.mintToTreasury([usdc.address]);
const reserveData = await helpersContract.getReserveData(usdc.address); reserveData = await helpersContract.getReserveData(usdc.address);
const userData = await helpersContract.getUserReserveData(usdc.address, depositor.address);
const totalLiquidity = reserveData.availableLiquidity const currentLiquidityRate = reserveData.liquidityRate;
.add(reserveData.totalStableDebt) const currentLiquidityIndex = reserveData.liquidityIndex;
.add(reserveData.totalVariableDebt)
.toString();
const currentLiqudityRate = reserveData.liquidityRate.toString();
const currentLiquidityIndex = reserveData.liquidityIndex.toString();
const currentUserBalance = userData.currentATokenBalance.toString();
const expectedLiquidity = await convertToCurrencyDecimals(usdc.address, '1000.450'); const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidity).to.be.equal(expectedLiquidity, 'Invalid total liquidity'); const reservesAfter = await aUsdc.balanceOf(await aUsdc.RESERVE_TREASURY_ADDRESS());
expect(currentLiqudityRate).to.be.equal('0', 'Invalid liquidity rate');
expect(currentLiquidityIndex).to.be.equal( expect(totalLiquidityBefore.plus(totalFees).toString()).to.be.equal(
new BigNumber('1.00045').multipliedBy(oneRay).toFixed(), totalLiquidityAfter.toString()
'Invalid liquidity index'
); );
expect(currentUserBalance.toString()).to.be.equal(expectedLiquidity, 'Invalid user balance'); expect(currentLiquidityRate.toString()).to.be.equal('0');
expect(currentLiquidityIndex.toString()).to.be.equal(
liquidityIndexBefore.add(liquidityIndexAdded.toString()).toString()
);
expect(reservesAfter).to.be.equal(reservesBefore.add(feesToProtocol));
}); });
it('Takes out a 500 USDC flashloan with mode = 0, does not return the funds. (revert expected)', async () => { it('Takes out a 500 USDC flashloan with mode = 0, does not return the funds. (revert expected)', async () => {

View File

@ -63,6 +63,7 @@ export interface TestEnv {
aWETH: AToken; aWETH: AToken;
dai: MintableERC20; dai: MintableERC20;
aDai: AToken; aDai: AToken;
aUsdc: AToken;
usdc: MintableERC20; usdc: MintableERC20;
aave: MintableERC20; aave: MintableERC20;
addressesProvider: LendingPoolAddressesProvider; addressesProvider: LendingPoolAddressesProvider;
@ -92,6 +93,7 @@ const testEnv: TestEnv = {
aWETH: {} as AToken, aWETH: {} as AToken,
dai: {} as MintableERC20, dai: {} as MintableERC20,
aDai: {} as AToken, aDai: {} as AToken,
aUsdc: {} as AToken,
usdc: {} as MintableERC20, usdc: {} as MintableERC20,
aave: {} as MintableERC20, aave: {} as MintableERC20,
addressesProvider: {} as LendingPoolAddressesProvider, addressesProvider: {} as LendingPoolAddressesProvider,
@ -138,6 +140,7 @@ export async function initializeMakeSuite() {
const allTokens = await testEnv.helpersContract.getAllATokens(); const allTokens = await testEnv.helpersContract.getAllATokens();
const aDaiAddress = allTokens.find((aToken) => aToken.symbol === 'aDAI')?.tokenAddress; const aDaiAddress = allTokens.find((aToken) => aToken.symbol === 'aDAI')?.tokenAddress;
const aUsdcAddress = allTokens.find((aToken) => aToken.symbol === 'aUSDC')?.tokenAddress;
const aWEthAddress = allTokens.find((aToken) => aToken.symbol === 'aWETH')?.tokenAddress; const aWEthAddress = allTokens.find((aToken) => aToken.symbol === 'aWETH')?.tokenAddress;
@ -156,6 +159,7 @@ export async function initializeMakeSuite() {
} }
testEnv.aDai = await getAToken(aDaiAddress); testEnv.aDai = await getAToken(aDaiAddress);
testEnv.aUsdc = await getAToken(aUsdcAddress);
testEnv.aWETH = await getAToken(aWEthAddress); testEnv.aWETH = await getAToken(aWEthAddress);
testEnv.dai = await getMintableERC20(daiAddress); testEnv.dai = await getMintableERC20(daiAddress);

View File

@ -47,10 +47,12 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => { it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => {
const { pool, helpersContract, weth } = testEnv; const { pool, helpersContract, weth } = testEnv;
const borrowedAmount = ethers.utils.parseEther('0.8');
await pool.flashLoan( await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
[weth.address], [weth.address],
[ethers.utils.parseEther('0.8')], [borrowedAmount],
[0], [0],
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
'0x10', '0x10',