Core Contracts

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

Incorrect amount calculation and approval in ``liquidateBorrower()`` of StabilityPool.sol.

Summary

Incorrect amount calculation and approval in liquidateBorrower() of StabilityPool.sol results in DOS of core functionality i.e liquidation and causes bad debt for the protocol.

Vulnerability Details

In the liquidateBorrower() function:

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
// 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();
// Call finalizeLiquidation on LendingPool
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}

userDebt is taken from getUserDebt() function of lendingPool.sol:

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

Here, scaledDebtBalance of user is multiplied with usageIndex for the userDebt.

Now in the liquidateBorrower() function, userDebt is again scaled as:

uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());

lendingPool.getNormalizedDebt() is equal to the usageIndex.

function getNormalizedDebt() external view returns (uint256) {
return reserve.usageIndex;
}

Thus, scaledUserDebt is multiplied twice with the usageIndex which is incorrect.

Impact

This issue creates scaledUserDebt way more than expected which may result in failure of liquidateBorrower() due to InsufficientBalance error and makes it impossible to finalize the liquidation for the borrower. Thus, hindering the core protocol functionality.

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();
// Call finalizeLiquidation on LendingPool
lendingPool.finalizeLiquidation(userAddress);

This issue will also result in the contract transferring more amount of scaledUserDebt for liquidation than necessary if enough funds are available and causes bad debt in the protocol which will impact both the protocol and the future liquidations(DoS due to lack of funds).

Tools Used

Manual Analysis

Recommendations

scaledUserDebt from userDebt should be calculated as:

uint256 scaledUserDebt = WadRayMath.rayDiv(userDebt, lendingPool.getNormalizedDebt());

Instead of rayMul, it should be rayDiv as seen in LendingPool.sol and DebtToken.sol contracts.

uint256 amountScaled = amount.rayDiv(index);
Updates

Lead Judging Commences

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