Core Contracts

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

Wrong `scaledUserDebt` Calculation Makes Liquidation Impossible

Summary

The calculation of scaledUserDebt in the StabilityPool::liquidateBorrower is wrong. This causes the wrong amount of crvUSDToken to be approved for transfer during liquidation. Hence, the transfer will always fail.

Wrong scaledUserDebt calculation -> Wrong Approval Amount -> Transfer in liquidation reverts.

Vulnerability Details

In the StabilityPool::liquidateBorrower, we have the following code:

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
...
uint256 userDebt = lendingPool.getUserDebt(userAddress);
@> uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
...

Looking at the above code, the function calculates the scaledUserDebt (borrowed tokens + interest) by multiplying userDebt and lendingPool.getNormalizedDebt() (which is the usageIndex). This calculation is wrong.

In all other functions, the scaled amount is calculated as follows:

uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);

Hence, the correct calculation is scaledAmount = amount / usageIndex, and not scaledDebt = userDebt * usageIndex. Here, 'Amount' and 'Debt' represent the same borrowed tokens.

Impact

Due to the calculation being wrong, the amount of tokens approved is direct affected as shown below:

uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
...
bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);

Hence, when the final function in the liquidation is called (finalizeLiquidation), the actual amountScaled will revert on transfer due to insufficient approval:

function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
...
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) = IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
// Transfer reserve assets from Stability Pool to cover the debt
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
...

In the burn function above, the amountScaled is calculated as:

uint256 amountScaled = amount.rayDiv(index);

In conclusion, the liquidation will always revert due to the wrong calculation causing the wrong amount being approved for transfer from the StabilityPool to the LendingPool.

Tools Used

Manual review.

Recommendations

Just need to change the multiplication to division.

Updates

Lead Judging Commences

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