Core Contracts

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

Double-Scaling of User Debt in Liquidation Process in `StabilityPool::liquidateBorrower` Leading to Incorrect Debt

Summary

In the StabilityPool contract, during the liquidation process, the user's debt is mistakenly scaled a second time. The LendingPool's getUserDebt function already returns the debt value after applying the necessary scaling (via ray multiplication with the reserve's usage index). However, in the liquidateBorrower function, this value is scaled again using the normalized debt, leading to an inflated debt value. This double scaling error results in incorrect debt calculations, potentially blocking legitimate liquidations.

Vulnerability Details

The issue arises in the liquidateBorrower function within the StabilityPool contract:

uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());

Here, lendingPool.getUserDebt(userAddress) returns a value already scaled by the reserve's usage index:

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

Since the returned value is fully scaled, applying WadRayMath.rayMul with the normalized debt again results in double scaling. This inflated debt value (scaledUserDebt) is then used for subsequent checks and operations in the liquidation process, such as verifying if the StabilityPool has enough crvUSD tokens to cover the debt and approve the lendingPool for using the crvUSD.

Key Issues:

  1. getUserDebt() returns debt already scaled by reserve.usageIndex (RAY-multiplied)

  2. Debt becomes (scaledDebt × usageIndex²) instead of correct (scaledDebt × usageIndex)

  3. Initial State:

    • Actual user debt: 100 crvUSD

    • reserve.usageIndex: 1.1e27 (10% interest)

    • getUserDebt() returns 110 crvUSD (100 × 1.1)

  4. Liquidation Call:

    userDebt = 110e18 // From getUserDebt()
    scaledUserDebt = 110e18 × 1.1e27 / 1e27 = 121e18 // 21% over real debt

Impact

Legitimate liquidation attempts may revert because the approve value is wrong, causing the following call to revert in the LendingPool::finalizeLiquidation() function

Tools Used

Manual Review

Recommendations

Remove the unnecessary second scaling in the liquidateBorrower function. The debt fetched from getUserDebt() is already properly scaled – no need to multiply it again.

  1. Before:

    uint256 userDebt = lendingPool.getUserDebt(userAddress);
    uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt()); // 🚨 Double scaling!
  2. After:

    uint256 scaledUserDebt = lendingPool.getUserDebt(userAddress); // ✅ Correct (already scaled)
Updates

Lead Judging Commences

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

StabilityPool::liquidateBorrower double-scales debt by multiplying already-scaled userDebt with usage index again, causing liquidations to fail

Support

FAQs

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

Give us feedback!