Core Contracts

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

Double Interest Scaling in Liquidation Causes Inflated Debt Amount

Relevant GitHub Links

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

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

Summary

In StabilityPool's liquidateBorrower() function, user debt is scaled twice with interest, causing users to be liquidated for higher amounts than they actually owe.

Vulnerability Details

The liquidateBorrower() function incorrectly scales the user's debt twice:

// StabilityPool.sol
function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
// ...
}

The user's debt is already scaled with interest in getUserDebt():

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

The additional rayMul with getNormalizedDebt() in liquidateBorrower() scales this already-scaled debt again, effectively squaring the interest rate.

Impact

  • Users being liquidated will have to pay significantly more than their actual debt

  • Liquidators will overpay for liquidations

  • Protocol will incorrectly collect excess debt payments

  • Financial loss for users and liquidators

Example:

  • User actual debt with interest: 1000 crvUSD

  • Interest rate (usageIndex): 1.1

  • Double scaled debt: 1000 * 1.1 * 1.1 = 1210 crvUSD

  • Results in 210 crvUSD excess payment vs 100 correct interest

Tools Used

Manual Review

Recommendations

Remove the additional scaling in liquidateBorrower():

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
uint256 userDebt = lendingPool.getUserDebt(userAddress); // Already includes interest
// Use userDebt directly after proper decimal conversion
// ...
}
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!