Core Contracts

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

Silent Fund Loss in LendingPool Repayment

Summary

The LendingPool's repayment mechanism can silently accept and lock user funds when they attempt to repay more than their outstanding debt. While there is a view function to check debt amounts, the contract fails to properly handle overpayments, leading to permanent loss of user funds.

Vulnerability Details

The vulnerability exists in the _repay function:

function _repay(uint256 amount, address onBehalfOf) internal {
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
// Still transfers the full amount despite capping the repayment
IERC20(reserve.reserveAssetAddress).safeTransferFrom(
msg.sender,
reserve.reserveRTokenAddress,
amount // Uses full amount instead of actualRepayAmount
);
}

While there is a getUserDebt() view function available:

function getUserDebt(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
return user.scaledDebtBalance.rayMul(reserve.usageIndex);
}

The issues are:

  • The contract accepts the full payment amount even when it exceeds the debt

  • No refund mechanism for excess payments

  • No events emitted to notify users of overpayment

  • The debt query function returns a ray-scaled value that requires additional calculation to determine the actual token amount needed

Impact

  • Direct Financial Loss: Users who accidentally overpay permanently lose their excess funds

  • Silent Failures: No feedback when overpayment occurs

  • Trapped Funds: Excess payments become permanently locked in the contract

Tools Used

  • Manual code review

Recommendations

  • Immediate Fixes:

function _repay(uint256 amount, address onBehalfOf) internal {
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
// Only transfer what's needed
IERC20(reserve.reserveAssetAddress).safeTransferFrom(
msg.sender,
reserve.reserveRTokenAddress,
actualRepayAmount
);
// Notify if there was an overpayment attempt
if (amount > actualRepayAmount) {
emit RepaymentOverage(msg.sender, amount - actualRepayAmount);
}
}

Additional Improvements:

  • Add a helper function that returns the exact token amount needed for repayment:

function getRepaymentAmount(address user) external view returns (uint256) {
return getUserDebt(user).rayDiv(reserve.usageIndex);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!