Core Contracts

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

`ReserveLibrary::setPrimeRate` only change `primeRate` but not `baseRate`, `optimalRate`, and `maxRate`

Summary

the protocol is utilizing dynamic interest rate model using prime rate as reference. the prime rate is a value derived from oracle, but when the prime rate is updated by oracle the baseRate, optimalRate, and maxRateare not updated accordingly leading to inaccurate state when calling updateInterestRatesAndLiquidity, a crucial function to determine the correct interest rate for entire protocol.

Vulnerability Details

when deploying LendingPoolcontract, the prime rate and other crucial rate are set in constructor:

LendingPool.sol#L205-L211

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

and when the RAACPrimeRateOracle contract updates the prime rate, this function is invoked inside LendingPool:

LendingPool.sol#L678-L680

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

now we should check how the newPrimeRate is handled inside the ReserveLibrary contract:

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

the function only check if the primeRate change are within 5% of its old value, after that the new value are getting updated inside rateData.primeRate.

the issue is the value rateData.baseRate, rateData.optimalRate and rateData.maxRateare not updated and still use 25%, 50% and 400% of the old value as their value.

why this crucial? because the updated and unupdated rate are now getting used inside updateInterestRatesAndLiquidityfunction to calculate few things:

ReserveLibrary.sol#L198-L239

function updateInterestRatesAndLiquidity(ReserveData storage reserve,ReserveRateData storage rateData,uint256 liquidityAdded,uint256 liquidityTaken) internal {
// Update total liquidity
if (liquidityAdded > 0) {
reserve.totalLiquidity = reserve.totalLiquidity + liquidityAdded.toUint128();
}
if (liquidityTaken > 0) {
if (reserve.totalLiquidity < liquidityTaken) revert InsufficientLiquidity();
reserve.totalLiquidity = reserve.totalLiquidity - liquidityTaken.toUint128();
}
uint256 totalLiquidity = reserve.totalLiquidity;
uint256 totalDebt = reserve.totalUsage;
uint256 computedDebt = getNormalizedDebt(reserve, rateData);
uint256 computedLiquidity = getNormalizedIncome(reserve, rateData);
// 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);
}

first, it calculate the currentUsageRate or borrow rate, by using old value in baseRate(min borrow rate), optimalRate(rate at optimal utilization), and maxRate(max borrow rate) the resulting value would be inaccurate representing the current borrow rate.

second, the liquidity rate are also affected by this because the currentUsageRate used are not accurate. the currentLiquidityRate would be inaccurate as the result of this.

the final issue is, when updateReserveInterestsare called by using inaccurate rateData, where this value would later be used to calculate reserve.liquidityIndex and reserve.usageIndex. as we knowthis two value are crucial in calculating core protocol state, example: how much the value of normalized RToken, DebtToken.

Impact

by using inaccurate rate after the prime rate updated, the protocol would be in a state where the interest index used would be different than what it should be. this discrepancy would potentially cause the user and protocol overall experience to degrade because the calculation would be inaccurate, example: leading to lender to loss potential reward, borrower debt would be inaccurate and can lead to liquidation.

Tools Used

manual review

Recommendations

function ReserveLibrary.setPrimeRateshould also update the value of rateData.baseRate, rateData.optimalRate and rateData.maxRate

Updates

Lead Judging Commences

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