Core Contracts

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

Liquidity and usage Indexes are incorrectly updated after `primeRate` change, leading to retroactive interest miscalculation

Summary

The prime rate, a key factor in determining borrowing and lending rates, is updated by an admin-triggered Chainlink function. However, when the prime rate is changed, the liquidityIndex and usageIndex are updated incorrectly due to a missing state update before the new rate is applied.

This results in incorrect interest accrual calculations, affecting both borrowers and lenders, and leading to potential financial discrepancies.

Vulnerability Details

The primeRate can be changed when the admin request is fulfilled by Chainlink Functions. The response is processed in _processResponse hook calling the lendingPool.setPrimeRate with the new prime rate. The ReserveLibrary.setPrimeRate is invoked, which:

// ReserveLibrary.sol
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);
}
  • directly updates the rateData.primeRate

  • calls updateInterestRatesAndLiquidity, which :

    • i. computes new borrow (currentUsageRate) and liquidity (currentLiquidityRate) rates based on the new prime rate.

    • ii. calls updateReserveInterests(), which updates liquidityIndex and usageIndex.

function updateInterestRatesAndLiquidity(ReserveData storage reserve,ReserveRateData storage rateData,uint256 liquidityAdded,uint256 liquidityTaken) internal {
...
// Calculate utilization rate
uint256 utilizationRate = calculateUtilizationRate(reserve.totalLiquidity, reserve.totalUsage);
// Update current usage rate (borrow rate)
@> rateData.currentUsageRate = calculateBorrowRate(
rateData.primeRate,
rateData.baseRate,
rateData.optimalRate,
rateData.maxRate,
rateData.optimalUtilizationRate,
utilizationRate
);
// Update current liquidity rate
@> rateData.currentLiquidityRate = calculateLiquidityRate(
utilizationRate,
rateData.currentUsageRate,
rateData.protocolFeeRate,
totalDebt
);
// Update the reserve interests
@> updateReserveInterests(reserve, rateData);
emit InterestRatesUpdated(rateData.currentLiquidityRate, rateData.currentUsageRate);
}

The liquidityIndex and usageIndex represent the accumulated interest for lenders and borrowers over time. These values should be updated before applying a new prime rate, ensuring that past interest accruals are calculated correctly.

However, in the current implementation, the new prime rate is applied before the indexes are updated.

  • Interest accrual uses timeDelta = block.timestamp - lastUpdateTimestamp.

  • If a prime rate update occurs after a long delay, it retroactively applies to the entire timeDelta, distorting historical interest calculations.

  • This disproportionately affects borrowers and lenders depending on whether the new prime rate is higher or lower.

Impact

Incorrect borrower debt accumulation:

  • If the prime rate increased, borrowers pay more than they should for past interest accruals.

  • If the prime rate decreased, borrowers pay less than they should, leading to protocol revenue loss.

Incorrect lender (LP) yield calculations

  • Lenders (LPs) receive incorrect yield allocations, which could distort APY calculations.

Tools Used

Recommendations

Before applying the new prime rate, update the liquidity and usage indexes first to correctly reflect historical interest accruals.
This ensures that:

  • Old prime rate is used to finalize past interest accruals.

  • New prime rate takes effect only after updating historical indexes.

function setPrimeRate(uint256 newPrimeRate) external onlyPrimeRateOracle {
+ ReserveLibrary.updateReserveState(reserve, rateData);
ReserveLibrary.setPrimeRate(reserve, rateData, newPrimeRate);
}
Updates

Lead Judging Commences

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

setPrimeRate applies new rates retroactively by updating rates after changing primeRate, causing incorrect interest calculations for past periods

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

setPrimeRate applies new rates retroactively by updating rates after changing primeRate, causing incorrect interest calculations for past periods

Support

FAQs

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