Core Contracts

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

Incorrect Debt Amount Transfer During finalizeLiquidation via Stability Pool

Summary

In the finalizeLiquidation function, when liquidating a borrower's position, the debt amount is incorrectly transferred due to improper scaling during the burn process. The function calculates the total debt using rayMul on the user's scaledDebtBalance with the usageIndex. However, when passed to the burn function, the value is adjusted to match the user's debt token balance rather than their actual borrowed amount, leading to an incomplete liquidation.

Vulnerability Details

Issue

  • In finalizeLiquidation, the debt is determined using:

    uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
  • This userDebt amount is then passed to the burn function, but if the user's debt token balance is lower than this amount, the function limits the burn amount to the user's debt token balance and returns this reduced amount as amountScaled.

    (uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
    IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
  • Back in the lending pool contract, the liquidation process relies on the returned amountScaled value instead of the actual userDebt, leading to an incorrect debt transfer:

    IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);

Root Cause

  • The burn function does not always process the full user debt and caps it to the user's existing debt token balance instead of the total borrowed amount.

  • The finalizeLiquidation function then uses this reduced value (amountScaled) instead of the full userDebt, resulting in a partial liquidation rather than a full position closure.

Example Scenario

  1. A user borrows 1000 tokens but only has 500 debt tokens in their balance.

  2. During liquidation, the contract attempts to burn 1000 tokens but only 500 are available, so the burn function caps the burn amount at 500 tokens.

  3. As a result, amountScaled = 500, which is less than the actual borrowed debt (1000 tokens).

  4. The lending pool then transfers only 500 tokens instead of the full 1000 tokens, leaving the liquidation incomplete.

Impact

  • Incomplete Liquidation:

    • The borrower still owes debt even after liquidation because only a portion of their debt is repaid.

    • The lending pool's accounting becomes inconsistent, which may lead to incorrect debt tracking and potential financial loss for the protocol.

Tools Used

  • Manual Code Review and Contract Behavior Analysis

Recommendations

To ensure complete liquidation, the finalizeLiquidation function should use userDebt instead of amountScaled when transferring funds from the stability pool:

Update the transfer function as follows:

// Use the correct total debt amount for the transfer
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, userDebt);

Conclusion

By using userDebt instead of amountScaled, the contract will correctly pay off the full outstanding debt rather than a partial amount, ensuring complete liquidation and accurate debt tracking.

Updates

Lead Judging Commences

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

LendingPool::finalizeLiquidation passes normalized userDebt to DebtToken::burn which compares against scaled balance, causing incomplete debt clearance while taking all collateral

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

LendingPool::finalizeLiquidation passes normalized userDebt to DebtToken::burn which compares against scaled balance, causing incomplete debt clearance while taking all collateral

Support

FAQs

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

Give us feedback!