Core Contracts

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

liquidateBorrower() reverts with InsufficientBalance() even when it has sufficient crvUSDBalance

Description

liquidateBorrower() multiplies userDebt with lendingPool.getNormalizedDebt() to get scaledUserDebt even though userDebt is already a scaled-up version:

File: contracts/core/pools/StabilityPool/StabilityPool.sol
449: function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
450: _update();
451: // Get the user's debt from the LendingPool.
452: uint256 userDebt = lendingPool.getUserDebt(userAddress);
453:@---> uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
454:
455: if (userDebt == 0) revert InvalidAmount();
456:
457: uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
458:@---> if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
459:
460: // Approve the LendingPool to transfer the debt amount
461: bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
462: if (!approveSuccess) revert ApprovalFailed();
463: // Update lending pool state before liquidation
464: lendingPool.updateState();
465:
466: // Call finalizeLiquidation on LendingPool
467: lendingPool.finalizeLiquidation(userAddress);
468:
469: emit BorrowerLiquidated(userAddress, scaledUserDebt);
470: }

and

File: contracts/core/pools/LendingPool/LendingPool.sol
579: function getUserDebt(address userAddress) public view returns (uint256) {
580: UserData storage user = userData[userAddress];
581:@---> return user.scaledDebtBalance.rayMul(reserve.usageIndex);
582: }

Impact

As a result, on L458 even if there's crvUSDBalance to handle userDebt, it may revert because it's less than the incorrectly inflated figure of scaledUserDebt.

Mitigation

File: contracts/core/pools/StabilityPool/StabilityPool.sol
449: function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
450: _update();
451: // Get the user's debt from the LendingPool.
452: uint256 userDebt = lendingPool.getUserDebt(userAddress);
- 453: uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
454:
455: if (userDebt == 0) revert InvalidAmount();
456:
457: uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
- 458: if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
+ 458: if (crvUSDBalance < userDebt) revert InsufficientBalance();
459:
460: // Approve the LendingPool to transfer the debt amount
- 461: bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
+ 461: bool approveSuccess = crvUSDToken.approve(address(lendingPool), userDebt);
462: if (!approveSuccess) revert ApprovalFailed();
463: // Update lending pool state before liquidation
464: lendingPool.updateState();
465:
466: // Call finalizeLiquidation on LendingPool
467: lendingPool.finalizeLiquidation(userAddress);
468:
- 469: emit BorrowerLiquidated(userAddress, scaledUserDebt);
+ 469: emit BorrowerLiquidated(userAddress, userDebt);
470: }
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.