diff --git a/package.json b/package.json index cf887ed4..575a1dde 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "test-scenarios": "npm run compile && npx hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/scenario.spec.ts", "test-subgraph:scenarios": "npm run compile && hardhat --network hardhatevm_docker test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/subgraph-scenarios.spec.ts", "test:main:check-list": "npm run compile && FORK=main TS_NODE_TRANSPILE_ONLY=1 hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/mainnet/check-list.spec.ts", + "test:aave": "hardhat test test-suites/test-aave/__setup.spec.ts", + "test:amm": "hardhat test test-suites/test-amm/__setup.spec.ts", "dev:coverage": "buidler compile --force && buidler coverage --network coverage", "aave:evm:dev:migration": "npm run compile && hardhat aave:dev", "aave:docker:full:migration": "npm run compile && npm run hardhat:docker -- aave:mainnet --skip-registry", diff --git a/test-suites/test-aave/reserve-pause.spec.ts b/test-suites/test-aave/reserve-pause.spec.ts new file mode 100644 index 00000000..f6a57d0c --- /dev/null +++ b/test-suites/test-aave/reserve-pause.spec.ts @@ -0,0 +1,330 @@ +import { makeSuite, TestEnv } from './helpers/make-suite'; +import { ProtocolErrors, RateMode } from '../../helpers/types'; +import { APPROVAL_AMOUNT_LENDING_POOL, oneEther } from '../../helpers/constants'; +import { convertToCurrencyDecimals } from '../../helpers/contracts-helpers'; +import { parseEther, parseUnits } from 'ethers/lib/utils'; +import { BigNumber } from 'bignumber.js'; +import { MockFlashLoanReceiver } from '../../types/MockFlashLoanReceiver'; +import { getMockFlashLoanReceiver } from '../../helpers/contracts-getters'; + +const { expect } = require('chai'); + +makeSuite('Pausable Pool', (testEnv: TestEnv) => { + let _mockFlashLoanReceiver = {} as MockFlashLoanReceiver; + + const { + VL_RESERVE_PAUSED, + INVALID_FROM_BALANCE_AFTER_TRANSFER, + INVALID_TO_BALANCE_AFTER_TRANSFER, + } = ProtocolErrors; + + before(async () => { + _mockFlashLoanReceiver = await getMockFlashLoanReceiver(); + }); + + it('User 0 deposits 1000 DAI. Configurator pauses pool. Transfers to user 1 reverts. Configurator unpauses the network and next transfer succees', async () => { + const { users, pool, dai, aDai, configurator } = testEnv; + + const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await dai.connect(users[0].signer).mint(amountDAItoDeposit); + + // user 0 deposits 1000 DAI + await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool + .connect(users[0].signer) + .deposit(dai.address, amountDAItoDeposit, users[0].address, '0'); + + const user0Balance = await aDai.balanceOf(users[0].address); + const user1Balance = await aDai.balanceOf(users[1].address); + + // Configurator pauses the pool + await configurator.connect(users[1].signer).pauseReserve(dai.address); + + // User 0 tries the transfer to User 1 + await expect( + aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit) + ).to.revertedWith(VL_RESERVE_PAUSED); + + const pausedFromBalance = await aDai.balanceOf(users[0].address); + const pausedToBalance = await aDai.balanceOf(users[1].address); + + expect(pausedFromBalance).to.be.equal( + user0Balance.toString(), + INVALID_TO_BALANCE_AFTER_TRANSFER + ); + expect(pausedToBalance.toString()).to.be.equal( + user1Balance.toString(), + INVALID_FROM_BALANCE_AFTER_TRANSFER + ); + + // Configurator unpauses the pool + await configurator.connect(users[1].signer).unpauseReserve(dai.address); + + // User 0 succeeds transfer to User 1 + await aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit); + + const fromBalance = await aDai.balanceOf(users[0].address); + const toBalance = await aDai.balanceOf(users[1].address); + + expect(fromBalance.toString()).to.be.equal( + user0Balance.sub(amountDAItoDeposit), + INVALID_FROM_BALANCE_AFTER_TRANSFER + ); + expect(toBalance.toString()).to.be.equal( + user1Balance.add(amountDAItoDeposit), + INVALID_TO_BALANCE_AFTER_TRANSFER + ); + }); + + it('Deposit', async () => { + const { users, pool, dai, aDai, configurator } = testEnv; + + const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await dai.connect(users[0].signer).mint(amountDAItoDeposit); + + // user 0 deposits 1000 DAI + await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + // Configurator pauses the pool + await configurator.connect(users[1].signer).pauseReserve(dai.address); + await expect( + pool.connect(users[0].signer).deposit(dai.address, amountDAItoDeposit, users[0].address, '0') + ).to.revertedWith(VL_RESERVE_PAUSED); + + // Configurator unpauses the pool + await configurator.connect(users[1].signer).unpauseReserve(dai.address); + }); + + it('Withdraw', async () => { + const { users, pool, dai, aDai, configurator } = testEnv; + + const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await dai.connect(users[0].signer).mint(amountDAItoDeposit); + + // user 0 deposits 1000 DAI + await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool + .connect(users[0].signer) + .deposit(dai.address, amountDAItoDeposit, users[0].address, '0'); + + // Configurator pauses the pool + await configurator.connect(users[1].signer).pauseReserve(dai.address); + + // user tries to burn + await expect( + pool.connect(users[0].signer).withdraw(dai.address, amountDAItoDeposit, users[0].address) + ).to.revertedWith(VL_RESERVE_PAUSED); + + // Configurator unpauses the pool + await configurator.connect(users[1].signer).unpauseReserve(dai.address); + }); + + it('Borrow', async () => { + const { pool, dai, users, configurator } = testEnv; + + const user = users[1]; + // Pause the pool + await configurator.connect(users[1].signer).pauseReserve(dai.address); + + // Try to execute liquidation + await expect( + pool.connect(user.signer).borrow(dai.address, '1', '1', '0', user.address) + ).revertedWith(VL_RESERVE_PAUSED); + + // Unpause the pool + await configurator.connect(users[1].signer).unpauseReserve(dai.address); + }); + + it('Repay', async () => { + const { pool, dai, users, configurator } = testEnv; + + const user = users[1]; + // Pause the pool + await configurator.connect(users[1].signer).pauseReserve(dai.address); + + // Try to execute liquidation + await expect(pool.connect(user.signer).repay(dai.address, '1', '1', user.address)).revertedWith( + VL_RESERVE_PAUSED + ); + + // Unpause the pool + await configurator.connect(users[1].signer).unpauseReserve(dai.address); + }); + + it('Flash loan', async () => { + const { dai, pool, weth, users, configurator } = testEnv; + + const caller = users[3]; + + const flashAmount = parseEther('0.8'); + + await _mockFlashLoanReceiver.setFailExecutionTransfer(true); + + // Pause pool + await configurator.connect(users[1].signer).pauseReserve(weth.address); + + await expect( + pool + .connect(caller.signer) + .flashLoan( + _mockFlashLoanReceiver.address, + [weth.address], + [flashAmount], + [1], + caller.address, + '0x10', + '0' + ) + ).revertedWith(VL_RESERVE_PAUSED); + + // Unpause pool + await configurator.connect(users[1].signer).unpauseReserve(weth.address); + }); + + it('Liquidation call', async () => { + const { users, pool, usdc, oracle, weth, configurator, helpersContract } = 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(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + //user 3 deposits 1000 USDC + const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); + + await pool + .connect(depositor.signer) + .deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0'); + + //user 4 deposits 1 ETH + const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); + + //mints WETH to borrower + await weth.connect(borrower.signer).mint(amountETHtoDeposit); + + //approve protocol to access 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 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.9502) + .toFixed(0) + ); + + await pool + .connect(borrower.signer) + .borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address); + + // 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')); + await usdc.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + const userReserveDataBefore = await helpersContract.getUserReserveData( + usdc.address, + borrower.address + ); + + const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString()) + .multipliedBy(0.5) + .toFixed(0); + + // Pause pool + await configurator.connect(users[1].signer).pauseReserve(usdc.address); + + // Do liquidation + await expect( + pool.liquidationCall(weth.address, usdc.address, borrower.address, amountToLiquidate, true) + ).revertedWith(VL_RESERVE_PAUSED); + + // Unpause pool + await configurator.connect(users[1].signer).unpauseReserve(usdc.address); + }); + + it('SwapBorrowRateMode', async () => { + const { pool, weth, dai, usdc, users, configurator } = testEnv; + const user = users[1]; + const amountWETHToDeposit = parseEther('10'); + const amountDAIToDeposit = parseEther('120'); + const amountToBorrow = parseUnits('65', 6); + + await weth.connect(user.signer).mint(amountWETHToDeposit); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, user.address, '0'); + + await dai.connect(user.signer).mint(amountDAIToDeposit); + await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0'); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address); + + // Pause pool + await configurator.connect(users[1].signer).pauseReserve(usdc.address); + + // Try to repay + await expect( + pool.connect(user.signer).swapBorrowRateMode(usdc.address, RateMode.Stable) + ).revertedWith(VL_RESERVE_PAUSED); + + // Unpause pool + await configurator.connect(users[1].signer).unpauseReserve(usdc.address); + }); + + it('RebalanceStableBorrowRate', async () => { + const { pool, dai, users, configurator } = testEnv; + const user = users[1]; + // Pause pool + await configurator.connect(users[1].signer).pauseReserve(dai.address); + + await expect( + pool.connect(user.signer).rebalanceStableBorrowRate(dai.address, user.address) + ).revertedWith(VL_RESERVE_PAUSED); + + // Unpause pool + await configurator.connect(users[1].signer).unpauseReserve(dai.address); + }); + + it('setUserUseReserveAsCollateral', async () => { + const { pool, weth, users, configurator } = testEnv; + const user = users[1]; + + const amountWETHToDeposit = parseEther('1'); + await weth.connect(user.signer).mint(amountWETHToDeposit); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, user.address, '0'); + + // Pause pool + await configurator.connect(users[1].signer).pauseReserve(weth.address); + + await expect( + pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false) + ).revertedWith(VL_RESERVE_PAUSED); + + // Unpause pool + await configurator.connect(users[1].signer).unpauseReserve(weth.address); + }); +});