The StabilityPool::liquidateBorrower
function in the Stability Pool contract approves a amount (scaledUserDebt - which is the amount of tokens the user had borrowed along with the interest oer the period) for transfer to the Lending Pool contract, but when LendingPool::finalizeLiquidation
is called, the Lending Pool only transfers the principal debt amount (amount) to the reserve asset contract. This discrepancy means that the interest accrued on the borrowed amount is effectively ignored, potentially leading to incorrect fund movements.
1.Liquidation Initiation in Stability Pool
A manager or owner calls liquidateBorrower()
in the Stability Pool contract.
The function retrieves the user's debt from the Lending Pool using lendingPool.getUserDebt(userAddress)
.
The scaled debt amount is calculated as scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt())
( which is the amount of tokens the user had borrowed along with the interest oer the period).
The Stability Pool approves scaledUserDebt
(which includes both borrowed amount and interest) for the Lending Pool.
The function then calls lendingPool.finalizeLiquidation(userAddress)
.
StabilityPool::liquidateBorrower
:
LendingPool::getUserDebt
:
LendingPool::getNormalizedDebt
:
2.Finalization in Lending Pool
The Lending Pool verifies that the user is under liquidation and checks if the grace period has expired.
The user’s debt is retrieved again using:
The contract then calls the burn() function of the debt token to remove the debt from the user’s balance.
The burn() function returns four values: (uint256 amount, uint256 totalSupply, uint256 amountScaled, uint256 balanceIncrease) where amount
only represents the borrowed amount (not including interest).
LendingPool::finalizeLiquidation
:
As you can see in the below function the amount returned is the amount which was transferred to the below function by the above finalizeLiquidation
function which is just the user's debt and does not include any interest.
DebtToken::burn
:
3.Incorrect Token Transfer from Stability Pool
The Lending Pool then attempts to transfer the reserve asset from the Stability Pool:
Issue: The Stability Pool initially approved scaledUserDebt
(borrowed amount + interest), but the Lending Pool only transfers amountScaled
(which excludes interest) which was returned by the burn function in Debt Token contract which was the amount/users debt passed by the finalizeLiquidation
function itself
This means that less than the expected amount of tokens are taken from the Stability Pool, leading to a discrepancy in the protocol's accounting.
As a result, the system fails to fully cover the liquidated user's debt, potentially leaving bad debt in the protocol.
The Stability Pool over-approves tokens, but the Lending Pool does not use the full approved amount.
Interest accrued on the borrowed amount is not correctly settled, leading to a mismatch in the system’s accounting.
This could result in an underpayment of debt, allowing users to get liquidated without fully repaying their outstanding obligations, which could cause financial losses to the protocol.
Manual
Modify finalizeLiquidation()
to correctly account for the full scaledUserDebt
, including both principal and interest, before performing the transfer.
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.