The StabilityPool contract incorrectly scales user debt twice during liquidation checks, This causes the StabilityPool to require more crvUSD than actually needed for liquidation, with the excess amount becoming permanently locked in the contract as there is no withdrawal mechanism for crvUSD.
To understand this vulnerability, let's first look at how debt calculation and liquidation work in the protocol:
Debt Calculation in LendingPool:
The LendingPool’s function getUserDebt computes the user’s debt by taking the scaled debt balance and multiplying it by the usageIndex. This usage index represents accrued interest (much like Aave’s liquidity index) so that the returned debt value already reflects interest accrued over time
Incorrect Double Scaling in StabilityPool:
In the StabilityPool contract, after retrieving the debt using getUserDebt, the contract performs an extra scaling multiplication by calling rayMul with lendingPool.getNormalizedDebt() again .
This second scaling factor, getNormalizedDebt(), appears to adjust the debt once more. If the value returned by getNormalizedDebt() is greater than 1 (in ray units), this will inflate the computed debt dramatically.
The StabilityPool then compares its crvUSD balance against this scaledUserDebt (which is inflated). If the balance is lower than the inflated debt, it prevents liquidation:
This leads to a situation where more funds need to be deposited into the StabilityPool to cover what is, in effect, an exaggerated debt value
However, when the actual liquidation occurs in the LendingPool, it uses the correct amount :
Initial user debt: 100 crvUSD
Usage index: 1.5
First scaling in LendingPool: 100 * 1.5 = 150 crvUSD (this is user debt value including accrued interest)
Second scaling in StabilityPool: 150 * 1.5 = 225 crvUSD (incorrect , inflated by 75 crvUSD)
The StabilityPool requires 225 crvUSD to be deposited for liquidation, but the LendingPool only uses 150 crvUSD to clear the debt.
The remaining 75 crvUSD becomes permanently locked in the StabilityPool as there is no mechanism to withdraw these excess funds.
Liquidators must over-deposit funds (up to x% more than needed depends on how big usageIndex , which keep increasing overtime) to perform liquidations
The excess funds become permanently locked in the StabilityPool with no withdrawal mechanism
Foundry
Manual Review
Remove the second scaling operation in the StabilityPool and rely on the LendingPool's debt calculation:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.