Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add zero IR strategy #818

Merged
merged 7 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions contracts/misc/ZeroReserveInterestRateStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
import {IDefaultInterestRateStrategy} from '../interfaces/IDefaultInterestRateStrategy.sol';
import {IReserveInterestRateStrategy} from '../interfaces/IReserveInterestRateStrategy.sol';
import {IPoolAddressesProvider} from '../interfaces/IPoolAddressesProvider.sol';

/**
* @title ZeroReserveInterestRateStrategy contract
* @author Aave
* @notice Interest Rate Strategy contract, with all parameters zeroed.
* @dev It returns zero liquidity and borrow rate.
*/
contract ZeroReserveInterestRateStrategy is IDefaultInterestRateStrategy {
/// @inheritdoc IDefaultInterestRateStrategy
uint256 public immutable OPTIMAL_USAGE_RATIO = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why immutable and just not constant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, fixed now


/// @inheritdoc IDefaultInterestRateStrategy
uint256 public immutable OPTIMAL_STABLE_TO_TOTAL_DEBT_RATIO = 0;

/// @inheritdoc IDefaultInterestRateStrategy
uint256 public immutable MAX_EXCESS_USAGE_RATIO = 0;

/// @inheritdoc IDefaultInterestRateStrategy
uint256 public immutable MAX_EXCESS_STABLE_TO_TOTAL_DEBT_RATIO = 0;

IPoolAddressesProvider public immutable ADDRESSES_PROVIDER;

// Base variable borrow rate when usage rate = 0. Expressed in ray
uint256 internal immutable _baseVariableBorrowRate = 0;

// Slope of the variable interest curve when usage ratio > 0 and <= OPTIMAL_USAGE_RATIO. Expressed in ray
uint256 internal immutable _variableRateSlope1 = 0;

// Slope of the variable interest curve when usage ratio > OPTIMAL_USAGE_RATIO. Expressed in ray
uint256 internal immutable _variableRateSlope2 = 0;

// Slope of the stable interest curve when usage ratio > 0 and <= OPTIMAL_USAGE_RATIO. Expressed in ray
uint256 internal immutable _stableRateSlope1 = 0;

// Slope of the stable interest curve when usage ratio > OPTIMAL_USAGE_RATIO. Expressed in ray
uint256 internal immutable _stableRateSlope2 = 0;

// Premium on top of `_variableRateSlope1` for base stable borrowing rate
uint256 internal immutable _baseStableRateOffset = 0;

// Additional premium applied to stable rate when stable debt surpass `OPTIMAL_STABLE_TO_TOTAL_DEBT_RATIO`
uint256 internal immutable _stableRateExcessOffset = 0;

/**
* @dev Constructor.
* @param provider The address of the PoolAddressesProvider contract
*/
constructor(IPoolAddressesProvider provider) {
ADDRESSES_PROVIDER = provider;
}

/// @inheritdoc IDefaultInterestRateStrategy
function getVariableRateSlope1() external pure returns (uint256) {
return _variableRateSlope1;
}

/// @inheritdoc IDefaultInterestRateStrategy
function getVariableRateSlope2() external pure returns (uint256) {
return _variableRateSlope2;
}

/// @inheritdoc IDefaultInterestRateStrategy
function getStableRateSlope1() external pure returns (uint256) {
return _stableRateSlope1;
}

/// @inheritdoc IDefaultInterestRateStrategy
function getStableRateSlope2() external pure returns (uint256) {
return _stableRateSlope2;
}

/// @inheritdoc IDefaultInterestRateStrategy
function getStableRateExcessOffset() external pure returns (uint256) {
return _stableRateExcessOffset;
}

/// @inheritdoc IDefaultInterestRateStrategy
function getBaseStableBorrowRate() public pure returns (uint256) {
return _variableRateSlope1 + _baseStableRateOffset;
}

/// @inheritdoc IDefaultInterestRateStrategy
function getBaseVariableBorrowRate() external pure override returns (uint256) {
return _baseVariableBorrowRate;
}

/// @inheritdoc IDefaultInterestRateStrategy
function getMaxVariableBorrowRate() external pure override returns (uint256) {
return _baseVariableBorrowRate + _variableRateSlope1 + _variableRateSlope2;
}

/// @inheritdoc IReserveInterestRateStrategy
function calculateInterestRates(DataTypes.CalculateInterestRatesParams memory)
public
pure
override
returns (
uint256,
uint256,
uint256
)
{
return (0, 0, 0);
}
}
272 changes: 272 additions & 0 deletions test-suites/configurator-rate-strategy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import { expect } from 'chai';
import { utils } from 'ethers';
import { ZeroReserveInterestRateStrategy__factory } from '../types';
import { TestEnv, makeSuite } from './helpers/make-suite';
import './helpers/utils/wadraymath';
import { MAX_UINT_AMOUNT, ZERO_ADDRESS, evmRevert, evmSnapshot } from '@aave/deploy-v3';

