Core Contracts

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

Loss of funds from StabilityPool due to double debt scaling during Liquidations

Summary

The liquidateBorrower function in the StabilityPool contract contains a critical vulnerability that leads to direct loss of funds. By incorrectly scaling user debt twice, the function forces the Stability Pool to pay out significantly more crvUSD than required during liquidations. Each liquidation event results in excess crvUSD being withdrawn from the Stability Pool, effectively draining user deposits beyond the legitimate liquidation amounts.

Vulnerability Details

In the liquidateBorrower function, user debt is fetched from the lending pool.

The getUserDebt() function already returns the normalized debt amount (including accrued interest).

However, the function then incorrectly multiplies this value again with getNormalizedDebt(), to get scaledUserDebt ,resulting in double scaling of the debt amount.

//LendingPool contract
function getUserDebt(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
return user.scaledDebtBalance.rayMul(reserve.usageIndex);
}
//stabilityPool contract
function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
uint256 userDebt = lendingPool.getUserDebt(userAddress);
// userDebt is already normalized (includes accrued interest)
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
//this .rayMul() inflates the debt value.
if (userDebt == 0) revert InvalidAmount();
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
// ... rest of the function
}

Impact

HIGH: Loss of funds from StabilityPool

  • Users will be liquidated for amounts larger than their actual debt

  • The Stability Pool will pay more crvUSD than necessary during liquidations

  • Protocol's accounting will be incorrect, leading to imbalances

  • Excess liquidations could lead to unnecessary losses for borrowers

  • The protocol's solvency calculations will be distorted

Tools Used

Manual review

Recommendations

  • Either Remove the additional scaling operation and use the userDebt value directly,

  • or set scaledUserDebtto userDebtas it is already scaled.

    function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
    _update();
    uint256 userDebt = lendingPool.getUserDebt(userAddress);
    // userDebt is already normalized (includes accrued interest)
    uint256 scaledUserDebt = userDebt;
    if (userDebt == 0) revert InvalidAmount();
    uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
    if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
    // ... rest of the function
    }
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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.