Core Contracts

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

Incorrect Decimal Precision in Liquidation Transfer Causes Failed or Incorrect Liquidations

Relevant GitHub Links

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/StabilityPool/StabilityPool.sol#L453

Summary

In StabilityPool's liquidateBorrower(), the debt amount used for token transfer is in RAY precision (27 decimals) without conversion to the token's decimal precision, potentially causing failed transfers or incorrect amounts.

Vulnerability Details

The liquidateBorrower() function uses RAY precision values directly for token transfer:

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
// ... attempts to transfer with RAY precision amount
}

The getUserDebt() returns value in RAY precision due to rayMul:

function getUserDebt(address userAddress) public view returns (uint256) {
return user.scaledDebtBalance.rayMul(reserve.usageIndex); // Returns RAY precision
}

Impact

  • Liquidations may fail due to amount exceeding token balance (when RAY > token decimals)

  • If transfer succeeds, incorrect amounts will be transferred (up to factor of 10^9)

  • System becomes unable to liquidate positions correctly

Tools Used

Manual Review

Recommendations

Convert the debt amount to token decimals before transfer (pseudocode):

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
// Convert from RAY (27 decimals) to token decimals
uint256 decimalDiff = 27 - crvUSDToken.decimals();
uint256 debtInTokenDecimals = scaledUserDebt / (10 ** decimalDiff);
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
if (crvUSDBalance < debtInTokenDecimals) revert InsufficientBalance();
bool approveSuccess = crvUSDToken.approve(address(lendingPool), debtInTokenDecimals);
// ...
}
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!