Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

Incorrect Use of `primeRate` Instead of `optimalRate` in Borrow Rate Calculation

Summary

The protocol incorrectly uses primeRate instead of optimalRate when calculating the borrow rate. optimalRate is designed to balance borrowing costs and liquidity incentives at the target utilization rate, while primeRate may not dynamically adjust to utilization changes. This mistake can result in suboptimal interest rate behavior, affecting both lenders and borrowers.

Vulnerability Details

The primeRate parameter is a reference interest rate (similar to central bank rates). It is unique and may act as an external benchmark. As the RAAC protocol uses a jump model similar to the AAVE protocol, we will have double slopes in the threshold (optimal) utilization point. Considering the different rates (slopes) here, we can infer that this model has two different behaviors in these situations:

  1. Below optimal utilization (U < U_optimal):
    The borrow rate increases gradually from baseRate to OptimalRate.

  2. Above optimal utilization (U > U_optimal):
    The borrow rate spikes more aggressively, moving towards maxRate.

Mathematically spoken, we can formulate this method as:

where the are the borrowRate, baseRate, optimalRate, optimalUtilizationRate, and maxRate respectively.

Now if we look at the borrow rate calculation inside the ReserveLibrary, we can see the model uses the primeRate instead of the optimalRate:

function calculateBorrowRate(
uint256 primeRate,
uint256 baseRate,
uint256 optimalRate,
uint256 maxRate,
uint256 optimalUtilizationRate,
uint256 utilizationRate
) internal pure returns (uint256) {
if (primeRate <= baseRate || primeRate >= maxRate || optimalRate <= baseRate || optimalRate >= maxRate) {
revert InvalidInterestRateParameters();
}
uint256 rate;
if (utilizationRate <= optimalUtilizationRate) {
uint256 rateSlope = primeRate - baseRate;
uint256 rateIncrease = utilizationRate.rayMul(rateSlope).rayDiv(optimalUtilizationRate);
rate = baseRate + rateIncrease;
} else {
uint256 excessUtilization = utilizationRate - optimalUtilizationRate;
uint256 maxExcessUtilization = WadRayMath.RAY - optimalUtilizationRate;
uint256 rateSlope = maxRate - primeRate;
uint256 rateIncrease = excessUtilization.rayMul(rateSlope).rayDiv(maxExcessUtilization);
rate = primeRate + rateIncrease;
}
return rate;
}

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/libraries/pools/ReserveLibrary.sol#L270

Moreover, the borrow rate calculation doesn't even use the optimalRate in the calculation. However, the optimalRate should be used instead of the primeRate and all the other rates should be compared and multiplied by the primeRate.
Let's compare this model with the Aave model and also the model with optimalRate graphically:

If the diagram is not displayed here, please refer to this Google Drive link

As it is clear from the graph, the current model (red line with the rates mentioned and initialized inside the lending pool) has a slope higher than the other models which means that the borrowing cost increases more rapidly even at lower utilization levels (which is not intended to). The protocol should keep the first slope more stable (with a very low slope) till the optimal utilization point. In other words, the current implementation of RAAC has a steeper slope, borrowers face higher interest rates even when utilization is low to moderate.

Impact

Lenders may experience high withdrawal failures if borrow demand surpasses available liquidity, as interest rates fail to regulate borrowing effectively. Also, Borrowers may either pay excessively high interest in low-utilization conditions or enjoy artificially low rates when liquidity is scarce. Furthermore, this miscalculation reduces capital efficiency and increases protocol instability.

POC

Expected Behavior (Using optimalRate):

When utilization is below the optimalUtilizationRate, borrowing costs remain relatively stable.
As utilization crosses optimalUtilizationRate, the interest rate increases sharply to prevent liquidity exhaustion.

Actual Behavior (Using primeRate):

The protocol applies primeRate, which is a static base rate and does not adjust dynamically based on utilization changes.
If utilization is high, borrowing rates may not rise fast enough and this results in the risk of liquidity depletion.
If utilization is low, borrowers might pay unnecessarily high rates which will discourage borrowing and reduce protocol usage.

Tools Used

Manual

Recommendations

Consider replacing primeRate with optimalRate in borrow rate calculations.

if (utilizationRate <= optimalUtilizationRate) {
- uint256 rateSlope = primeRate - baseRate;
+ uint256 rateSlope = optimalRate - baseRate;
uint256 rateIncrease = utilizationRate.rayMul(rateSlope).rayDiv(optimalUtilizationRate);
rate = baseRate + rateIncrease;
} else {
uint256 excessUtilization = utilizationRate - optimalUtilizationRate;
uint256 maxExcessUtilization = WadRayMath.RAY - optimalUtilizationRate;
- uint256 rateSlope = maxRate - primeRate;
+ uint256 rateSlope = maxRate - optimalRate;
uint256 rateIncrease = excessUtilization.rayMul(rateSlope).rayDiv(maxExcessUtilization);
- rate = primeRate + rateIncrease;
+ rate = optimalRate + rateIncrease;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

ReserveLibrary::calculateBorrowRate uses primeRate instead of optimalRate in first slope calculation, causing borrowers to pay double the intended interest at optimal utilization

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

ReserveLibrary::calculateBorrowRate uses primeRate instead of optimalRate in first slope calculation, causing borrowers to pay double the intended interest at optimal utilization

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!