Core Contracts

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

`StabilityPool::liquidateBorrower` invalid amount when calculating `userDebt` potentially makes the borrower cant be liquidated

Summary

invalid calculation happen inside liquidateBorrowerfunction where it calculate userDebtusing unupdated index, potentially make the function revert if the amount is less than the actual userDebtmultiplied by the updated index because insufficient balance.

Vulnerability Details

the vulnerability lies inside the liquidateBorrower:

StabilityPool.sol#L449-L470

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);
}

when the function first calculate userDebt, it fetch the value of normalized debt amount (scaled x interest).

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

there are few issue here, first the return value of getUserDebtis already multiplied by interest index but on the next line the userDebtis getting multiplied by the same index.

second, the approve function are called using the amount of scaledUserDebtfrom previous step, which is innacurrate.

why? because it calculated before lendingPool.updateState()is called. this is crucial because the updateStatefunction would later update the index of debt interest and also the index of income interest.

so on the first step, the index used to calculate the user debt are using the old value instead of the new one.

Impact

the function would revert when the approved amount of scaledUserDebtare not sufficient when it would be transferred later in lendingPool.finalizeLiquidationfunction. because inside of finalizeLiquidation, the actual amount transferred from the StabilityPool would be calculated using updated index.

Tools Used

manual review

Recommendations

first update the index by calling lendingPool.updateState()before calculating the borrower debt.

second, no need to multiply the user debt amount if you want to get the scaled amount. instead you should divide it.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

StabilityPool: liquidateBorrower should call lendingPool.updateState earlier, to ensure the updated usageIndex is used in calculating the scaledUserDebt

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.