The LendingPool contract provides functions for repaying debt—both for oneself via repay and on behalf of another user via repayOnBehalf. According to the NatSpec documentation, if the onBehalfOf parameter is set to address(0), the repayment should default to the caller's own debt. More importantly, the protocol’s design intends that only healthy users (i.e., users not under liquidation) can actively repay their debts. However, the current implementation does not restrict users who are under liquidation from repaying their debt. This loophole enables a scenario where a user, even after being flagged as under liquidation, can repay their debt, thereby reducing the debt balance to zero. Consequently, when the Stability Pool later attempts to finalize liquidation, it encounters zero debt—causing the liquidation process to revert. This bug undermines the intended liquidation mechanics and effectively locks the user in an under-liquidation state, potentially blacklisting them and disrupting the system's debt resolution process. Lastly, an under-liquidation user can repay their debt using the repay function and also can repay on behalf of another under-liquidation user using the repayOnBehalf function, bypassing the intended liquidation mechanics.
Repayment Functionality:
The repay function allows any user to repay their own debt by calling:
Similarly, repayOnBehalf enables repayment on behalf of another user:
Internal Repayment Logic:
The _repay function does not include any checks to prevent repayment by users flagged as under liquidation. Even though the NatSpec specifies that if onBehalfOf is address(0) the function should default to repaying the caller's debt, the implementation reverts if a zero address is provided:
This oversight permits a user, even when under liquidation, to call repay (or repayOnBehalf for themselves) and reduce their debt to zero.
Liquidation Mechanics Conflict:
When a user (e.g., Alice) falls below the required health factor, another party (e.g., Bob) may initiate liquidation by calling initiateLiquidation, which sets isUnderLiquidation[Alice] to true and starts a grace period.
During the grace period, Alice has the option to close liquidation via closeLiquidation if her debt is near zero. However, if the grace period expires and she has not closed liquidation, the Stability Pool is expected to finalize the liquidation using finalizeLiquidation.
If Alice, despite being under liquidation, repays her debt using the repay function, her debt balance becomes zero. Consequently, when the Stability Pool later calls finalizeLiquidation, the calculation of userDebt results in zero, causing the debt-burning mechanism to revert.
And, anyone else no matter if they are under liquidation or not can repay on behalf of Alice using repayOnBehalf function. This will also reduce Alice's debt to zero.
Setup:
Alice deposits reserve assets into the pool and mints an NFT to use as collateral.
She borrows 900e18 from the pool, thereby increasing her debt and reducing her health factor below the liquidation threshold.
Liquidation Initiation:
Bob detects that Alice’s health factor is below the threshold and calls initiateLiquidation(Alice). Now, isUnderLiquidation[Alice] is set to true and the liquidation grace period begins.
Repayment by an Under-Liquidation User:
Instead of allowing only the closeLiquidation mechanism to resolve her position, Alice (or even someone (under liquidated or not no matter) on her behalf) repays her debt via the repay or repayOnBehalf function.
This repayment reduces her debt to zero.
Liquidation Finalization Failure:
Later, when the Stability Pool attempts to finalize liquidation by calling finalizeLiquidation(Alice), the function calculates userDebt as zero. This unexpected state causes the debt-burning process to fail and the transaction to revert.
Result:
Alice remains permanently under liquidation (with isUnderLiquidation[Alice] still set to true), effectively blacklisting her and preventing further collateral withdrawals or new borrowings.
Create a Foundry Project:
Open your terminal and run:
Place Contract Files:
Place all relevant contract files (e.g., LendingPool.sol, RAACNFT.sol, RAACHousePrices.sol, etc.) in the src directory of your project.
Create Test Directory:
Create a directory named test adjacent to the src directory, and add the test file (for example, PoolsTest.t.sol) that contains the test suite code.
Run the Test:
In your terminal, execute:
output:
Denial of Service (DoS) in Liquidation:
Under-liquidation users can repay their debts, which resets their debt balance to zero. This unintended behavior prevents the Stability Pool from finalizing liquidation since the expected debt-burning logic cannot execute on a zero debt value.
Systemic Integrity Risk:
The failure to finalize liquidation leaves users stuck in an under-liquidation state. This not only locks their collateral but also potentially disrupts the liquidity and risk management mechanisms of the protocol.
User Blacklisting:
Affected users may become effectively blacklisted, as they cannot withdraw collateral, initiate new borrowings, or exit liquidation via the prescribed process, leading to long-term operational issues.
Adversary Opportunity:
Malicious actors could exploit this vulnerability to disrupt the system, potentially causing cascading effects that impact other users and the protocol's overall stability. An attacker could intentionally lock users in under-liquidation states, leading to systemic issues.
Manual Review
Foundry (Forge)
To remediate this vulnerability, modify the repayment functions to prevent under-liquidation users from repaying their debts via the standard repay and repayOnBehalf functions. Only healthy users should be allowed to reduce their debt outside of the liquidation process. A possible approach is to add a check that prevents repayment if the caller (or the on-behalf user) is flagged as under liquidation.
repay FunctionAdd a check to ensure that only healthy (non-under-liquidation) users can repay their own debt:
repayOnBehalf FunctionSimilarly, restrict repayments on behalf of users under liquidation:
By enforcing these additional checks, the protocol will ensure that once a user is under liquidation, they must follow the proper liquidation process rather than bypassing it through debt repayment. This preserves the intended mechanics of liquidation, allowing the Stability Pool to finalize the process correctly and preventing users from inadvertently or maliciously locking themselves out of the system.
After implementing these changes, rerun the test suite to verify that under-liquidation users cannot repay their debts via these functions, thereby restoring the intended liquidation flow.
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.