The liquidation process in the LendingPool contract may result in users retaining outstanding debt after liquidation. This occurs because the user's normalized debt is compared against their scaled debt balance, leading to users emerging from liquidation with unresolved debt.
When a user intends to borrow, they specify the amount of reserve tokens they need. DebtToken.mint() is then invoked to mint this user debt tokens in exchange:
As seen above, the amount is first scaled via the rayDiv(index) as done by the _update() and as such, when minting is done, the amount of debt tokens received by the user is amountScaled and they receive amount of reserve tokens and their scaledDebtBalance is updated as follows.
We can deduce that user.scaledDebtBalance is incremented by the number of debt tokens minted to them which is a scaled value of the amount they receive.
Now during liquidation, the following is done in finalizeLiquidation():
Now lets see how DebtToken.burn() handles all this and the values it returns:
Issue:
In finalizeLiquidation(), userDebt is already normalized by rayMul(reserve.usageIndex) which means that it is in underlying units.
This userDebt is then passed as amount to burn().
The problem however is that a normalized value is compared againts a scaled value. Remember that user's debt balance (userBalance) was achieved by rayDiv() while userDebt is this balance normalized and as such, the check if(amount > userBalance) will always be true.
This results in amount being set to userBalance which is a smaller value. Then when burning is done, a very small portion of debt tokens will burned and finally the burn() function returns amount which has been updated back to finalizeLiquidation().
Now since this first return value is what is pulled from the stability pool, the debt remains unsettled.
Scenario:
Lets say that with the current index, 600 units of reserve tokens clears 300 debt tokens
A user has 300 debt tokens which when normalized is 600 reserve tokens
As such, during liquidation, 600 units of reserve tokens, should burn the whole 300 debt tokens from the user
However, the 600 is compared against the user's 300 debt tokens and since this is greater, the amount is set to 300.
The burn() function then calls _burn(300) and since the _update() is invoked during burning, this only burns 150 debt tokens and the user is left with 150 more debt tokens.
The finalizeLiquidation() function pulls amountScaled i.e the amount that was updated based on user's debt token balance (300) and user's debt reduced by amountBurned which is 150.
Final state:
User's debt token balance => 150
user.scaledDebtBalance => 300-150 = 150
All user's NFTs taken
The issue here is that user's debt remain unsettled even after liquidation yet all their collateral is taken. This means that their chances of getting liquidated the next time is increased as they start out afresh will a pre-existing debt.
Manual Review
Compare user's debt token balance against amountScaled then update the amount to be supplied by the user:
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.