The _repay function compares the repayment amount (provided by the caller) against the user’s scaled debt rather than their actual debt. In doing so, it caps the repayment amount at the scaled debt value (i.e. userDebt divided by the usage index) instead of using the full actual debt value. This miscalculation can lead to under-repayment because the scaled debt is typically lower than the actual debt when the usage index is greater than one.
The root cause is the use of userScaledDebt (i.e. userDebt.rayDiv(reserve.usageIndex)) as the cap for the repayment amount. The repayment logic incorrectly assumes that scaling the debt for comparison is appropriate, but it inadvertently limits the repayable amount to a fraction of the actual debt when the usage index is greater than RAY (1e27). This arithmetic error in capping the repay amount is what leads to the under-repayment issue.
Explain in relevant detail using numbers and creating scenarios demonstrating the impact
Consider a scenario where:
The actual user debt (userDebt) is 1,000,000 units.
The reserve usage index is 2e27 (i.e. twice the RAY value), meaning the scaled debt (userScaledDebt) becomes 500,000 units (1,000,000 / 2).
A user attempts to repay 600,000 units.
With the current logic, the function compares 600,000 to the scaled debt of 500,000 and caps the repayment amount at 500,000 units.
Thus, even though the user intended to repay 600,000 units (which would be sufficient to cover the full 1,000,000 unit debt when properly accounted), the function only processes 500,000 units of repayment.
The remaining 500,000 units of actual debt remain unaddressed, leaving the protocol with an under-repayment issue.
As a consequence, users may end up repaying less than they intend or less than required to fully cover their outstanding debt. This under-repayment means that a portion of the debt remains unaccounted for in the system, potentially leading to inaccuracies in the debt records, prolonged indebtedness, or even systemic under-collateralization. This discrepancy can expose the protocol to additional risk, as the borrower's actual liability might not be fully cleared.
To resolve the issue, the repayment cap should be based on the actual user debt (userDebt) rather than the scaled debt (userScaledDebt). This can be done by modifying the comparison logic in the _repay function so that actualRepayAmount is capped using userDebt. For example, replace:
uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
with:
uint256 actualRepayAmount = amount > userDebt ? userDebt : amount;
This change ensures that the repay function correctly accounts for the full outstanding debt and prevents the under-repayment issue.
That amount is not actually used.
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.