01. Relevant GitHub Links
02. Summary
Two internal functions, getBorrowRate
and getLiquidityRate
, return rates based on an incorrect calculation of totalDebt
. Instead of providing the actual total debt, they rely on getNormalizedDebt
, which returns the usageIndex or an incorrect value. This leads to entirely wrong rate computations. Additionally, getLiquidityRate
uses rateData.currentUsageRate
rather than an updated value from getBorrowRate
, further compounding inaccuracies.
03. Vulnerability Details
When these functions calculate the utilization rate, they use getNormalizedDebt
. However, getNormalizedDebt
is designed to compute debt growth over time and ends up returning the usageIndex
(multiplied by compounded interest) rather than the current total debt. This results in an incorrect utilizationRate
. Consequently, calculateBorrowRate
and calculateLiquidityRate
produce misleading values.
* @notice Gets the current borrow rate of the reserve.
* @param reserve The reserve data.
* @param rateData The reserve rate parameters.
* @return The current borrow rate (in RAY).
*/
function getBorrowRate(ReserveData storage reserve,ReserveRateData storage rateData) internal view returns (uint256) {
@> uint256 totalDebt = getNormalizedDebt(reserve, rateData);
uint256 utilizationRate = calculateUtilizationRate(reserve.totalLiquidity, totalDebt);
return calculateBorrowRate(rateData.primeRate, rateData.baseRate, rateData.optimalRate, rateData.maxRate, rateData.optimalUtilizationRate, utilizationRate);
}
* @notice Gets the current liquidity rate of the reserve.
* @param reserve The reserve data.
* @param rateData The reserve rate parameters.
* @return The current liquidity rate (in RAY).
*/
function getLiquidityRate(ReserveData storage reserve,ReserveRateData storage rateData) internal view returns (uint256) {
@> uint256 totalDebt = getNormalizedDebt(reserve, rateData);
uint256 utilizationRate = calculateUtilizationRate(reserve.totalLiquidity, totalDebt);
return calculateLiquidityRate(utilizationRate, rateData.currentUsageRate, rateData.protocolFeeRate, totalDebt);
}
Furthermore, because getLiquidityRate
directly uses rateData.currentUsageRate
instead of retrieving the latest borrow rate, it also fails to reflect the most recent state. These errors can cause incorrect interest distributions within the system if these functions are later utilized in new or updated contracts.
04. Impact
Currently, both functions are declared as internal and are not actively used. However, they exist in a core library (ReserveLibrary) and may be incorporated in future protocol updates or external services. If used as-is, the system could miscalculate borrowers’ and depositors’ rates, leading to inconsistent interest accrual and potential financial imbalances.
05. Tools Used
Manual Code Review and Foundry
07. Recommended Mitigation
/**
* @notice Gets the current borrow rate of the reserve.
* @param reserve The reserve data.
* @param rateData The reserve rate parameters.
* @return The current borrow rate (in RAY).
*/
function getBorrowRate(ReserveData storage reserve,ReserveRateData storage rateData) internal view returns (uint256) {
- uint256 totalDebt = getNormalizedDebt(reserve, rateData);
+ uint256 totalDebt = reserve.totalUsage
uint256 utilizationRate = calculateUtilizationRate(reserve.totalLiquidity, totalDebt);
return calculateBorrowRate(rateData.primeRate, rateData.baseRate, rateData.optimalRate, rateData.maxRate, rateData.optimalUtilizationRate, utilizationRate);
}
/**
* @notice Gets the current liquidity rate of the reserve.
* @param reserve The reserve data.
* @param rateData The reserve rate parameters.
* @return The current liquidity rate (in RAY).
*/
function getLiquidityRate(ReserveData storage reserve,ReserveRateData storage rateData) internal view returns (uint256) {
+ uint256 currentUsageRate = getBorrowRate()
- uint256 totalDebt = getNormalizedDebt(reserve, rateData);
+ uint256 totalDebt = reserve.totalUsage
uint256 utilizationRate = calculateUtilizationRate(reserve.totalLiquidity, totalDebt);
- return calculateLiquidityRate(utilizationRate, rateData.currentUsageRate, rateData.protocolFeeRate, totalDebt);
+ return calculateLiquidityRate(utilizationRate, currentUsageRate, rateData.protocolFeeRate, totalDebt);
}