From fe8cecf099997bc04a71a61a11c200e47270a87a Mon Sep 17 00:00:00 2001 From: The-3D Date: Mon, 8 Aug 2022 19:31:20 +0200 Subject: [PATCH 1/3] feat: removed borrow/repay in the same block check --- .../protocol/libraries/helpers/Errors.sol | 1 - .../libraries/logic/ValidationLogic.sol | 14 -- helpers/types.ts | 1 - test-suites/validation-logic.spec.ts | 126 ------------------ 4 files changed, 142 deletions(-) diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 640e46322..d022f65ee 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -54,7 +54,6 @@ library Errors { string public constant HEALTH_FACTOR_NOT_BELOW_THRESHOLD = '45'; // 'Health factor is not below the threshold' string public constant COLLATERAL_CANNOT_BE_LIQUIDATED = '46'; // 'The collateral chosen cannot be liquidated' string public constant SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER = '47'; // 'User did not borrow the specified currency' - string public constant SAME_BLOCK_BORROW_REPAY = '48'; // 'Borrow and repay in same block is not allowed' string public constant INCONSISTENT_FLASHLOAN_PARAMS = '49'; // 'Inconsistent flashloan parameters' string public constant BORROW_CAP_EXCEEDED = '50'; // 'Borrow cap is exceeded' string public constant SUPPLY_CAP_EXCEEDED = '51'; // 'Supply cap is exceeded' diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index fb2eda0c1..784fc728d 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -327,20 +327,6 @@ library ValidationLogic { require(isActive, Errors.RESERVE_INACTIVE); require(!isPaused, Errors.RESERVE_PAUSED); - uint256 variableDebtPreviousIndex = IScaledBalanceToken(reserveCache.variableDebtTokenAddress) - .getPreviousIndex(onBehalfOf); - - uint40 stableRatePreviousTimestamp = IStableDebtToken(reserveCache.stableDebtTokenAddress) - .getUserLastUpdated(onBehalfOf); - - require( - (stableRatePreviousTimestamp < uint40(block.timestamp) && - interestRateMode == DataTypes.InterestRateMode.STABLE) || - (variableDebtPreviousIndex < reserveCache.nextVariableBorrowIndex && - interestRateMode == DataTypes.InterestRateMode.VARIABLE), - Errors.SAME_BLOCK_BORROW_REPAY - ); - require( (stableDebt != 0 && interestRateMode == DataTypes.InterestRateMode.STABLE) || (variableDebt != 0 && interestRateMode == DataTypes.InterestRateMode.VARIABLE), diff --git a/helpers/types.ts b/helpers/types.ts index 79cd5f3d6..5ad641560 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -118,7 +118,6 @@ export enum ProtocolErrors { HEALTH_FACTOR_NOT_BELOW_THRESHOLD = '45', // 'Health factor is not below the threshold' COLLATERAL_CANNOT_BE_LIQUIDATED = '46', // 'The collateral chosen cannot be liquidated' SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER = '47', // 'User did not borrow the specified currency' - SAME_BLOCK_BORROW_REPAY = '48', // 'Borrow and repay in same block is not allowed' INCONSISTENT_FLASHLOAN_PARAMS = '49', // 'Inconsistent flashloan parameters' BORROW_CAP_EXCEEDED = '50', // 'Borrow cap is exceeded' SUPPLY_CAP_EXCEEDED = '51', // 'Supply cap is exceeded' diff --git a/test-suites/validation-logic.spec.ts b/test-suites/validation-logic.spec.ts index 3bcbffc12..cb7b244b8 100644 --- a/test-suites/validation-logic.spec.ts +++ b/test-suites/validation-logic.spec.ts @@ -404,132 +404,6 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { ).to.be.revertedWith(RESERVE_INACTIVE); }); - it('validateRepay() when variable borrowing and repaying in same block (revert expected)', async () => { - // Same block repay - - const { pool, users, dai, aDai, usdc } = testEnv; - const user = users[0]; - - // We need some debt. - await usdc.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); - await usdc.connect(user.signer).approve(pool.address, MAX_UINT_AMOUNT); - await pool - .connect(user.signer) - .deposit(usdc.address, utils.parseEther('2000'), user.address, 0); - await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); - await dai.connect(user.signer).transfer(aDai.address, utils.parseEther('2000')); - - // Turn off automining - pretty sure that coverage is getting messed up here. - await setAutomine(false); - - // Borrow 500 dai - await pool - .connect(user.signer) - .borrow(dai.address, utils.parseEther('500'), RateMode.Variable, 0, user.address); - - // Turn on automining, but not mine a new block until next tx - await setAutomineEvm(true); - - await expect( - pool - .connect(user.signer) - .repay(dai.address, utils.parseEther('500'), RateMode.Variable, user.address) - ).to.be.revertedWith(SAME_BLOCK_BORROW_REPAY); - }); - - it('validateRepay() when variable borrowing and repaying in same block using credit delegation (revert expected)', async () => { - const { - pool, - dai, - weth, - users: [user1, user2, user3], - } = testEnv; - - // Add liquidity - await dai.connect(user3.signer)['mint(uint256)'](utils.parseUnits('1000', 18)); - await dai.connect(user3.signer).approve(pool.address, MAX_UINT_AMOUNT); - await pool - .connect(user3.signer) - .supply(dai.address, utils.parseUnits('1000', 18), user3.address, 0); - - // User1 supplies 10 WETH - await dai.connect(user1.signer)['mint(uint256)'](utils.parseUnits('100', 18)); - await dai.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); - await weth.connect(user1.signer)['mint(uint256)'](utils.parseUnits('10', 18)); - await weth.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); - await pool - .connect(user1.signer) - .supply(weth.address, utils.parseUnits('10', 18), user1.address, 0); - - const daiData = await pool.getReserveData(dai.address); - const variableDebtToken = await getVariableDebtToken(daiData.variableDebtTokenAddress); - - // User1 approves User2 to borrow 1000 DAI - expect( - await variableDebtToken - .connect(user1.signer) - .approveDelegation(user2.address, utils.parseUnits('1000', 18)) - ); - - // User2 borrows on behalf of User1 - const borrowOnBehalfAmount = utils.parseUnits('100', 18); - expect( - await pool - .connect(user2.signer) - .borrow(dai.address, borrowOnBehalfAmount, RateMode.Variable, 0, user1.address) - ); - - // Turn off automining to simulate actions in same block - await setAutomine(false); - - // User2 borrows 2 DAI on behalf of User1 - await pool - .connect(user2.signer) - .borrow(dai.address, utils.parseEther('2'), RateMode.Variable, 0, user1.address); - - // Turn on automining, but not mine a new block until next tx - await setAutomineEvm(true); - - await expect( - pool - .connect(user1.signer) - .repay(dai.address, utils.parseEther('2'), RateMode.Variable, user1.address) - ).to.be.revertedWith(SAME_BLOCK_BORROW_REPAY); - }); - - it('validateRepay() when stable borrowing and repaying in same block (revert expected)', async () => { - // Same block repay - - const { pool, users, dai, aDai, usdc } = testEnv; - const user = users[0]; - - // We need some debt. - await usdc.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); - await usdc.connect(user.signer).approve(pool.address, MAX_UINT_AMOUNT); - await pool - .connect(user.signer) - .deposit(usdc.address, utils.parseEther('2000'), user.address, 0); - await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); - await dai.connect(user.signer).transfer(aDai.address, utils.parseEther('2000')); - - // Turn off automining - pretty sure that coverage is getting messed up here. - await setAutomine(false); - - // Borrow 500 dai - await pool - .connect(user.signer) - .borrow(dai.address, utils.parseEther('500'), RateMode.Stable, 0, user.address); - - // Turn on automining, but not mine a new block until next tx - await setAutomineEvm(true); - - await expect( - pool - .connect(user.signer) - .repay(dai.address, utils.parseEther('500'), RateMode.Stable, user.address) - ).to.be.revertedWith(SAME_BLOCK_BORROW_REPAY); - }); - it('validateRepay() the variable debt when is 0 (stableDebt > 0) (revert expected)', async () => { // (stableDebt > 0 && DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.STABLE) || // (variableDebt > 0 && DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.VARIABLE), From 0028ea671af786ad38ff5e09a6c58493c4140016 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Tue, 9 Aug 2022 22:16:05 +0200 Subject: [PATCH 2/3] test: Add tests for borrow-repay in same block --- test-suites/stable-debt-token.spec.ts | 172 ++++++++++++++--- test-suites/validation-logic.spec.ts | 73 ++++---- test-suites/variable-debt-token.spec.ts | 239 +++++++++++++++++++++--- 3 files changed, 387 insertions(+), 97 deletions(-) diff --git a/test-suites/stable-debt-token.spec.ts b/test-suites/stable-debt-token.spec.ts index 584eed351..22233e36b 100644 --- a/test-suites/stable-debt-token.spec.ts +++ b/test-suites/stable-debt-token.spec.ts @@ -1,26 +1,30 @@ -import { expect } from 'chai'; -import { BigNumber, utils } from 'ethers'; -import { ProtocolErrors, RateMode } from '../helpers/types'; -import { MAX_UINT_AMOUNT, RAY, ZERO_ADDRESS } from '../helpers/constants'; -import { impersonateAccountsHardhat, setAutomine } from '../helpers/misc-utils'; -import { makeSuite, TestEnv } from './helpers/make-suite'; -import { topUpNonPayableWithEther } from './helpers/utils/funds'; -import { convertToCurrencyDecimals } from '../helpers/contracts-helpers'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { evmRevert, evmSnapshot, increaseTime, waitForTx } from '@aave/deploy-v3'; -import { StableDebtToken__factory } from '../types'; +import {expect} from 'chai'; +import {BigNumber, utils} from 'ethers'; +import {ProtocolErrors, RateMode} from '../helpers/types'; +import {MAX_UINT_AMOUNT, RAY, ZERO_ADDRESS} from '../helpers/constants'; +import {impersonateAccountsHardhat, setAutomine, setAutomineEvm} from '../helpers/misc-utils'; +import {makeSuite, TestEnv} from './helpers/make-suite'; +import {topUpNonPayableWithEther} from './helpers/utils/funds'; +import {convertToCurrencyDecimals} from '../helpers/contracts-helpers'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {evmRevert, evmSnapshot, getStableDebtToken, increaseTime, waitForTx} from '@aave/deploy-v3'; +import {StableDebtToken__factory} from '../types'; declare var hre: HardhatRuntimeEnvironment; makeSuite('StableDebtToken', (testEnv: TestEnv) => { - const { CALLER_MUST_BE_POOL, CALLER_NOT_POOL_ADMIN } = ProtocolErrors; + const {CALLER_MUST_BE_POOL, CALLER_NOT_POOL_ADMIN} = ProtocolErrors; + let snap: string; - before(async () => { + beforeEach(async () => { snap = await evmSnapshot(); }); + afterEach(async () => { + await evmRevert(snap); + }); it('Check initialization', async () => { - const { pool, weth, dai, helpersContract, users } = testEnv; + const {pool, weth, dai, helpersContract, users} = testEnv; const daiStableDebtTokenAddress = (await helpersContract.getReserveTokensAddresses(dai.address)) .stableDebtTokenAddress; const stableDebtContract = StableDebtToken__factory.connect( @@ -70,7 +74,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { }); it('Tries to mint not being the Pool (revert expected)', async () => { - const { deployer, dai, helpersContract } = testEnv; + const {deployer, dai, helpersContract} = testEnv; const daiStableDebtTokenAddress = (await helpersContract.getReserveTokensAddresses(dai.address)) .stableDebtTokenAddress; @@ -86,7 +90,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { }); it('Tries to burn not being the Pool (revert expected)', async () => { - const { deployer, dai, helpersContract } = testEnv; + const {deployer, dai, helpersContract} = testEnv; const daiStableDebtTokenAddress = (await helpersContract.getReserveTokensAddresses(dai.address)) .stableDebtTokenAddress; @@ -105,7 +109,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { }); it('Tries to transfer debt tokens (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiStableDebtTokenAddress = (await helpersContract.getReserveTokensAddresses(dai.address)) .stableDebtTokenAddress; const stableDebtContract = StableDebtToken__factory.connect( @@ -119,7 +123,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { }); it('Check Mint and Transfer events when borrowing on behalf', async () => { - const snapId = await evmSnapshot(); + // const snapId = await evmSnapshot(); const { pool, weth, @@ -191,14 +195,14 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { ); const rawTransferEvents = tx.logs.filter( - ({ topics, address }) => topics[0] === transferEventSig && address == stableDebtToken.address + ({topics, address}) => topics[0] === transferEventSig && address == stableDebtToken.address ); const transferAmount = stableDebtToken.interface.parseLog(rawTransferEvents[0]).args.value; const rawMintEvents = tx.logs.filter( - ({ topics, address }) => topics[0] === mintEventSig && address == stableDebtToken.address + ({topics, address}) => topics[0] === mintEventSig && address == stableDebtToken.address ); - const { amount: mintAmount, balanceIncrease } = stableDebtToken.interface.parseLog( + const {amount: mintAmount, balanceIncrease} = stableDebtToken.interface.parseLog( rawMintEvents[0] ).args; @@ -207,11 +211,11 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { expect(expectedDebtIncreaseUser1).to.be.eq(balanceIncrease); expect(afterDebtBalanceUser2).to.be.eq(beforeDebtBalanceUser2); - await evmRevert(snapId); + // await evmRevert(snapId); }); it('Tries to approve debt tokens (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiStableDebtTokenAddress = (await helpersContract.getReserveTokensAddresses(dai.address)) .stableDebtTokenAddress; const stableDebtContract = StableDebtToken__factory.connect( @@ -228,7 +232,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { }); it('Tries to increase allowance of debt tokens (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiStableDebtTokenAddress = (await helpersContract.getReserveTokensAddresses(dai.address)) .stableDebtTokenAddress; const stableDebtContract = StableDebtToken__factory.connect( @@ -242,7 +246,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { }); it('Tries to decrease allowance of debt tokens (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiStableDebtTokenAddress = (await helpersContract.getReserveTokensAddresses(dai.address)) .stableDebtTokenAddress; const stableDebtContract = StableDebtToken__factory.connect( @@ -256,7 +260,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { }); it('Tries to transferFrom (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiStableDebtTokenAddress = (await helpersContract.getReserveTokensAddresses(dai.address)) .stableDebtTokenAddress; const stableDebtContract = StableDebtToken__factory.connect( @@ -280,13 +284,13 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { // progress time by a year, to accrue significant debt. // then let user 2 withdraw sufficient funds such that secondTerm (userStableRate * burnAmount) >= averageRate * supply // if we do not have user 1 deposit as well, we will have issues getting past previousSupply <= amount, as amount > supply for secondTerm to be > firstTerm. - await evmRevert(snap); + // await evmRevert(snap); const rateGuess1 = BigNumber.from(RAY); const rateGuess2 = BigNumber.from(10).pow(30); const amount1 = BigNumber.from(2); const amount2 = BigNumber.from(1); - const { deployer, pool, dai, helpersContract, users } = testEnv; + const {deployer, pool, dai, helpersContract, users} = testEnv; // Impersonate the Pool await topUpNonPayableWithEther(deployer.signer, [pool.address], utils.parseEther('1')); @@ -316,8 +320,8 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { }); it('setIncentivesController() ', async () => { - const snapshot = await evmSnapshot(); - const { dai, helpersContract, poolAdmin, aclManager, deployer } = testEnv; + // const snapshot = await evmSnapshot(); + const {dai, helpersContract, poolAdmin, aclManager, deployer} = testEnv; const config = await helpersContract.getReserveTokensAddresses(dai.address); const stableDebt = StableDebtToken__factory.connect( config.stableDebtTokenAddress, @@ -330,7 +334,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { expect(await stableDebt.connect(poolAdmin.signer).setIncentivesController(ZERO_ADDRESS)); expect(await stableDebt.getIncentivesController()).to.be.eq(ZERO_ADDRESS); - await evmRevert(snapshot); + // await evmRevert(snapshot); }); it('setIncentivesController() from not pool admin (revert expected)', async () => { @@ -348,4 +352,110 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { stableDebt.connect(user.signer).setIncentivesController(ZERO_ADDRESS) ).to.be.revertedWith(CALLER_NOT_POOL_ADMIN); }); + + it('User borrows and repays in same block with zero fees', async () => { + const {pool, users, dai, aDai, usdc, stableDebtDai} = testEnv; + const user = users[0]; + + // We need some debt. + await usdc.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); + await usdc.connect(user.signer).approve(pool.address, MAX_UINT_AMOUNT); + await pool + .connect(user.signer) + .deposit(usdc.address, utils.parseEther('2000'), user.address, 0); + await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); + await dai.connect(user.signer).transfer(aDai.address, utils.parseEther('2000')); + await dai.connect(user.signer).approve(pool.address, MAX_UINT_AMOUNT); + + const userDataBefore = await pool.getUserAccountData(user.address); + expect(await stableDebtDai.balanceOf(user.address)).to.be.eq(0); + + // Turn off automining - pretty sure that coverage is getting messed up here. + await setAutomine(false); + // Borrow 500 dai + await pool + .connect(user.signer) + .borrow(dai.address, utils.parseEther('500'), RateMode.Stable, 0, user.address); + + // Turn on automining, but not mine a new block until next tx + await setAutomineEvm(true); + expect( + await pool + .connect(user.signer) + .repay(dai.address, utils.parseEther('500'), RateMode.Stable, user.address) + ); + + expect(await stableDebtDai.balanceOf(user.address)).to.be.eq(0); + expect(await dai.balanceOf(user.address)).to.be.eq(0); + expect(await dai.balanceOf(aDai.address)).to.be.eq(utils.parseEther('2000')); + + const userDataAfter = await pool.getUserAccountData(user.address); + expect(userDataBefore.totalCollateralBase).to.be.lte(userDataAfter.totalCollateralBase); + expect(userDataBefore.healthFactor).to.be.lte(userDataAfter.healthFactor); + expect(userDataBefore.totalDebtBase).to.be.eq(userDataAfter.totalDebtBase); + }); + + it('User borrows and repays in same block using credit delegation with zero fees', async () => { + const { + pool, + dai, + aDai, + weth, + users: [user1, user2, user3], + } = testEnv; + + // Add liquidity + await dai.connect(user3.signer)['mint(uint256)'](utils.parseUnits('1000', 18)); + await dai.connect(user3.signer).approve(pool.address, MAX_UINT_AMOUNT); + await pool + .connect(user3.signer) + .supply(dai.address, utils.parseUnits('1000', 18), user3.address, 0); + + // User1 supplies 10 WETH + await dai.connect(user1.signer)['mint(uint256)'](utils.parseUnits('100', 18)); + await dai.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + await weth.connect(user1.signer)['mint(uint256)'](utils.parseUnits('10', 18)); + await weth.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + await pool + .connect(user1.signer) + .supply(weth.address, utils.parseUnits('10', 18), user1.address, 0); + + const daiData = await pool.getReserveData(dai.address); + const variableDebtToken = await getStableDebtToken(daiData.variableDebtTokenAddress); + + // User1 approves User2 to borrow 1000 DAI + expect( + await variableDebtToken + .connect(user1.signer) + .approveDelegation(user2.address, utils.parseUnits('1000', 18)) + ); + + const userDataBefore = await pool.getUserAccountData(user1.address); + + // Turn off automining to simulate actions in same block + await setAutomine(false); + + // User2 borrows 2 DAI on behalf of User1 + await pool + .connect(user2.signer) + .borrow(dai.address, utils.parseEther('2'), RateMode.Stable, 0, user1.address); + + // Turn on automining, but not mine a new block until next tx + await setAutomineEvm(true); + + expect( + await pool + .connect(user1.signer) + .repay(dai.address, utils.parseEther('2'), RateMode.Stable, user1.address) + ); + + expect(await variableDebtToken.balanceOf(user1.address)).to.be.eq(0); + expect(await dai.balanceOf(user2.address)).to.be.eq(utils.parseEther('2')); + expect(await dai.balanceOf(aDai.address)).to.be.eq(utils.parseEther('1000')); + + const userDataAfter = await pool.getUserAccountData(user1.address); + expect(userDataBefore.totalCollateralBase).to.be.lte(userDataAfter.totalCollateralBase); + expect(userDataBefore.healthFactor).to.be.lte(userDataAfter.healthFactor); + expect(userDataBefore.totalDebtBase).to.be.eq(userDataAfter.totalDebtBase); + }); }); diff --git a/test-suites/validation-logic.spec.ts b/test-suites/validation-logic.spec.ts index cb7b244b8..017e0c5ce 100644 --- a/test-suites/validation-logic.spec.ts +++ b/test-suites/validation-logic.spec.ts @@ -1,14 +1,14 @@ -import { expect } from 'chai'; -import { utils, constants } from 'ethers'; -import { parseUnits } from '@ethersproject/units'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { MAX_UINT_AMOUNT } from '../helpers/constants'; -import { RateMode, ProtocolErrors } from '../helpers/types'; -import { impersonateAccountsHardhat, setAutomine, setAutomineEvm } from '../helpers/misc-utils'; -import { makeSuite, TestEnv } from './helpers/make-suite'; -import { convertToCurrencyDecimals } from '../helpers/contracts-helpers'; -import { waitForTx, evmSnapshot, evmRevert, getVariableDebtToken } from '@aave/deploy-v3'; -import { topUpNonPayableWithEther } from './helpers/utils/funds'; +import {expect} from 'chai'; +import {utils, constants} from 'ethers'; +import {parseUnits} from '@ethersproject/units'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {MAX_UINT_AMOUNT} from '../helpers/constants'; +import {RateMode, ProtocolErrors} from '../helpers/types'; +import {impersonateAccountsHardhat} from '../helpers/misc-utils'; +import {makeSuite, TestEnv} from './helpers/make-suite'; +import {convertToCurrencyDecimals} from '../helpers/contracts-helpers'; +import {waitForTx, evmSnapshot, evmRevert} from '@aave/deploy-v3'; +import {topUpNonPayableWithEther} from './helpers/utils/funds'; declare var hre: HardhatRuntimeEnvironment; @@ -23,7 +23,6 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { COLLATERAL_SAME_AS_BORROWING_CURRENCY, AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLE, NO_DEBT_OF_SELECTED_TYPE, - SAME_BLOCK_BORROW_REPAY, HEALTH_FACTOR_NOT_BELOW_THRESHOLD, INVALID_INTEREST_RATE_MODE_SELECTED, UNDERLYING_BALANCE_ZERO, @@ -35,13 +34,13 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { let snap: string; before(async () => { - const { addressesProvider, oracle } = testEnv; + const {addressesProvider, oracle} = testEnv; await waitForTx(await addressesProvider.setPriceOracle(oracle.address)); }); after(async () => { - const { aaveOracle, addressesProvider } = testEnv; + const {aaveOracle, addressesProvider} = testEnv; await waitForTx(await addressesProvider.setPriceOracle(aaveOracle.address)); }); @@ -53,7 +52,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateDeposit() when reserve is not active (revert expected)', async () => { - const { pool, poolAdmin, configurator, helpersContract, users, dai } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai} = testEnv; const user = users[0]; const configBefore = await helpersContract.getReserveConfigurationData(dai.address); @@ -74,7 +73,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateDeposit() when reserve is frozen (revert expected)', async () => { - const { pool, poolAdmin, configurator, helpersContract, users, dai } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai} = testEnv; const user = users[0]; const configBefore = await helpersContract.getReserveConfigurationData(dai.address); @@ -101,7 +100,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { * If deposited normally it is not possible for us deactivate. */ - const { pool, poolAdmin, configurator, helpersContract, users, dai, aDai, usdc } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai, aDai, usdc} = testEnv; const user = users[0]; await usdc.connect(user.signer)['mint(uint256)'](utils.parseEther('10000')); @@ -132,7 +131,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateBorrow() when reserve is frozen (revert expected)', async () => { - const { pool, poolAdmin, configurator, helpersContract, users, dai, usdc } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai, usdc} = testEnv; const user = users[0]; await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('1000')); @@ -163,7 +162,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateBorrow() when amount == 0 (revert expected)', async () => { - const { pool, users, dai } = testEnv; + const {pool, users, dai} = testEnv; const user = users[0]; await expect( @@ -172,7 +171,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateBorrow() when borrowing is not enabled (revert expected)', async () => { - const { pool, poolAdmin, configurator, helpersContract, users, dai, usdc } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai, usdc} = testEnv; const user = users[0]; await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('1000')); @@ -203,7 +202,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateBorrow() when stableRateBorrowing is not enabled', async () => { - const { pool, poolAdmin, configurator, helpersContract, users, dai, aDai, usdc } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai, aDai, usdc} = testEnv; const user = users[0]; await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('1000')); @@ -227,7 +226,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateBorrow() borrowing when user has already a HF < threshold', async () => { - const { pool, users, dai, usdc, oracle } = testEnv; + const {pool, users, dai, usdc, oracle} = testEnv; const user = users[0]; const depositor = users[1]; @@ -290,7 +289,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { // ltv != 0 // amount < aToken Balance - const { pool, users, dai, aDai, usdc } = testEnv; + const {pool, users, dai, aDai, usdc} = testEnv; const user = users[0]; await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); @@ -312,7 +311,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateBorrow() stable borrowing when amount > maxLoanSizeStable (revert expected)', async () => { - const { pool, users, dai, aDai, usdc } = testEnv; + const {pool, users, dai, aDai, usdc} = testEnv; const user = users[0]; await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); @@ -335,7 +334,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { it('validateLiquidationCall() when healthFactor > threshold (revert expected)', async () => { // Liquidation something that is not liquidatable - const { pool, users, dai, usdc } = testEnv; + const {pool, users, dai, usdc} = testEnv; const depositor = users[0]; const borrower = users[1]; @@ -384,7 +383,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { it('validateRepay() when reserve is not active (revert expected)', async () => { // Unsure how we can end in this scenario. Would require that it could be deactivated after someone have borrowed - const { pool, users, dai, helpersContract, configurator, poolAdmin } = testEnv; + const {pool, users, dai, helpersContract, configurator, poolAdmin} = testEnv; const user = users[0]; const configBefore = await helpersContract.getReserveConfigurationData(dai.address); @@ -408,7 +407,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { // (stableDebt > 0 && DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.STABLE) || // (variableDebt > 0 && DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.VARIABLE), - const { pool, users, dai, aDai, usdc } = testEnv; + const {pool, users, dai, aDai, usdc} = testEnv; const user = users[0]; // We need some debt @@ -435,7 +434,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { // (stableDebt > 0 && DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.STABLE) || // (variableDebt > 0 && DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.VARIABLE), - const { pool, users, dai } = testEnv; + const {pool, users, dai} = testEnv; const user = users[0]; // We need some debt @@ -456,7 +455,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { it('validateSwapRateMode() when reserve is not active', async () => { // Not clear when this would be useful in practice, as you should not be able to have debt if it is deactivated - const { pool, poolAdmin, configurator, helpersContract, users, dai, aDai } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai, aDai} = testEnv; const user = users[0]; const configBefore = await helpersContract.getReserveConfigurationData(dai.address); @@ -482,7 +481,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { it('validateSwapRateMode() when reserve is frozen', async () => { // Not clear when this would be useful in practice, as you should not be able to have debt if it is deactivated - const { pool, poolAdmin, configurator, helpersContract, users, dai } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai} = testEnv; const user = users[0]; const configBefore = await helpersContract.getReserveConfigurationData(dai.address); @@ -507,7 +506,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateSwapRateMode() with currentRateMode not equal to stable or variable, (revert expected)', async () => { - const { pool, helpersContract, users, dai } = testEnv; + const {pool, helpersContract, users, dai} = testEnv; const user = users[0]; const configBefore = await helpersContract.getReserveConfigurationData(dai.address); @@ -520,7 +519,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateSwapRateMode() from variable to stable with stableBorrowing disabled (revert expected)', async () => { - const { pool, poolAdmin, configurator, helpersContract, users, dai } = testEnv; + const {pool, poolAdmin, configurator, helpersContract, users, dai} = testEnv; const user = users[0]; await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('1000')); @@ -567,7 +566,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { // ltv != 0 // stableDebt + variableDebt < aToken - const { pool, users, dai, aDai, usdc } = testEnv; + const {pool, users, dai, aDai, usdc} = testEnv; const user = users[0]; await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); @@ -591,7 +590,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateRebalanceStableBorrowRate() when reserve is not active (revert expected)', async () => { - const { pool, configurator, helpersContract, poolAdmin, users, dai } = testEnv; + const {pool, configurator, helpersContract, poolAdmin, users, dai} = testEnv; const user = users[0]; const configBefore = await helpersContract.getReserveConfigurationData(dai.address); @@ -615,7 +614,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { * aToken balance (aDAI) its not technically possible to end up in this situation. * However, we impersonate the Pool to get some aDAI and make the test possible */ - const { pool, configurator, helpersContract, poolAdmin, users, dai, aDai } = testEnv; + const {pool, configurator, helpersContract, poolAdmin, users, dai, aDai} = testEnv; const user = users[0]; const configBefore = await helpersContract.getReserveConfigurationData(dai.address); @@ -643,7 +642,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateSetUseReserveAsCollateral() with userBalance == 0 (revert expected)', async () => { - const { pool, users, dai } = testEnv; + const {pool, users, dai} = testEnv; const user = users[0]; await expect( @@ -656,7 +655,7 @@ makeSuite('ValidationLogic: Edge cases', (testEnv: TestEnv) => { }); it('validateFlashloan() with inconsistent params (revert expected)', async () => { - const { pool, users, dai, aDai, usdc } = testEnv; + const {pool, users, dai, aDai, usdc} = testEnv; const user = users[0]; await expect( diff --git a/test-suites/variable-debt-token.spec.ts b/test-suites/variable-debt-token.spec.ts index 444a0de92..9f9029a36 100644 --- a/test-suites/variable-debt-token.spec.ts +++ b/test-suites/variable-debt-token.spec.ts @@ -1,24 +1,39 @@ -import { expect } from 'chai'; -import { utils } from 'ethers'; -import { impersonateAccountsHardhat } from '../helpers/misc-utils'; -import { MAX_UINT_AMOUNT, ZERO_ADDRESS } from '../helpers/constants'; -import { ProtocolErrors, RateMode } from '../helpers/types'; -import { makeSuite, TestEnv } from './helpers/make-suite'; -import { topUpNonPayableWithEther } from './helpers/utils/funds'; -import { convertToCurrencyDecimals } from '../helpers/contracts-helpers'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { evmRevert, evmSnapshot, increaseTime, waitForTx } from '@aave/deploy-v3'; -import { VariableDebtToken__factory } from '../types'; +import {expect} from 'chai'; +import {utils} from 'ethers'; +import {impersonateAccountsHardhat, setAutomine, setAutomineEvm} from '../helpers/misc-utils'; +import {MAX_UINT_AMOUNT, ZERO_ADDRESS} from '../helpers/constants'; +import {ProtocolErrors, RateMode} from '../helpers/types'; +import {makeSuite, TestEnv} from './helpers/make-suite'; +import {topUpNonPayableWithEther} from './helpers/utils/funds'; +import {convertToCurrencyDecimals} from '../helpers/contracts-helpers'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import { + evmRevert, + evmSnapshot, + getVariableDebtToken, + increaseTime, + waitForTx, +} from '@aave/deploy-v3'; +import {VariableDebtToken__factory} from '../types'; import './helpers/utils/wadraymath'; declare var hre: HardhatRuntimeEnvironment; makeSuite('VariableDebtToken', (testEnv: TestEnv) => { - const { CALLER_MUST_BE_POOL, INVALID_MINT_AMOUNT, INVALID_BURN_AMOUNT, CALLER_NOT_POOL_ADMIN } = + const {CALLER_MUST_BE_POOL, INVALID_MINT_AMOUNT, INVALID_BURN_AMOUNT, CALLER_NOT_POOL_ADMIN} = ProtocolErrors; + let snap: string; + + beforeEach(async () => { + snap = await evmSnapshot(); + }); + afterEach(async () => { + await evmRevert(snap); + }); + it('Check initialization', async () => { - const { pool, weth, dai, helpersContract, users } = testEnv; + const {pool, weth, dai, helpersContract, users} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).variableDebtTokenAddress; @@ -81,7 +96,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to mint not being the Pool (revert expected)', async () => { - const { deployer, dai, helpersContract } = testEnv; + const {deployer, dai, helpersContract} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) @@ -98,7 +113,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to burn not being the Pool (revert expected)', async () => { - const { deployer, dai, helpersContract } = testEnv; + const {deployer, dai, helpersContract} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) @@ -115,7 +130,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to mint with amountScaled == 0 (revert expected)', async () => { - const { deployer, pool, dai, helpersContract, users } = testEnv; + const {deployer, pool, dai, helpersContract, users} = testEnv; // Impersonate the Pool await topUpNonPayableWithEther(deployer.signer, [pool.address], utils.parseEther('1')); @@ -139,7 +154,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to burn with amountScaled == 0 (revert expected)', async () => { - const { deployer, pool, dai, helpersContract, users } = testEnv; + const {deployer, pool, dai, helpersContract, users} = testEnv; // Impersonate the Pool await topUpNonPayableWithEther(deployer.signer, [pool.address], utils.parseEther('1')); @@ -161,7 +176,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to transfer debt tokens (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).variableDebtTokenAddress; @@ -176,7 +191,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to approve debt tokens (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).variableDebtTokenAddress; @@ -194,7 +209,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to increaseAllowance (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).variableDebtTokenAddress; @@ -209,7 +224,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to decreaseAllowance (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).variableDebtTokenAddress; @@ -224,7 +239,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('Tries to transferFrom debt tokens (revert expected)', async () => { - const { users, dai, helpersContract } = testEnv; + const {users, dai, helpersContract} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).variableDebtTokenAddress; @@ -241,8 +256,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { }); it('setIncentivesController() ', async () => { - const snapshot = await evmSnapshot(); - const { dai, helpersContract, poolAdmin, aclManager, deployer } = testEnv; + const {dai, helpersContract, poolAdmin, aclManager, deployer} = testEnv; const daiVariableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).variableDebtTokenAddress; @@ -258,8 +272,6 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { await variableDebtContract.connect(poolAdmin.signer).setIncentivesController(ZERO_ADDRESS) ); expect(await variableDebtContract.getIncentivesController()).to.be.eq(ZERO_ADDRESS); - - await evmRevert(snapshot); }); it('setIncentivesController() from not pool admin (revert expected)', async () => { @@ -357,8 +369,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { ); const rawTransferEvents = tx.logs.filter( - ({ topics, address }) => - topics[0] === transferEventSig && address == variableDebtToken.address + ({topics, address}) => topics[0] === transferEventSig && address == variableDebtToken.address ); const parsedTransferEvent = variableDebtToken.interface.parseLog(rawTransferEvents[0]); const transferAmount = parsedTransferEvent.args.value; @@ -369,7 +380,7 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { utils.toUtf8Bytes('Mint(address,address,uint256,uint256,uint256)') ); const rawMintEvents = tx.logs.filter( - ({ topics, address }) => topics[0] === mintEventSig && address == variableDebtToken.address + ({topics, address}) => topics[0] === mintEventSig && address == variableDebtToken.address ); const parsedMintEvent = variableDebtToken.interface.parseLog(rawMintEvents[0]); @@ -377,4 +388,174 @@ makeSuite('VariableDebtToken', (testEnv: TestEnv) => { expect(parsedMintEvent.args.value).to.be.closeTo(borrowOnBehalfAmount.add(interest), 2); expect(parsedMintEvent.args.balanceIncrease).to.be.closeTo(interest, 2); }); + + it('User borrows and repays in same block with zero fees', async () => { + const {pool, users, dai, aDai, usdc, variableDebtDai} = testEnv; + const user = users[0]; + + // We need some debt. + await usdc.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); + await usdc.connect(user.signer).approve(pool.address, MAX_UINT_AMOUNT); + await pool + .connect(user.signer) + .deposit(usdc.address, utils.parseEther('2000'), user.address, 0); + await dai.connect(user.signer)['mint(uint256)'](utils.parseEther('2000')); + await dai.connect(user.signer).transfer(aDai.address, utils.parseEther('2000')); + await dai.connect(user.signer).approve(pool.address, MAX_UINT_AMOUNT); + + const userDataBefore = await pool.getUserAccountData(user.address); + expect(await variableDebtDai.balanceOf(user.address)).to.be.eq(0); + + // Turn off automining - pretty sure that coverage is getting messed up here. + await setAutomine(false); + // Borrow 500 dai + await pool + .connect(user.signer) + .borrow(dai.address, utils.parseEther('500'), RateMode.Variable, 0, user.address); + + // Turn on automining, but not mine a new block until next tx + await setAutomineEvm(true); + expect( + await pool + .connect(user.signer) + .repay(dai.address, utils.parseEther('500'), RateMode.Variable, user.address) + ); + + expect(await variableDebtDai.balanceOf(user.address)).to.be.eq(0); + expect(await dai.balanceOf(user.address)).to.be.eq(0); + expect(await dai.balanceOf(aDai.address)).to.be.eq(utils.parseEther('2000')); + + const userDataAfter = await pool.getUserAccountData(user.address); + expect(userDataBefore.totalCollateralBase).to.be.lte(userDataAfter.totalCollateralBase); + expect(userDataBefore.healthFactor).to.be.lte(userDataAfter.healthFactor); + expect(userDataBefore.totalDebtBase).to.be.eq(userDataAfter.totalDebtBase); + }); + + it('User borrows and repays in same block using credit delegation with zero fees', async () => { + const { + pool, + dai, + aDai, + weth, + users: [user1, user2, user3], + } = testEnv; + + // Add liquidity + await dai.connect(user3.signer)['mint(uint256)'](utils.parseUnits('1000', 18)); + await dai.connect(user3.signer).approve(pool.address, MAX_UINT_AMOUNT); + await pool + .connect(user3.signer) + .supply(dai.address, utils.parseUnits('1000', 18), user3.address, 0); + + // User1 supplies 10 WETH + await dai.connect(user1.signer)['mint(uint256)'](utils.parseUnits('100', 18)); + await dai.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + await weth.connect(user1.signer)['mint(uint256)'](utils.parseUnits('10', 18)); + await weth.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + await pool + .connect(user1.signer) + .supply(weth.address, utils.parseUnits('10', 18), user1.address, 0); + + const daiData = await pool.getReserveData(dai.address); + const variableDebtToken = await getVariableDebtToken(daiData.variableDebtTokenAddress); + + // User1 approves User2 to borrow 1000 DAI + expect( + await variableDebtToken + .connect(user1.signer) + .approveDelegation(user2.address, utils.parseUnits('1000', 18)) + ); + + const userDataBefore = await pool.getUserAccountData(user1.address); + + // Turn off automining to simulate actions in same block + await setAutomine(false); + + // User2 borrows 2 DAI on behalf of User1 + await pool + .connect(user2.signer) + .borrow(dai.address, utils.parseEther('2'), RateMode.Variable, 0, user1.address); + + // Turn on automining, but not mine a new block until next tx + await setAutomineEvm(true); + + expect( + await pool + .connect(user1.signer) + .repay(dai.address, utils.parseEther('2'), RateMode.Variable, user1.address) + ); + + expect(await variableDebtToken.balanceOf(user1.address)).to.be.eq(0); + expect(await dai.balanceOf(user2.address)).to.be.eq(utils.parseEther('2')); + expect(await dai.balanceOf(aDai.address)).to.be.eq(utils.parseEther('1000')); + + const userDataAfter = await pool.getUserAccountData(user1.address); + expect(userDataBefore.totalCollateralBase).to.be.lte(userDataAfter.totalCollateralBase); + expect(userDataBefore.healthFactor).to.be.lte(userDataAfter.healthFactor); + expect(userDataBefore.totalDebtBase).to.be.eq(userDataAfter.totalDebtBase); + }); + + it('User borrows and repays in same block using credit delegation with zero fees', async () => { + const { + pool, + dai, + aDai, + weth, + users: [user1, user2, user3], + } = testEnv; + + // Add liquidity + await dai.connect(user3.signer)['mint(uint256)'](utils.parseUnits('1000', 18)); + await dai.connect(user3.signer).approve(pool.address, MAX_UINT_AMOUNT); + await pool + .connect(user3.signer) + .supply(dai.address, utils.parseUnits('1000', 18), user3.address, 0); + + // User1 supplies 10 WETH + await dai.connect(user1.signer)['mint(uint256)'](utils.parseUnits('100', 18)); + await dai.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + await weth.connect(user1.signer)['mint(uint256)'](utils.parseUnits('10', 18)); + await weth.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + await pool + .connect(user1.signer) + .supply(weth.address, utils.parseUnits('10', 18), user1.address, 0); + + const daiData = await pool.getReserveData(dai.address); + const variableDebtToken = await getVariableDebtToken(daiData.variableDebtTokenAddress); + + // User1 approves User2 to borrow 1000 DAI + expect( + await variableDebtToken + .connect(user1.signer) + .approveDelegation(user2.address, utils.parseUnits('1000', 18)) + ); + + const userDataBefore = await pool.getUserAccountData(user1.address); + + // Turn off automining to simulate actions in same block + await setAutomine(false); + + // User2 borrows 2 DAI on behalf of User1 + await pool + .connect(user2.signer) + .borrow(dai.address, utils.parseEther('2'), RateMode.Variable, 0, user1.address); + + // Turn on automining, but not mine a new block until next tx + await setAutomineEvm(true); + + expect( + await pool + .connect(user1.signer) + .repay(dai.address, utils.parseEther('2'), RateMode.Variable, user1.address) + ); + + expect(await variableDebtToken.balanceOf(user1.address)).to.be.eq(0); + expect(await dai.balanceOf(user2.address)).to.be.eq(utils.parseEther('2')); + expect(await dai.balanceOf(aDai.address)).to.be.eq(utils.parseEther('1000')); + + const userDataAfter = await pool.getUserAccountData(user1.address); + expect(userDataBefore.totalCollateralBase).to.be.lte(userDataAfter.totalCollateralBase); + expect(userDataBefore.healthFactor).to.be.lte(userDataAfter.healthFactor); + expect(userDataBefore.totalDebtBase).to.be.eq(userDataAfter.totalDebtBase); + }); }); From 320eecc59608997dedab57190918cf27af915d95 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Tue, 9 Aug 2022 22:38:21 +0200 Subject: [PATCH 3/3] fix: Fix error in tests --- test-suites/stable-debt-token.spec.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test-suites/stable-debt-token.spec.ts b/test-suites/stable-debt-token.spec.ts index 22233e36b..fa34952a5 100644 --- a/test-suites/stable-debt-token.spec.ts +++ b/test-suites/stable-debt-token.spec.ts @@ -421,24 +421,25 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { .supply(weth.address, utils.parseUnits('10', 18), user1.address, 0); const daiData = await pool.getReserveData(dai.address); - const variableDebtToken = await getStableDebtToken(daiData.variableDebtTokenAddress); + const stableDebtToken = await getStableDebtToken(daiData.stableDebtTokenAddress); // User1 approves User2 to borrow 1000 DAI expect( - await variableDebtToken + await stableDebtToken .connect(user1.signer) .approveDelegation(user2.address, utils.parseUnits('1000', 18)) ); - const userDataBefore = await pool.getUserAccountData(user1.address); // Turn off automining to simulate actions in same block await setAutomine(false); // User2 borrows 2 DAI on behalf of User1 - await pool - .connect(user2.signer) - .borrow(dai.address, utils.parseEther('2'), RateMode.Stable, 0, user1.address); + expect( + await pool + .connect(user2.signer) + .borrow(dai.address, utils.parseEther('2'), RateMode.Stable, 0, user1.address) + ); // Turn on automining, but not mine a new block until next tx await setAutomineEvm(true); @@ -449,7 +450,7 @@ makeSuite('StableDebtToken', (testEnv: TestEnv) => { .repay(dai.address, utils.parseEther('2'), RateMode.Stable, user1.address) ); - expect(await variableDebtToken.balanceOf(user1.address)).to.be.eq(0); + expect(await stableDebtToken.balanceOf(user1.address)).to.be.eq(0); expect(await dai.balanceOf(user2.address)).to.be.eq(utils.parseEther('2')); expect(await dai.balanceOf(aDai.address)).to.be.eq(utils.parseEther('1000'));