makeSuite('PoolConfigurator: Set Rate Strategy', (testEnv: TestEnv) => {
let snap: string;

beforeEach(async () => {
snap = await evmSnapshot();
});
afterEach(async () => {
await evmRevert(snap);
});

it('Update Interest Rate of a reserve', async () => {
const {
poolAdmin,
configurator,
pool,
helpersContract,
weth,
dai,
usdc,
users: [depositor, borrower],
} = testEnv;

// Utilize the DAI pool
const mintedAmount = utils.parseEther('100');
expect(await dai.connect(depositor.signer)['mint(uint256)'](mintedAmount));
expect(await dai.connect(depositor.signer).approve(pool.address, MAX_UINT_AMOUNT));
expect(
await pool.connect(depositor.signer).deposit(dai.address, mintedAmount, depositor.address, 0)
);
expect(await weth.connect(borrower.signer)['mint(uint256)'](mintedAmount));
expect(await weth.connect(borrower.signer).approve(pool.address, MAX_UINT_AMOUNT));
expect(
await pool.connect(borrower.signer).deposit(weth.address, mintedAmount, borrower.address, 0)
);
expect(
await pool
.connect(borrower.signer)
.borrow(dai.address, utils.parseEther('1'), 1, 0, borrower.address)
);

// PoolAdmin updates IR strategy address
const strategyUSDC = await helpersContract.getInterestRateStrategyAddress(usdc.address);
const reserveDataBefore = await pool.getReserveData(dai.address);

await expect(
configurator
.connect(poolAdmin.signer)
.setReserveInterestRateStrategyAddress(dai.address, strategyUSDC)
)
.to.emit(configurator, 'ReserveInterestRateStrategyChanged')
.withArgs(dai.address, reserveDataBefore.interestRateStrategyAddress, strategyUSDC);

const reserveDataAfter = await pool.getReserveData(dai.address);

expect(reserveDataAfter.interestRateStrategyAddress).to.be.eq(strategyUSDC);

// Indexes and rates are the same until a new operation is performed
expect(reserveDataBefore.liquidityIndex).to.be.eq(reserveDataAfter.liquidityIndex);
expect(reserveDataBefore.currentLiquidityRate).to.be.eq(reserveDataAfter.currentLiquidityRate);
expect(reserveDataBefore.variableBorrowIndex).to.be.eq(reserveDataAfter.variableBorrowIndex);
expect(reserveDataBefore.currentVariableBorrowRate).to.be.eq(
reserveDataAfter.currentVariableBorrowRate
);
expect(reserveDataBefore.currentStableBorrowRate).to.be.eq(
reserveDataAfter.currentStableBorrowRate
);
expect(reserveDataBefore.lastUpdateTimestamp).to.be.eq(reserveDataAfter.lastUpdateTimestamp);

// Reserve interaction so IR gets applied
expect(
await pool
.connect(borrower.signer)
.borrow(dai.address, utils.parseEther('1'), 1, 0, borrower.address)
);

// Rates get updated
const reserveDataUpdated = await pool.getReserveData(dai.address);
expect(reserveDataAfter.interestRateStrategyAddress).to.be.eq(
reserveDataUpdated.interestRateStrategyAddress
);

expect(reserveDataAfter.currentLiquidityRate).to.be.not.eq(
reserveDataUpdated.currentLiquidityRate
);
expect(reserveDataAfter.currentVariableBorrowRate).to.be.not.eq(
reserveDataUpdated.currentVariableBorrowRate
);
expect(reserveDataAfter.currentStableBorrowRate).to.be.not.eq(
reserveDataUpdated.currentStableBorrowRate
);
expect(reserveDataAfter.lastUpdateTimestamp).to.be.lt(reserveDataUpdated.lastUpdateTimestamp);
});

it('Update Interest Rate of a reserve with ZERO_ADDRESS and bricks the reserve (revert expected)', async () => {
const {
poolAdmin,
configurator,
pool,
weth,
dai,
users: [depositor, borrower],
} = testEnv;

// Utilize the DAI pool
const mintedAmount = utils.parseEther('100');
expect(await dai.connect(depositor.signer)['mint(uint256)'](mintedAmount));
expect(await dai.connect(depositor.signer).approve(pool.address, MAX_UINT_AMOUNT));
expect(
await pool.connect(depositor.signer).deposit(dai.address, mintedAmount, depositor.address, 0)
);
expect(await weth.connect(borrower.signer)['mint(uint256)'](mintedAmount));
expect(await weth.connect(borrower.signer).approve(pool.address, MAX_UINT_AMOUNT));
expect(
await pool.connect(borrower.signer).deposit(weth.address, mintedAmount, borrower.address, 0)
);
expect(
await pool
.connect(borrower.signer)
.borrow(dai.address, utils.parseEther('1'), 1, 0, borrower.address)
);

// PoolAdmin updates IR strategy address
const reserveDataBefore = await pool.getReserveData(dai.address);

await expect(
configurator
.connect(poolAdmin.signer)
.setReserveInterestRateStrategyAddress(dai.address, ZERO_ADDRESS)
)
.to.emit(configurator, 'ReserveInterestRateStrategyChanged')
.withArgs(dai.address, reserveDataBefore.interestRateStrategyAddress, ZERO_ADDRESS);

// Reserve interaction so IR gets applied
await expect(
pool
.connect(borrower.signer)
.borrow(dai.address, utils.parseEther('1'), 1, 0, borrower.address)
).reverted;
});

it('ZeroReserveInterestRateStrategy - Checks all rates are 0', async () => {
const { deployer, addressesProvider } = testEnv;
const zeroStrategy = await new ZeroReserveInterestRateStrategy__factory(deployer.signer).deploy(
addressesProvider.address
);

expect(await zeroStrategy.OPTIMAL_USAGE_RATIO()).to.be.eq(0);
expect(await zeroStrategy.OPTIMAL_STABLE_TO_TOTAL_DEBT_RATIO()).to.be.eq(0);
expect(await zeroStrategy.MAX_EXCESS_USAGE_RATIO()).to.be.eq(0);
expect(await zeroStrategy.MAX_EXCESS_STABLE_TO_TOTAL_DEBT_RATIO()).to.be.eq(0);
expect(await zeroStrategy.getVariableRateSlope1()).to.be.eq(0);
expect(await zeroStrategy.getVariableRateSlope2()).to.be.eq(0);
expect(await zeroStrategy.getStableRateSlope1()).to.be.eq(0);
expect(await zeroStrategy.getStableRateSlope2()).to.be.eq(0);
expect(await zeroStrategy.getStableRateExcessOffset()).to.be.eq(0);
expect(await zeroStrategy.getBaseStableBorrowRate()).to.be.eq(0);
expect(await zeroStrategy.getBaseVariableBorrowRate()).to.be.eq(0);
expect(await zeroStrategy.getMaxVariableBorrowRate()).to.be.eq(0);

const {
0: currentLiquidityRate,
1: currentStableBorrowRate,
2: currentVariableBorrowRate,
} = await zeroStrategy.calculateInterestRates({
unbacked: 0,
liquidityAdded: 0,
liquidityTaken: 0,
totalStableDebt: 0,
totalVariableDebt: 0,
averageStableBorrowRate: 0,
reserveFactor: 0,
reserve: ZERO_ADDRESS,
aToken: ZERO_ADDRESS,
});

expect(currentLiquidityRate).to.be.eq(0, 'Invalid liquidity rate');
expect(currentStableBorrowRate).to.be.eq(0, 'Invalid stable rate');
expect(currentVariableBorrowRate).to.be.eq(0, 'Invalid variable rate');
});

it('ZeroReserveInterestRateStrategy - Update a reserve with ZeroInterestRateStrategy to set zero rates', async () => {
const {
deployer,
poolAdmin,
configurator,
pool,
addressesProvider,
weth,
dai,
users: [depositor, borrower],
} = testEnv;

const zeroStrategy = await new ZeroReserveInterestRateStrategy__factory(deployer.signer).deploy(
addressesProvider.address
);
// Utilize the DAI pool
const mintedAmount = utils.parseEther('100');
expect(await dai.connect(depositor.signer)['mint(uint256)'](mintedAmount));
expect(await dai.connect(depositor.signer).approve(pool.address, MAX_UINT_AMOUNT));
expect(
await pool.connect(depositor.signer).deposit(dai.address, mintedAmount, depositor.address, 0)
);
expect(await weth.connect(borrower.signer)['mint(uint256)'](mintedAmount));
expect(await weth.connect(borrower.signer).approve(pool.address, MAX_UINT_AMOUNT));
expect(
await pool.connect(borrower.signer).deposit(weth.address, mintedAmount, borrower.address, 0)
);
expect(
await pool
.connect(borrower.signer)
.borrow(dai.address, utils.parseEther('1'), 1, 0, borrower.address)
);

// PoolAdmin updates IR strategy address
const reserveDataBefore = await pool.getReserveData(dai.address);

await expect(
configurator
.connect(poolAdmin.signer)
.setReserveInterestRateStrategyAddress(dai.address, zeroStrategy.address)
)
.to.emit(configurator, 'ReserveInterestRateStrategyChanged')
.withArgs(dai.address, reserveDataBefore.interestRateStrategyAddress, zeroStrategy.address);

const reserveDataAfter = await pool.getReserveData(dai.address);
expect(reserveDataAfter.interestRateStrategyAddress).to.be.eq(zeroStrategy.address);

// Indexes and rates are the same until a new operation is performed
expect(reserveDataBefore.liquidityIndex).to.be.eq(reserveDataAfter.liquidityIndex);
expect(reserveDataBefore.currentLiquidityRate).to.be.eq(reserveDataAfter.currentLiquidityRate);
expect(reserveDataBefore.variableBorrowIndex).to.be.eq(reserveDataAfter.variableBorrowIndex);
expect(reserveDataBefore.currentVariableBorrowRate).to.be.eq(
reserveDataAfter.currentVariableBorrowRate
);
expect(reserveDataBefore.currentStableBorrowRate).to.be.eq(
reserveDataAfter.currentStableBorrowRate
);
expect(reserveDataBefore.lastUpdateTimestamp).to.be.eq(reserveDataAfter.lastUpdateTimestamp);

// Reserve interaction so IR gets applied
expect(
await pool
.connect(borrower.signer)
.borrow(dai.address, utils.parseEther('1'), 1, 0, borrower.address)
);

// Rates get updated
const reserveDataUpdated = await pool.getReserveData(dai.address);
expect(reserveDataAfter.interestRateStrategyAddress).to.be.eq(zeroStrategy.address);
expect(reserveDataAfter.currentLiquidityRate).to.be.not.eq(
reserveDataUpdated.currentLiquidityRate
);
expect(reserveDataAfter.currentVariableBorrowRate).to.be.not.eq(
reserveDataUpdated.currentVariableBorrowRate
);
expect(reserveDataAfter.currentStableBorrowRate).to.be.not.eq(
reserveDataUpdated.currentStableBorrowRate
);
expect(reserveDataAfter.lastUpdateTimestamp).to.be.lt(reserveDataUpdated.lastUpdateTimestamp);

expect(reserveDataUpdated.currentLiquidityRate).to.be.eq(0);
expect(reserveDataUpdated.currentVariableBorrowRate).to.be.eq(0);
expect(reserveDataUpdated.currentStableBorrowRate).to.be.eq(0);
});
});