Core Contracts

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

Incorrect amount returned in ReserveLibrary's getNormalizedDebt Function Leading to Debt Accounting Issues

Summary

The getNormalizedDebt function in ReserveLibrary returns inconsistent values depending on the time delta. When timeDelta < 1, it returns the actual borrowed amount (reserve.totalUsage), but for any other case, it returns only the usage index without scaling it with the total borrowed amount, leading to incorrect debt calculations throughout the protocol.

Vulnerability Details

The issue lies in how the normalized debt is calculated;

/**
* @notice Gets the normalized debt of the reserve.
* @param reserve The reserve data.
@> * @return The normalized debt (in underlying asset units).
*/
function getNormalizedDebt(
ReserveData storage reserve,
ReserveRateData storage rateData
) internal view returns (uint256) {
uint256 timeDelta = block.timestamp -
uint256(reserve.lastUpdateTimestamp);
if (timeDelta < 1) {
return reserve.totalUsage;
}
// @audit this returns usage index (RAY) instead of the debt in crvUSD
return
calculateCompoundedInterest(rateData.currentUsageRate, timeDelta)
.rayMul(reserve.usageIndex);
}

Even the comment The normalized debt (in underlying asset units). suggests that the amount returned should be in underlying asset units which is crvUSD and not RAY.

  1. For timeDelta < 1, it correctly returns reserve.totalUsage (actual borrowed amount in crvUSD)

  2. For timeDelta >= 1, it returns calculateCompoundedInterest(rateData.currentUsageRate, timeDelta).rayMul(reserve.usageIndex)

The second case only calculates an updated usage index but doesn't multiply it by the total borrowed amount. This is incorrect because:

  • The usage index (in RAY = 1e27) represents the cumulative interest factor

  • To get the actual debt, you need to multiply the usage index by the total borrowed amount

  • This affects debt calculations throughout the protocol since LendingPool and DebtToken rely on this function

This is significant issue since the getNormalizedDebt function is used in other functions such as updateInterestRatesAndLiquidity, getBorrowRate and getLiquidityRate which are used or may be used (therefore it brings incorrect accounting).

Impact

  • Incorrect debt accounting throughout the protocol

  • Affects liquidation calculations since health factors depend on debt amounts

  • Interest accrual calculations become inaccurate

  • Could lead to under/over collection of debt payments

Tools Used

Manual review

Recommendations

function getNormalizedDebt(
ReserveData storage reserve,
ReserveRateData storage rateData
) internal view returns (uint256) {
uint256 timeDelta = block.timestamp - uint256(reserve.lastUpdateTimestamp);
if (timeDelta < 1) {
return reserve.totalUsage;
}
return calculateCompoundedInterest(rateData.currentUsageRate, timeDelta)
.rayMul(reserve.usageIndex)
.rayMul(reserve.totalUsage); // @audit-info Scale by total borrowed amount
}
Updates

Lead Judging Commences

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

getNormalizedDebt returns totalUsage (amount) instead of usageIndex (rate) when timeDelta < 1, breaking interest calculations across the protocol

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

getNormalizedDebt returns totalUsage (amount) instead of usageIndex (rate) when timeDelta < 1, breaking interest calculations across the protocol

Support

FAQs

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

Give us feedback!