Core Contracts

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

Incorrect Unit Conversion in Debt Repayment Leads to Undervalued Loan Repayments

Summary

The _repay function incorrectly converts DebtToken balance from scaled to underlying units using rayDiv instead of rayMul, leading to incorrect debt calculations and potential repayment issues.

Vulnerability Details

In the repayment function:

// Get user's debt in scaled units (DebtToken units)
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
// @audit: Incorrect conversion - converts scaled to even more scaled units
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
// Comparison uses wrong units
uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;

The issue occurs because:

  • userDebt is already in scaled units (DebtToken units)

  • It needs to be converted to underlying asset units for comparison with amount

  • The code incorrectly uses rayDiv instead of rayMul

  • This makes the debt appear smaller than it actually is

Correct conversion should be:

uint256 userDebtInUnderlying = userDebt.rayMul(reserve.usageIndex);
uint256 actualRepayAmount = amount > userDebtInUnderlying ? userDebtInUnderlying : amount;

Impact

  • Incorrect calculation of user's actual debt amount

  • Users might be allowed to repay less than their actual debt

  • Comparison between amount and userScaledDebt uses incompatible units

  • Protocol's debt accounting becomes inaccurate

  • Could lead to bad debt accumulation

  • Users might be unable to fully repay their loans

Tools Used

Manual Review

Recommendations

function _repay(uint256 amount, address onBehalfOf) internal {
if (amount == 0) revert InvalidAmount();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
UserData storage user = userData[onBehalfOf];
// Update reserve state before repayment
ReserveLibrary.updateReserveState(reserve, rateData);
// Calculate the user's debt (for the onBehalfOf address)
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
- uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
+ uint256 userDebtInUnderlying = userDebt.rayMul(reserve.usageIndex);
// If amount is greater than userDebt, cap it at userDebt
- uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
+ uint256 actualRepayAmount = amount > userDebtInUnderlying ? userDebtInUnderlying : amount;
uint256 scaledAmount = actualRepayAmount.rayDiv(reserve.usageIndex);
// Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
// is not actualRepayAmount because we want to allow paying extra dust and we will then cap there
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
// Transfer reserve assets from the caller (msg.sender) to the reserve
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
reserve.totalUsage = newTotalSupply;
user.scaledDebtBalance -= amountBurned;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

LendingPool::_repay double scales the debt

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

LendingPool::_repay double scales the debt

Support

FAQs

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