Core Contracts

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

Wrong user debt calculation in `StabilityPool::liquidateBorrower` leads to overestimation of user debt during liquidation

Summary

StabilityPool::liquidateBorrower function is designed to liquidate a borrower's position. But in the user debt calculation this is incorrectly scaled twice during liquidation. This occurs because the function scales the already-scaled debt returned by LendingPool::getUserDebt with the normalization factor again usage.index`. This leads to an overestimation of user debt during liquidation.

Vulnerability Details

StabilityPool.sol

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
// Get the user's debt from the LendingPool.
@> uint256 userDebt = lendingPool.getUserDebt(userAddress);
@> 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);
}

LendingPool.sol

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

Impact

Consider the following scenario:

  • User's scaled debt balance: 100

  • Usage index: 1.1 RAY (1.1 * 1e27)

In StabilityPool::liquidateBorrower:

  1. First scaling (in LendingPool::getUserDebt):

  • userDebt = scaledDebtBalance.rayMul(usageIndex)

  • userDebt = 100 * (1.1 * 1e27) / 1e27

  • userDebt = 110 // Correct scaled debt

  1. Second incorrect scaling:

  • scaledUserDebt = userDebt.rayMul(usageIndex)

  • scaledUserDebt = 110 * (1.1 * 1e27) / 1e27

  • scaledUserDebt = 121 // Incorrectly scaled

Result:

  • Actual user debt: 110

  • Incorrectly calculated debt: 121

  • Excess liquidation amount: 11 (10% more than actual debt)

  • The user would be overcharged by 121 USDC during liquidation

In the liquidateBorrower there is a double application of the normalization factor to the user's debt. This causes a severe overestimation of user debt during liquidation. Users are liquidated for more debt than they actually owe.

Tools Used

Manual review

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.
uint256 userDebt = lendingPool.getUserDebt(userAddress);
- uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
if (userDebt == 0) revert InvalidAmount();
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
...omitted code
}
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!