In DebtToken.burn()
, the amount
in underlying units is compared against the borrower's DebtToken balance which is a scaled value. This results in incorrect debt repayment contrary to what the user intended to do.
The protocol overides the normal _update()
as follows for DebtToken:
This function is designed to first scale the amount
into scaledAmount
via the rayDiv
function based on the current usageIndex
. Notice that _update()
is an internal function invoked in functions such as _mint()
and _burn()
which here are used to mint and burn DebtTokens
to/from users respectively.
Now, 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 tokes received by the user is amountScaled
and they supply amount
of reserve tokens.
On the other hand, during repayment, a user specifies the amount of reserve tokens they wish to send.
However, the following is done in DebtToken.burn()
:
Issue breakdown:
The amount
of reserve tokens to be supplied is is in underlying units.
User's debt token balance is a scaled value which was performed in _update()
during minting
The function however, compares these two values which is not correct.
Scenario:
Lets say that with the current usageIndex, 400
units of reserve tokens translate to 200
debt tokens
A user has 300
debt tokens
As such, if they piad 400
units of reserve tokens, they would burn 200
debt tokens and be left with 100
debt tokens
However, the 400
they wish to pay back is compared against their 300
debt tokens and since this is greater, the amount
is set to 300
.
Then amountScaled
becomes 150
The function the calls _burn(300)
and since the _update()
is invoked during burning, this only burns 150
debt tokens and they are left with 150
more.
Now notice the return statement:
Back to _repay()
function, these returned values are used as shown here:
The values are matched in this manner:
amountScaled
=> amount
amoun
tBurned => amountScaled
Notice that the function pulls amountScaled
i.e the amount that was reset based on user's debt token balance (200) and user's debt reduced by amountBurned
which is 150
.
The user was willing to pay 400
reserve tokens which could have cleared 200
units of their debt but the currect implementation forces them to pay less and clear only a small portion of their debt. This results in repeated attempts at debt repayment to finally achieve a desired outcome.
Risk of liquidation
:
A user has a grace period within which they should clear their debt and opt out of liquidation train. However, instead of having their debt paid in one tx, the system prevents them. This only increases their chances of getting liquidated if the grace period runs out before they opt out.
Manual Review
Compare user's debt token balance against amountScaled
then update the amount
to be supplied by the user:
Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.
Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.
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.