Core Contracts

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

Double Scaling in Liquidate Borrower Function

Summary

The liquidateBorrower function in StabilityPool incorrectly scales the user's debt amount when retrieving it from the LendingPool. The getUserDebt function in LendingPool already returns a scaled value, but the liquidateBorrower function applies additional scaling using getNormalizedDebt. This results in an overestimated debt calculation, which may cause liquidation transactions to revert, even when the stability pool has sufficient funds.

Vulnerability Details

Affected Function:

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
// Get the user's debt from the LendingPool.
uint256 userDebt = lendingPool.getUserDebt(userAddress);
// @audit user debt is already scaled
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
if (userDebt == 0) revert InvalidAmount();
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
// Approve the LendingPool to transfer the debt amount
bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
if (!approveSuccess) revert ApprovalFailed();
// Update lending pool state before liquidation
lendingPool.updateState();
// Call finalizeLiquidation on LendingPool
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}

Root Cause:

  • The function getUserDebt in LendingPool already scales the debt balance:

function getUserDebt(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
return user.scaledDebtBalance.rayMul(reserve.usageIndex);
}
  • However, in liquidateBorrower, the returned value is again multiplied by getNormalizedDebt(), resulting in an excessive debt calculation.

Impact:

  • Overestimated debt amount can cause the liquidation process to revert due to the InsufficientBalance check.

  • Stability pool may fail to liquidate users, even when it has enough funds.

Proof of Concept

  1. Assume a borrower has a scaled debt of 1000.

  2. getUserDebt(user) returns 1000 * reserve.usageIndex = 1200 (if usageIndex = 1.2).

  3. liquidateBorrower further scales it: 1200 * getNormalizedDebt() = 1440 (if getNormalizedDebt() = 1.2).

  4. If the stability pool has only 1200 tokens, the liquidation incorrectly fails due to InsufficientBalance().

Recommended Fix

Remove the extra scaling when retrieving user debt:

// Get the user's debt from the LendingPool.
uint256 userDebt = lendingPool.getUserDebt(userAddress);
// Remove the unnecessary scaling step
// uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
uint256 scaledUserDebt = userDebt;
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!