Core Contracts

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

Inaccurate interest-rate and liquidity calculations due to omitted `updateInterestRatesAndLiquidity()` call in `setProtocolFeeRate()`

Summary

The setProtocolFeeRate() updates the protocol fee rate but fails to invoke the updateInterestRatesAndLiquidity() function afterward. This oversight result in incorrect calculations for interest rates and liquidity downstream.

Vulnerability Details

The setProtocolFeeRate() function in LendingPool is implemented as follows:

function setProtocolFeeRate(uint256 newProtocolFeeRate) external onlyOwner {
>> rateData.protocolFeeRate = newProtocolFeeRate;
}

This function directly updates the rateData.protocolFeeRate to the given newProtocolFeeRate but fails to invoke updateInterestRatesAndLiquidity() immediately after.

This omitted updateInterestRatesAndLiquidity() is used update the interest rates and liquidity based on the latest reserve state.

function updateInterestRatesAndLiquidity(ReserveData storage reserve,ReserveRateData storage rateData,uint256 liquidityAdded,uint256 liquidityTaken) internal {
// @audit-info 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);
// @audit-info Calculates utilization rate based on the updated totalLiquidity
>> uint256 utilizationRate = calculateUtilizationRate(reserve.totalLiquidity, reserve.totalUsage);
---SNIP---
// @audit-info Updates current liquidity rate based on the calculated utilizationRate
rateData.currentLiquidityRate = calculateLiquidityRate(
>> utilizationRate,
rateData.currentUsageRate,
>> rateData.protocolFeeRate, // @audit-issue Problem stems here
totalDebt
);
// @audit-issue Updates the reserve interests based on that currentLiquidityRate
>> updateReserveInterests(reserve, rateData);
emit InterestRatesUpdated(rateData.currentLiquidityRate, rateData.currentUsageRate);
}

As seen, it internally calls calculateLiquidityRate() to determine the portion of the gross liquidity rate allocated to the protocol.

However, when the rateData.protocolFeeRate is updated, the rateData.currentLiquidityRate remains stale as it is not updated.

Now when a user calls a function such as deposit(), they will provide more liquidity to the protocol. Notice that the deposit() function inernally invokes updateInterestRatesAndLiquidity() to update the interest rates and liquidity based on the latest reserve state:

function deposit(ReserveData storage reserve,ReserveRateData storage rateData,uint256 amount,address depositor) internal returns (uint256 amountMinted) {
---SNIP---
// Update the total liquidity and interest rates
>> updateInterestRatesAndLiquidity(reserve, rateData, amount, 0);
---SNIP---
}

Since liquidity is increased, the reserve.totalLiquidity will be increased in updateInterestRatesAndLiquidity(). Then after, utilizationRate will be calculated based on this new liquidity amount and finally, the rateData.currentLiquidityRate will be calculated using this utilizationRate and protocolFeeRate.

But, remember that rateData.protocolFeeRate was also updated. Only that, it was not used to adjust the rateData.currentLiquidityRate based on liquidity at that time. Therefore, when the calculateLiquidityRate() is invoked with this new protocolFeeRate, the obtained currentLiquidityRate does not accurately reflect the state of the contract.

Also notice that after calculateLiquidityRate() has been called and rateData.currentLiquidityRate set, the function proceeds to invoke updateReserveInterests(reserve, rateData) which uses this rateData.currentLiquidityRate to set a new liquidity index using linear interest:

// Update liquidity index using linear interest
reserve.liquidityIndex = calculateLiquidityIndex(
>> rateData.currentLiquidityRate,
timeDelta,
reserve.liquidityIndex
);

In contrast to this, notice how setPrimeRate() works:

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

It calls ReserveLibrary.setPrimeRate() which does the following:

function setPrimeRate( ReserveData storage reserve,ReserveRateData storage rateData,uint256 newPrimeRate) internal {
---SNIP--
// @audit-info updates the primeRate here
rateData.primeRate = newPrimeRate;
// @audit-info Then proceeds to update the contract state immediately
>> updateInterestRatesAndLiquidity(reserve, rateData, 0, 0);
emit PrimeRateUpdated(oldPrimeRate, newPrimeRate);
}

This final call after setting a new primeRate ensures that the contract state is harmonized to reflect this new change so that subsequent operations may operate with updated values.


Impact

When the protocol fee rate is updated, the interest rates and liquidity calculations do not reflect this change, leading to potential inaccuracies in user transactions.

Tools Used

Manual Review

Recommendations

Modify setProtocolFeeRate() to include a call to updateInterestRatesAndLiquidity() immediately after updating the protocolFeeRate.

function setProtocolFeeRate(uint256 newProtocolFeeRate) external onlyOwner {
rateData.protocolFeeRate = newProtocolFeeRate;
+ updateInterestRatesAndLiquidity(reserve, rateData, 0, 0);
}
Updates

Lead Judging Commences

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

ReserveLibrary fails to update reserve state before changing rate parameters (prime rate, protocol fee rate), causing new rates to be applied retroactively to interest since last update

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

ReserveLibrary fails to update reserve state before changing rate parameters (prime rate, protocol fee rate), causing new rates to be applied retroactively to interest since last update

Support

FAQs

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

Give us feedback!