Core Contracts

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

Incorrect Prime Rate Update Causing Cascading Effects on Borrow Rates, Reserve Indices, and System Stability

Summary

A critical vulnerability exists in the setPrimeRate() function within the LendingPool contract due to the failure to proportionately update baseRate, optimalRate, and maxRate alongside the primeRate when changes occur. This oversight leads to incorrect borrow rate calculations, which propagate through the reserve lending system, ultimately affecting liquidity indices, borrower debt accumulation, and system stability. The cascading effects of this flaw significantly degrade the protocol’s risk management and capital efficiency.

Vulnerability Details

When the LendingPool is initialized, the following proportional relationships between the primeRate and the other interest rate parameters are established:

LendingPool.sol#L205-L209

// 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

However, when setPrimeRate() is called, only the primeRate is updated, while the baseRate, optimalRate, and maxRate remain at their initial values:

ReserveLibrary.sol#L399-L414

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);
}

Since the baseRate, optimalRate, and maxRate are not updated proportionately, their values may become disconnected from the prime rate. When this change propagates to the updateInterestRatesAndLiquidity() function, it triggers incorrect borrow rate calculations.

ReserveLibrary.sol#L218-L225

rateData.currentUsageRate = calculateBorrowRate(
rateData.primeRate,
rateData.baseRate,
rateData.optimalRate,
rateData.maxRate,
rateData.optimalUtilizationRate,
utilizationRate
);

Cascading Effects of Incorrect Borrow Rate

Step 1: Incorrect Calculation of the Borrow Rate (rateData.currentUsageRate)

  • The calculateBorrowRate() function depends on primeRate, baseRate, optimalRate, and maxRate. However, since only primeRate is updated, the mismatch causes the borrow rate to be improperly calculated.

  • If the borrow rate is too low, the protocol underestimates debt accumulation, leading to liquidity risks. Conversely, if the rate is too high, it causes unnecessary borrower liquidation events.

Step 2: Propagation to Reserve and Usage Indices

  • The incorrect borrow rate directly impacts the usage index through the updateReserveInterests() function:

ReserveLibrary.sol#L138-L142

reserve.usageIndex = calculateUsageIndex(
rateData.currentUsageRate,
timeDelta,
reserve.usageIndex
);
  • The usage index represents the protocol’s cumulative debt growth. A higher-than-expected usage index inflates the perceived borrower debt, while a lower usage index underestimates the protocol’s exposure to risk.

Step 3: Incorrect Debt Calculations in Normalized Debt Estimation

  • The incorrect usage index affects many calculations dependent on it.

LendingPool.sol#L609-L611

function getNormalizedDebt() external view returns (uint256) {
return reserve.usageIndex;
}
  • If the usage index is too high, borrowers face higher repayment obligations, increasing the likelihood of defaults and liquidation events. If it is too low, the protocol underestimates its debt exposure, leading to systemic risk.

Step 4: Impact on Liquidity Rate

  • Additionally, it also affects the determination of the current liquidity rate (rateData.currentLiquidityRate), which influences how liquidity providers are rewarded for their deposits via the linearly growing reserve.liquidityIndex. Improper rewards can discourage liquidity providers, leading to capital outflows.

ReserveLibrary.sol#L131-L135

rateData.currentLiquidityRate = calculateLiquidityRate(
utilizationRate,
rateData.currentUsageRate,
rateData.protocolFeeRate,
totalDebt
);

Impact

  • Inaccurate Borrower Obligations: Borrowers may face unrealistic repayment requirements or insufficient debt monitoring, leading to either unexpected defaults or protocol under-collateralization.

  • Mismanaged Risk Exposure: The protocol’s estimation of risk becomes unreliable, increasing systemic vulnerability to liquidity crises or insufficient capital reserves.

  • Suboptimal Rewards for Liquidity Providers: The incorrect calculation of liquidity rates discourages deposits and affects the protocol’s ability to maintain sufficient liquidity.

Tools Used

Manual

Recommendations

Consider refactoring as follows:

ReserveLibrary.sol#L399-L414

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;
+ rateData.baseRate = newPrimeRate.percentMul(25_00); // Update baseRate as 25% of primeRate
+ rateData.optimalRate = newPrimeRate.percentMul(50_00); // Update optimalRate as 50% of primeRate
+ rateData.maxRate = newPrimeRate.percentMul(400_00); // Update maxRate as 400% of primeRate
updateInterestRatesAndLiquidity(reserve, rateData, 0, 0);
emit PrimeRateUpdated(oldPrimeRate, newPrimeRate);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 2 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 2 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.