Omniscia Steadefi Audit

LendingPoolConfig Manual Review Findings

LendingPoolConfig Manual Review Findings

LPC-01M: Arbitrary Adjustment of Interest Rate Model

TypeSeverityLocation
Centralization ConcernLendingPoolConfig.sol:L113-L125

Description:

The LendingPoolConfig::updateInterestRateModel function permits the contract's owner to arbitrarily set the contract's various interest rate variables.

Example:

contracts/lending/LendingPoolConfig.sol
105/**
106 * Updates lending pool interest rate model variables, callable only by owner
107 @param _baseRate // Base interest rate when utilization rate is 0 in 1e18
108 @param _multiplier // Multiplier of utilization rate that gives the slope of the interest rate in 1e18
109 @param _jumpMultiplier // Multiplier after hitting a specified utilization point (kink2) in 1e18
110 @param _kink1 // Utilization point at which the interest rate is fixed in 1e18
111 @param _kink2 // Utilization point at which the jump multiplier is applied in 1e18
112*/
113function updateInterestRateModel(
114 uint256 _baseRate,
115 uint256 _multiplier,
116 uint256 _jumpMultiplier,
117 uint256 _kink1,
118 uint256 _kink2
119) external onlyOwner {
120 baseRate = _baseRate;
121 multiplier = _multiplier;
122 jumpMultiplier = _jumpMultiplier;
123 kink1 = _kink1;
124 kink2 = _kink2;
125}

Recommendation:

We advise the function to be solely invoke-able by a governance entity, preferably sitting behind a timelock function to permit existing users of the system to adequately react to a lending pool configurational change by i.e. repaying their loans while interest rates are low etc.

Alleviation (4325253d6de0ea91c1e9fb9e01d2e7e98f3d83a9):

The Steadefi team has stated that they will implement a path to decentralization for all the deployed contracts within their ecosystem by utilizing a combination of a governance mechanism and a Timelock contract. As such, we consider this exhibit alleviated based on the fact that the Steadefi team will implement a decentralization path in the near future.

LPC-02M: Inexplicable Per Second Interest Rate Calculation

TypeSeverityLocation
Mathematical OperationsLendingPoolConfig.sol:L67

Description:

The LendingPoolConfig::interestRatePerSecond function offsets the calculated interest rate by the SAFE_MULTIPLIER (1e18) prior to dividing by the SECONDS_PER_YEAR.

Impact:

The interestRatePerSecond is solely utilized in LendingPool::_pendingInterest where the "double" 1e18 offset is in fact normalized via two divisions with SAFE_MULTIPLIER. As such, this does not represent an active error in the mathematical calculations of the protocol. In any case, we advise this to be corrected optimizing the code's gas cost and minimizing its error surface.

Example:

contracts/lending/LendingPoolConfig.sol
60/**
61 * Return the interest rate based on the utilization rate, per second
62 * @param _debt Total borrowed amount
63 * @param _floating Total available liquidity
64 * @return ratePerSecond Current interest rate per second in 1e18
65*/
66function interestRatePerSecond(uint256 _debt, uint256 _floating) external view returns (uint256) {
67 return _calculateInterestRate(_debt, _floating) * SAFE_MULTIPLIER / SECONDS_PER_YEAR;
68}

Recommendation:

We advise the multiplication to be omitted as it represents an incorrect accuracy increase. An interest rate defined w/ 18 decimal places will retain the same accuracy when divided by the SECONDS_PER_YEAR, meaning that multiplication by SAFE_MULTIPLIER is incorrect.

Alleviation (4325253d6de0ea91c1e9fb9e01d2e7e98f3d83a9):

The multiplication by SAFE_MULTIPLIER has been removed as advised, ensuring that the LendingPoolConfig::interestRatePerSecond function yields a value with an expectable precision.

LPC-03M: Inexistent Sanitization of Pool Configuration

TypeSeverityLocation
Input SanitizationLendingPoolConfig.sol:L34-L46, L113-L125

Description:

The lending pool's constructor and configuration function (LendingPoolConfig::updateInterestRateModel) do not apply any sanitization to their input arguments.

Impact:

A misconfigured lending pool configuration can lead to undesirable arbitrage attacks, ultimately hurting the pool's and consequently protocol's health.

Example:

