Core Contracts

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

Incorrect Derived Rates Update Leading to Borrow Rate Calculation Failures

Summary

The setPrimeRate function in the ReserveLibrary fails to update derived interest rate parameters (baseRate, optimalRate, maxRate) after modifying the primeRate. This omission results in inconsistent interest rate calculations, potentially causing reverts in critical functions and disrupting protocol operations. Additionally, the maximum allowed change check for primeRate may not account for accrued interest due to an outdated reserve state.

Vulnerability Details

The primeRate is a foundational parameter influencing other rates (baseRate, optimalRate, maxRate). These derived rates are initialized as percentages of primeRate during contract deployment.

constructor(
address _reserveAssetAddress,
address _rTokenAddress,
address _debtTokenAddress,
address _raacNFTAddress,
address _priceOracleAddress,
uint256 _initialPrimeRate
) Ownable(msg.sender) {
//...
// Prime Rate
rateData.primeRate = uint256(_initialPrimeRate);
rateData.baseRate = rateData.primeRate.percentMul(25_00); // 25% of prime rate
rateData.optimalRate = rateData.primeRate.percentMul(50_00); // 50% of prime rate
rateData.maxRate = rateData.primeRate.percentMul(400_00); // 400% of prime rate
rateData.optimalUtilizationRate = WadRayMath.RAY.percentMul(80_00); // 80% in RAY (27 decimals)
rateData.protocolFeeRate = 0; // 0% in RAY (27 decimals)
//...
}

However, when primeRate is updated via setPrimeRate, the derived rates remain static.

function setPrimeRate( ReserveData storage reserve,ReserveRateData storage rateData,uint256 newPrimeRate) internal {
if (newPrimeRate < 1) revert PrimeRateMustBePositive();
uint256 oldPrimeRate = rateData.primeRate;
if (oldPrimeRate > 0) {
uint256 maxChange = oldPrimeRate.percentMul(500); // Max 5% change
uint256 diff = newPrimeRate > oldPrimeRate ? newPrimeRate - oldPrimeRate : oldPrimeRate - newPrimeRate;
if (diff > maxChange) revert PrimeRateChangeExceedsLimit();
}
rateData.primeRate = newPrimeRate;
updateInterestRatesAndLiquidity(reserve, rateData, 0, 0);
emit PrimeRateUpdated(oldPrimeRate, newPrimeRate);
}
  1. Invalid Rate Parameters: Subsequent calls to calculateBorrowRate may revert if the updated primeRate exceeds the outdated maxRate or falls below baseRate, violating function preconditions.

  2. Incorrect Interest Calculations: Even without reverts, the derived rates no longer reflect the intended percentages of the current primeRate, leading to miscalculated borrow and liquidity rates.

Proof of Concept (POC)

  1. Initial State:

    • primeRate = 1e26 (RAY)

    • baseRate = 1e26 * 25% = 2.5e25

    • maxRate = 1e26 * 400% = 4e26

  2. Admin Action:

    • Incrementally increases primeRate by 5% multiple times (allowed per the 5% max change check).

    • After several increments, primeRate reaches 4.2e26.

Result:

  • Outcome:

    • maxRate remains at 4e26 (400% of initial primeRate).

    • calculateBorrowRate checks primeRate <= baseRate || primeRate >= maxRate.

    • With primeRate = 4.2e26 and maxRate = 4e26, the condition primeRate >= maxRate is true, causing a revert.

// Initial primeRate = 1e26
// After several allowed 5% increases, primeRate becomes 4.2e26
// Call setPrimeRate with newPrimeRate = 4.2e26
// maxChange = 4e26 * 5% = 2e25 (5% of previous primeRate)
// diff = 4.2e26 - 4e26 = 2e25 (allowed)
// Update primeRate to 4.2e26 without adjusting maxRate (still 4e26)
// Later, when calculating borrow rate:
function calculateBorrowRate(...) {
require(!(primeRate >= maxRate), "Invalid parameters"); // Reverts here
}

Impact

  • Borrowing, deposit, and withdrawal functions relying on interest rate calculations may revert, halting user operations.

  • Incorrect interest rates lead to unfair borrowing costs or liquidity provider returns, undermining protocol trust.

Tools Used

Manual Review

Recommendations

Update Derived Rates in setPrimeRate:

function setPrimeRate( ReserveData storage reserve,ReserveRateData storage rateData,uint256 newPrimeRate) internal {
if (newPrimeRate < 1) revert PrimeRateMustBePositive();
uint256 oldPrimeRate = rateData.primeRate;
if (oldPrimeRate > 0) {
uint256 maxChange = oldPrimeRate.percentMul(500); // Max 5% change
uint256 diff = newPrimeRate > oldPrimeRate ? newPrimeRate - oldPrimeRate : oldPrimeRate - newPrimeRate;
if (diff > maxChange) revert PrimeRateChangeExceedsLimit();
}
rateData.primeRate = newPrimeRate;
// Recalculate derived rates
+ rateData.baseRate = newPrimeRate.percentMul(25_00); // 25%
+ rateData.optimalRate = newPrimeRate.percentMul(50_00); // 50%
+ rateData.maxRate = newPrimeRate.percentMul(400_00); // 400%
updateInterestRatesAndLiquidity(reserve, rateData, 0, 0);
emit PrimeRateUpdated(oldPrimeRate, newPrimeRate);
}
Updates

Lead Judging Commences

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

ReserveLibrary::setPrimeRate only updates primeRate without adjusting baseRate, optimalRate, and maxRate proportionally, breaking interest rate model and causing incorrect calculations

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

ReserveLibrary::setPrimeRate only updates primeRate without adjusting baseRate, optimalRate, and maxRate proportionally, breaking interest rate model and causing incorrect calculations

Support

FAQs

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

Give us feedback!