The liquidation process allows the Stability Pool to liquidate users after the grace period if the liquidation has not been closed, even if users have repaid their debt.
After liquidation has been initiated, the user has a three-day grace period to repay the debt before the Stability Pool can finalize the liquidation. After repayment, the user must manually call closeLiquidation() to clear the liquidation status. However, in many lending protocols, it is common practice that users do not need to worry about the liquidation process once the debt is repaid.
In the current liquidation process, a problem arises if the user repays the debt but forgets to close the liquidation. Another scenario where the same issue occurs is if someone else repays the debt for the user, but the user was AFK or unaware of it. The person who repaid the debt cannot close the liquidation because the function uses msg.sender as the address. Technically, the debt is repaid, and the user should no longer be liquidatable. However, the flag isUnderLiquidation[userAddress] is only set to false after the liquidation is manually closed.
After the three-day grace period expires, the Stability Pool calls finalizeLiquidation, and it succeeds because the user's liquidation status has not changed. Even though the user's debt is 0, the burn function of the DebtToken does not check the amount burned and "burns" 0 tokens. Before the token burning process, the user's entire collateral is transferred to the Stability Pool, leaving the user with no collateral and 0 debt.
Add this test to the LendingPool.test.js:
Likelihood: High; Impact: High
Any user can still be liquidated if the user repays the debt but does not call closeLiquidation()
Manual Review
There are two options that could be implemented to remove the responsibility of liquidation closure from the end users and prevent the situation described above from occurring:
Make the closeLiquidation() function internal, add the user address as a parameter, and then in the _repay() function, after debt repayment, check if the user is under liquidation and check the health factor to see if the user should still be liquidated. If not, call closeLiquidation() automatically.
Rerun the test from the POC to confirm it fails now.
If the current implementation is your design choice, you should still implement a safety check in finalizeLiquidation() to see if the user's debt has already been fully repaid and gracefully return
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.