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 liquidateBorrower Function

Summary

The liquidateBorrower function attempts to liquidate a borrower's position. It retrieves the userDebt from lendingPool.getUserDebtand scales it by multiplying it by the normalized debt. However, the value returned by getUserDebt has already been scaled so scaling it again in liquidateBorrower leads to double scaling

Vulnerability Details

function liquidateBorrower(address userAddress) external {
_update();
uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
// ... rest of the function
}
function getUserDebt(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
return user.scaledDebtBalance.rayMul(reserve.usageIndex);
}

The double scaling occurs because:

  1. getUserDebt() already returns a scaled value by multiplying scaledDebtBalance with usageIndex

  2. liquidateBorrower() then scales this already-scaled value again by multiplying it with getNormalizedDebt()

This results in the actual liquidation amount being larger than intended:

Actual Amount = Debt * Scale * Scale

Instead of the correct:

Actual Amount = Debt * Scale

Impact

  • Users being liquidated for incorrect amounts

Tools Used

Manual

Recommendations

Remove the second scaling operation in liquidateBorrower:

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