-
Notifications
You must be signed in to change notification settings - Fork 531
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
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
445b8b7
feat: Add zero IR strategy
miguelmtzinf 87d3cf7
test: Add test cases for updating reserve interest rate strategy
miguelmtzinf 1619a9c
Merge branch 'feat/3.0.2' into feat/817-zero-ir
miguelmtzinf 9c3dba1
fix: Format zeroed IR
miguelmtzinf e0ebb72
fix: Use constant instead of immutable for zero values
miguelmtzinf d7ed3ea
test: Add rebalance call in tests
miguelmtzinf 6815265
fix: Add additional rebalance in test with zero ir
miguelmtzinf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
||
/// @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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why
immutable
and just notconstant
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, fixed now