Skip to content

Commit

Permalink
Borrow/repay check removal (#705)
Browse files Browse the repository at this point in the history
* feat: removed borrow/repay in the same block check

* test: Add tests for borrow-repay in same block

* fix: Fix error in tests

Co-authored-by: miguelmtzinf <[email protected]>
  • Loading branch information
The-3D and miguelmtzinf authored Oct 28, 2022
1 parent c35c46d commit b1d94da
Show file tree
Hide file tree
Showing 6 changed files with 388 additions and 239 deletions.
1 change: 0 additions & 1 deletion contracts/protocol/libraries/helpers/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
14 changes: 0 additions & 14 deletions contracts/protocol/libraries/logic/ValidationLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 0 additions & 1 deletion helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
173 changes: 142 additions & 31 deletions test-suites/stable-debt-token.spec.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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(
Expand All @@ -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,
Expand Down Expand Up @@ -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;

Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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'));
Expand Down Expand Up @@ -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,
Expand All @@ -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 () => {
Expand All @@ -348,4 +352,111 @@ 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 stableDebtToken = await getStableDebtToken(daiData.stableDebtTokenAddress);

// User1 approves User2 to borrow 1000 DAI
expect(
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
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);

expect(
await pool
.connect(user1.signer)
.repay(dai.address, utils.parseEther('2'), RateMode.Stable, user1.address)
);

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

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);
});
});
Loading

0 comments on commit b1d94da

Please sign in to comment.