Core Contracts

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

Double interest accrual in liquidation process drains StabilityPool funds

Summary

The liquidation process incorrectly applies interest twice when calculating debt amounts, causing the StabilityPool to overpay for liquidated positions. This error creates an exponential fund leakage vector that would lead to complete insolvency of the StabilityPool within hours of protocol operation under normal market conditions.

Finding Description

The root cause exists in the interaction between LendingPool.getUserDebt() and StabilityPool.liquidateBorrower(). The getUserDebt() function returns a debt amount that has already been multiplied by the usageIndex (interest accumulator):

function getUserDebt(address user) public view returns (uint256) {
return user.scaledDebtBalance.rayMul(usageIndex); // First interest application
}

The StabilityPool then incorrectly reapplies the interest index when processing liquidations:

function liquidateBorrower(address user) external {
uint256 userDebt = lendingPool.getUserDebt(user);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, normalizedDebt);
// Uses scaledUserDebt for payment calculations
}

This double application creates a squared interest effect where debt = scaledDebt × index² instead of the correct scaledDebt × index. The protocol's comment in LendingPool.getUserDebt() states "returns the user's total debt including interest" but fails to account for how this value would be used in subsequent calculations.

The worst-case impact occurs during normal liquidation operations:

  1. User borrows 100 ETH at 10% APR

  2. After 1 year: Actual debt = 110 ETH (100 × 1.1)

  3. StabilityPool calculates debt as 121 ETH (100 × 1.1 × 1.1)

  4. Protocol permanently loses 11 ETH per liquidation

Impact

This error enables exponential fund drainage from the StabilityPool, with losses growing quadratically relative to interest rate changes. Even at modest 5% interest rates, liquidations would permanently remove 0.25% of pool funds per operation, making protocol insolvency inevitable within weeks.

Proof Of Concept

  1. Alice deposits 1000 crvUSD to LendingPool

  2. Bob deposits NFT worth 100 crvUSD and borrows 100 crvUSD

  3. After 1 year at 10% interest:

    • Actual debt: 110 crvUSD (getUserDebt() returns 110)

    • Reserve's normalized debt index = 1.1e27

  4. Oracle reports NFT price drop to 50 crvUSD

  5. StabilityPool initiates liquidation:

    • Calculates debt as 110 × 1.1 = 121 crvUSD

    • Transfers 121 crvUSD from pool to cover 110 crvUSD debt

  6. Protocol permanently loses 11 crvUSD (10% of actual debt)

Tools Used

manual review

Recommendations

Remove the second interest application in StabilityPool by using the direct scaled debt value. Modify StabilityPool.liquidateBorrower() to use the already-interest-adjusted debt from LendingPool without additional multiplication:

- uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, normalizedDebt);
+ uint256 scaledUserDebt = userDebt;

This change aligns debt calculations with the protocol's intended single interest accrual model while maintaining existing accounting structures.

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.