Skip to content

Commit

Permalink
fix: fixes and optimizations in LinearInterestRateModelV3 (#230)
Browse files Browse the repository at this point in the history
In this PR:
* Small fixes in `calcBorrowRate` and `availableToBorrow` to address #194
* Small gas optimizations like removing redundant getters and evaluating some expressions at compile time
  • Loading branch information
lekhovitsky committed Jul 7, 2024
1 parent 020a835 commit b3999d8
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 85 deletions.
7 changes: 1 addition & 6 deletions contracts/interfaces/ILinearInterestRateModelV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@ import {IInterestRateModel} from "./base/IInterestRateModel.sol";

/// @title Linear interest rate model V3 interface
interface ILinearInterestRateModelV3 is IInterestRateModel {
/// @dev Linear IRM is stateless so the overriden function is marked as `view`
function calcBorrowRate(uint256 expectedLiquidity, uint256 availableLiquidity, bool checkOptimalBorrowing)
external
view
override
returns (uint256);

function availableToBorrow(uint256 expectedLiquidity, uint256 availableLiquidity)
external
view
override
returns (uint256);

function isBorrowingMoreU2Forbidden() external view returns (bool);

function getModelParameters()
Expand Down
2 changes: 2 additions & 0 deletions contracts/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pragma solidity ^0.8.17;
uint256 constant WAD = 1e18;
uint256 constant RAY = 1e27;
uint16 constant PERCENTAGE_FACTOR = 1e4;
uint256 constant WAD_OVER_PERCENTAGE = WAD / PERCENTAGE_FACTOR;
uint256 constant RAY_OVER_PERCENTAGE = RAY / PERCENTAGE_FACTOR;

uint256 constant SECONDS_PER_YEAR = 365 days;
uint256 constant EPOCH_LENGTH = 7 days;
Expand Down
6 changes: 2 additions & 4 deletions contracts/libraries/QuotasLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
pragma solidity ^0.8.17;

import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {RAY, SECONDS_PER_YEAR, PERCENTAGE_FACTOR} from "../libraries/Constants.sol";

uint192 constant RAY_DIVIDED_BY_PERCENTAGE = uint192(RAY / PERCENTAGE_FACTOR);
import {RAY, RAY_OVER_PERCENTAGE, SECONDS_PER_YEAR, PERCENTAGE_FACTOR} from "../libraries/Constants.sol";

/// @title Quotas logic library
library QuotasLogic {
Expand All @@ -21,7 +19,7 @@ library QuotasLogic {
{
return uint192(
uint256(cumulativeIndexLU)
+ RAY_DIVIDED_BY_PERCENTAGE * (block.timestamp - lastQuotaRateUpdate) * rate / SECONDS_PER_YEAR
+ uint192(RAY_OVER_PERCENTAGE) * (block.timestamp - lastQuotaRateUpdate) * rate / SECONDS_PER_YEAR
); // U:[QL-1]
}

Expand Down
101 changes: 47 additions & 54 deletions contracts/pool/LinearInterestRateModelV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
pragma solidity ^0.8.17;
pragma abicoder v1;

import {WAD, RAY, PERCENTAGE_FACTOR} from "../libraries/Constants.sol";
import {ILinearInterestRateModelV3} from "../interfaces/ILinearInterestRateModelV3.sol";

// EXCEPTIONS
import {IncorrectParameterException, BorrowingMoreThanU2ForbiddenException} from "../interfaces/IExceptions.sol";

import {PERCENTAGE_FACTOR, RAY, RAY_OVER_PERCENTAGE, WAD, WAD_OVER_PERCENTAGE} from "../libraries/Constants.sol";

/// @title Linear interest rate model V3
/// @notice Gearbox V3 uses a two-point linear interest rate model in its pools.
/// Unlike previous single-point models, it has a new intermediate slope between the obtuse and steep regions
Expand All @@ -18,28 +17,28 @@ import {IncorrectParameterException, BorrowingMoreThanU2ForbiddenException} from
/// in order to create a reserve for exits and make rates more stable.
contract LinearInterestRateModelV3 is ILinearInterestRateModelV3 {
/// @notice Contract version
uint256 public constant override version = 3_00;
uint256 public constant override version = 3_10;

/// @notice Whether to prevent borrowing over `U_2` utilization
bool public immutable override isBorrowingMoreU2Forbidden;

/// @notice The first slope change point (obtuse -> intermediate region)
uint256 public immutable U_1_WAD;
uint256 internal immutable U_1_WAD;

/// @notice The second slope change point (intermediate -> steep region)
uint256 public immutable U_2_WAD;
uint256 internal immutable U_2_WAD;

/// @notice Base interest rate in RAY format
uint256 public immutable R_base_RAY;
uint256 internal immutable R_base_RAY;

/// @notice Slope of the obtuse region
uint256 public immutable R_slope1_RAY;
uint256 internal immutable R_slope1_RAY;

/// @notice Slope of the intermediate region
uint256 public immutable R_slope2_RAY;
uint256 internal immutable R_slope2_RAY;

/// @notice Slope of the steep region
uint256 public immutable R_slope3_RAY;
uint256 internal immutable R_slope3_RAY;

/// @notice Constructor
/// @param U_1 `U_1` in basis points
Expand All @@ -49,6 +48,7 @@ contract LinearInterestRateModelV3 is ILinearInterestRateModelV3 {
/// @param R_slope2 `R_slope2` in basis points
/// @param R_slope3 `R_slope3` in basis points
/// @param _isBorrowingMoreU2Forbidden Whether to prevent borrowing over `U_2` utilization
/// @custom:tests U:[LIM-1], U:[LIM-2]
constructor(
uint16 U_1,
uint16 U_2,
Expand All @@ -59,114 +59,107 @@ contract LinearInterestRateModelV3 is ILinearInterestRateModelV3 {
bool _isBorrowingMoreU2Forbidden
) {
if (
(U_1 >= PERCENTAGE_FACTOR) || (U_2 >= PERCENTAGE_FACTOR) || (U_1 > U_2) || (R_base > PERCENTAGE_FACTOR)
|| (R_slope1 > PERCENTAGE_FACTOR) || (R_slope2 > PERCENTAGE_FACTOR) || (R_slope1 > R_slope2)
|| (R_slope2 > R_slope3)
) {
revert IncorrectParameterException(); // U:[LIM-2]
}

/// Critical utilization points are stored in WAD format
U_2 >= PERCENTAGE_FACTOR || U_1 > U_2 || R_base > PERCENTAGE_FACTOR || R_slope2 > PERCENTAGE_FACTOR
|| R_slope1 > R_slope2 || R_slope2 > R_slope3
) revert IncorrectParameterException();

U_1_WAD = U_1 * (WAD / PERCENTAGE_FACTOR); // U:[LIM-1]
U_2_WAD = U_2 * (WAD / PERCENTAGE_FACTOR); // U:[LIM-1]
// critical utilization points are stored in WAD format
U_1_WAD = U_1 * WAD_OVER_PERCENTAGE;
U_2_WAD = U_2 * WAD_OVER_PERCENTAGE;

/// Slopes are stored in RAY format
R_base_RAY = R_base * (RAY / PERCENTAGE_FACTOR); // U:[LIM-1]
R_slope1_RAY = R_slope1 * (RAY / PERCENTAGE_FACTOR); // U:[LIM-1]
R_slope2_RAY = R_slope2 * (RAY / PERCENTAGE_FACTOR); // U:[LIM-1]
R_slope3_RAY = R_slope3 * (RAY / PERCENTAGE_FACTOR); // U:[LIM-1]
// slopes are stored in RAY format
R_base_RAY = R_base * RAY_OVER_PERCENTAGE;
R_slope1_RAY = R_slope1 * RAY_OVER_PERCENTAGE;
R_slope2_RAY = R_slope2 * RAY_OVER_PERCENTAGE;
R_slope3_RAY = R_slope3 * RAY_OVER_PERCENTAGE;

isBorrowingMoreU2Forbidden = _isBorrowingMoreU2Forbidden; // U:[LIM-1]
}

/// @dev Same as the next one with `checkOptimalBorrowing` set to `false`, added for compatibility with older pools
function calcBorrowRate(uint256 expectedLiquidity, uint256 availableLiquidity) external view returns (uint256) {
return calcBorrowRate(expectedLiquidity, availableLiquidity, false);
isBorrowingMoreU2Forbidden = _isBorrowingMoreU2Forbidden;
}

/// @notice Returns the borrow rate calculated based on expected and available liquidity
/// @param expectedLiquidity Expected liquidity in the pool
/// @param availableLiquidity Available liquidity in the pool
/// @param checkOptimalBorrowing Whether to check if borrowing over `U_2` utilization should be prevented
/// @custom:tests U:[LIM-3], U:[LIM-4]
function calcBorrowRate(uint256 expectedLiquidity, uint256 availableLiquidity, bool checkOptimalBorrowing)
public
view
override
returns (uint256)
{
if (expectedLiquidity <= availableLiquidity) {
return R_base_RAY; // U:[LIM-3]
return R_base_RAY;
}

// expectedLiquidity - availableLiquidity
// U = ----------------------------------------
// expectedLiquidity

uint256 U_WAD = (WAD * (expectedLiquidity - availableLiquidity)) / expectedLiquidity; // U:[LIM-3]
uint256 U_WAD = (WAD * (expectedLiquidity - availableLiquidity)) / expectedLiquidity;

// If U < U_1:
// If U <= U_1:
// U
// borrowRate = R_base + R_slope1 * -----
// U_1

if (U_WAD < U_1_WAD) {
return R_base_RAY + ((R_slope1_RAY * U_WAD) / U_1_WAD); // U:[LIM-3]
if (U_WAD <= U_1_WAD) {
return R_base_RAY + ((R_slope1_RAY * U_WAD) / U_1_WAD);
}

// If U >= U_1 & U < U_2:
// if U > U_1 & U <= U_2:
// U - U_1
// borrowRate = R_base + R_slope1 + R_slope2 * -----------
// U_2 - U_1

if (U_WAD < U_2_WAD) {
return R_base_RAY + R_slope1_RAY + (R_slope2_RAY * (U_WAD - U_1_WAD)) / (U_2_WAD - U_1_WAD); // U:[LIM-3]
if (U_WAD <= U_2_WAD) {
return R_base_RAY + R_slope1_RAY + (R_slope2_RAY * (U_WAD - U_1_WAD)) / (U_2_WAD - U_1_WAD);
}

// If U > U_2 in `isBorrowingMoreU2Forbidden` and the utilization check is requested,
// if U > U_2 in `isBorrowingMoreU2Forbidden` and the utilization check is requested,
// the function will revert to prevent raising utilization over the limit
if (checkOptimalBorrowing && isBorrowingMoreU2Forbidden) {
revert BorrowingMoreThanU2ForbiddenException(); // U:[LIM-3]
revert BorrowingMoreThanU2ForbiddenException();
}

// If U >= U_2:
// if U > U_2:
// U - U_2
// borrowRate = R_base + R_slope1 + R_slope2 + R_slope3 * ----------
// 1 - U_2

return R_base_RAY + R_slope1_RAY + R_slope2_RAY + R_slope3_RAY * (U_WAD - U_2_WAD) / (WAD - U_2_WAD); // U:[LIM-3]
return R_base_RAY + R_slope1_RAY + R_slope2_RAY + R_slope3_RAY * (U_WAD - U_2_WAD) / (WAD - U_2_WAD);
}

/// @notice Returns the model's parameters in basis points
/// @custom:tests U:[LIM-1]
function getModelParameters()
external
view
override
returns (uint16 U_1, uint16 U_2, uint16 R_base, uint16 R_slope1, uint16 R_slope2, uint16 R_slope3)
{
U_1 = uint16(U_1_WAD / (WAD / PERCENTAGE_FACTOR)); // U:[LIM-1]
U_2 = uint16(U_2_WAD / (WAD / PERCENTAGE_FACTOR)); // U:[LIM-1]
R_base = uint16(R_base_RAY / (RAY / PERCENTAGE_FACTOR)); // U:[LIM-1]
R_slope1 = uint16(R_slope1_RAY / (RAY / PERCENTAGE_FACTOR)); // U:[LIM-1]
R_slope2 = uint16(R_slope2_RAY / (RAY / PERCENTAGE_FACTOR)); // U:[LIM-1]
R_slope3 = uint16(R_slope3_RAY / (RAY / PERCENTAGE_FACTOR)); // U:[LIM-1]
U_1 = uint16(U_1_WAD / WAD_OVER_PERCENTAGE);
U_2 = uint16(U_2_WAD / WAD_OVER_PERCENTAGE);
R_base = uint16(R_base_RAY / RAY_OVER_PERCENTAGE);
R_slope1 = uint16(R_slope1_RAY / RAY_OVER_PERCENTAGE);
R_slope2 = uint16(R_slope2_RAY / RAY_OVER_PERCENTAGE);
R_slope3 = uint16(R_slope3_RAY / RAY_OVER_PERCENTAGE);
}

/// @notice Returns the amount available to borrow
/// - If borrowing over `U_2` is prohibited, returns the amount that can be borrowed before `U_2` is reached
/// - Otherwise, simply returns the available liquidity
/// @custom:tests U:[LIM-3], U:[LIM-4]
function availableToBorrow(uint256 expectedLiquidity, uint256 availableLiquidity)
external
view
override
returns (uint256)
{
if (isBorrowingMoreU2Forbidden && (expectedLiquidity >= availableLiquidity) && (expectedLiquidity != 0)) {
uint256 U_WAD = (WAD * (expectedLiquidity - availableLiquidity)) / expectedLiquidity; // U:[LIM-3]

return (U_WAD < U_2_WAD) ? ((U_2_WAD - U_WAD) * expectedLiquidity) / WAD : 0; // U:[LIM-3]
if (isBorrowingMoreU2Forbidden && expectedLiquidity != 0) {
uint256 minAvailableLiquidity = expectedLiquidity - expectedLiquidity * U_2_WAD / WAD;
return availableLiquidity > minAvailableLiquidity ? availableLiquidity - minAvailableLiquidity : 0;
} else {
return availableLiquidity; // U:[LIM-3]
return availableLiquidity;
}
}
}
Loading

0 comments on commit b3999d8

Please sign in to comment.