contracts/lending/LendingPoolConfig.sol
27/**
28 * @param _baseRate // Base interest rate when utilization rate is 0 in 1e18
29 * @param _multiplier // Multiplier of utilization rate that gives the slope of the interest rate in 1e18
30 * @param _jumpMultiplier // Multiplier after hitting a specified utilization point (kink2) in 1e18
31 * @param _kink1 // Utilization point at which the interest rate is fixed in 1e18
32 * @param _kink2 // Utilization point at which the jump multiplier is applied in 1e18
33*/
34constructor(
35 uint256 _baseRate,
36 uint256 _multiplier,
37 uint256 _jumpMultiplier,
38 uint256 _kink1,
39 uint256 _kink2
40) {
41 baseRate = _baseRate;
42 multiplier = _multiplier;
43 jumpMultiplier = _jumpMultiplier;
44 kink1 = _kink1;
45 kink2 = _kink2;
46}

Recommendation:

We advise the input values to be sanitized per the business requirements of Steadefi. Some example sanitizations include mandating that _jumpMultiplier is greater-than _multiplier (based on LendingPoolConfig::_calculateInterestRate), ensuring that the _kink1 / _kink2 thresholds are at most equal to 1e18 (100% utilization) as well as the _baseRate to be greater-than-or-equal-to a "zero" interest rate (i.e. 1e18).

Alleviation (4325253d6de0ea91c1e9fb9e01d2e7e98f3d83a9):

The constructor of the LendingPoolConfig contract now makes use of the LendingPoolConfig::updateInterestRateModel function which has had multiple require checks introduced meant to sanitize incoming interest rate model adjustments. As a result, we consider this exhibit fully alleviated given that all interest rate model adjustments are sanitized as being valid.

LPC-04M: Incorrect Interest Rate Calculation

TypeSeverityLocation
Logical FaultLendingPoolConfig.sol:L89, L95, L100

Description:

The LendingPoolConfig::_calculateInterestRate function will yield an incorrectly calculated interest rate if the utilizationRate is exactly equal to kink2 which will be much higher than expected.

Impact:

The interest rate calculated when utilizationRate is equivalent to kink2 is significantly higher than the expected one based on the calculations of the utilizationRate > kink2 conditional. Given that the utilization rate can be arbitrarily adjusted by a party using flash-loans, they can exploit this interest rate within the system to create artificial arbitrage opportunities.

Example:

contracts/lending/LendingPoolConfig.sol
72/**
73 * Return the interest rate based on the utilization rate
74 * @param _debt Total borrowed amount
75 * @param _floating Total available liquidity
76 * @return rate Current interest rate in 1e18
77*/
78function _calculateInterestRate(uint256 _debt, uint256 _floating) internal view returns (uint256) {
79 if (_debt == 0 && _floating == 0) return 0;
80
81 uint256 total = _debt + _floating;
82 uint256 utilizationRate = _debt * SAFE_MULTIPLIER / total;
83
84 // calculate borrow rate for slope up to kink 1
85 uint256 rate = baseRate + (utilizationRate * multiplier / SAFE_MULTIPLIER);
86
87 // If utilization above kink2, return a higher interest rate
88 // (base + rate + excess utilization above kink 2 * jumpMultiplier)
89 if (utilizationRate > kink2) {
90 return baseRate + (kink1 * multiplier / SAFE_MULTIPLIER)
91 + ((utilizationRate - kink2) * jumpMultiplier / SAFE_MULTIPLIER);
92 }
93
94 // If utilization between kink1 and kink2, rates are flat
95 if (kink1 < utilizationRate && utilizationRate < kink2) {
96 return baseRate + (kink1 * multiplier / SAFE_MULTIPLIER);
97 }
98
99 // If utilization below kink1, return rate
100 return rate;
101}

Recommendation:

We advise the conditional of L95 to become inclusive in its comparison between utilizationRate and kink2, ensuring that the edge case of utilizationRate == kink2 is adequately handled by the function.

Alleviation (4325253d6de0ea91c1e9fb9e01d2e7e98f3d83a9):

The LendingPoolConfig::_calculateInterestRate function's referenced conditional was corrected to be inclusive with kink2 on the lower bound, optimizing its execution cost and correcting the rate's abnormal momentary increase that was present.