an inconsistency exists in the way the LendingPool contract calculates and passes the debt amount to the DebtToken's burn function during liquidation finalization. In the liquidation flow, the contract computes the user's debt by applying a multiplication factor (using rayMul) to the user's scaled debt, whereas the DebtToken's burn function expects an unscaled amount that it converts internally using rayDiv in the DebtToken::_update. This mismatch leads to an incorrect debt value being passed for burning, which can result in erroneous debt accounting and potential financial imbalances within the protocol.
The issue arises due to the following discrepancies:
Debt Calculation in Finalization:
In the finalizeLiquidation function, the contract computes the user's debt as follows:
This calculation converts the user's scaled debt into an absolute debt value by applying the reserve's usage index.
Incompatible Burn Function Conversion:
The DebtToken's burn function is called the _burn --> _update performs its own scaling conversion. Specifically, it calculates the scaled amount to burn using:
This means that the burn --> _update function expects an amount that has not yet been converted. Passing an already scaled debt (via rayMul) results in the burn function applying an additional division by the normalized debt, effectively miscomputing the scaled amount.
Inconsistent Handling in Repay vs. Finalization:
In the repay function, the LendingPool correctly passes the repayment amount without any prior scaling:
This ensures the burn function's internal conversion is applied correctly. However, during liquidation finalization, by pre-scaling the debt value with rayMul, the LendingPool ends up with a double conversion, leading to an erroneous burn of DebtTokens.
Consider the following hypothetical example:
Initial Setup:
Suppose a user has a scaled debt balance of X.
The reserve's usage index (normalized debt factor) is A.
The expected actual debt should be:
Actual Debt = X.rayMul(A).
Repayment Scenario (Correct Flow):
When the user repays their debt, the contract passes the unscaled repayment amount directly to the burn function:
Inside the update function, the repayment amount is converted using:
scaledAmount = repaymentAmount.rayDiv(A),
which correctly translates the unscaled repayment into the scaled units.
Liquidation Finalization (Faulty Flow):
During liquidation finalization, the contract computes the user's debt as:
Suppose this results in a value D.
This value D is then passed to the burn function:
The _update function then performs:
scaledAmount = D.rayDiv(A).
Since D was already computed as X.rayMul(A), this extra conversion reduces the burned amount to:
(X.rayMul(A)).rayDiv(A) ≈ X, effectively nullifying the intended conversion and misrepresenting the user’s actual debt.
Outcome:
The debt accounting becomes inconsistent as the DebtToken burn operation does not correctly reflect the intended absolute debt value.
This discrepancy may lead to either an over-burn or under-burn of DebtTokens, ultimately causing inaccurate updates to reserve.totalUsage and the user's debt balance.
The miscalculation of burned DebtTokens results in a failure to accurately update the user’s debt and the protocol’s total usage, potentially leading to systemic imbalances.
Liquidation finalization might not reduce the user's debt as expected, or it could overly penalize the user, impacting collateral recovery and liquidation fairness.
Manual Review
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.