In the mint
function of the DebtToken.sol
contract, the calculation of the total amount to mint is performed as follows:
Here, amount
represents the new debt in underlying asset units, while balanceIncrease
is computed as:
The issue is that these two values are derived from different unit bases—mixing underlying and scaled units—which leads to an inconsistent calculation of the debt tokens to be minted.
Mismatched Units:
The variable amount
is provided in underlying asset units and is later converted to a scaled amount using rayDiv(index)
(although that result is only used for a zero-check).
The balanceIncrease
is calculated based on scaledBalance
, which is obtained by calling the overridden balanceOf
function. This function returns the underlying debt by multiplying the stored scaled balance by the current normalized debt.
As a result, adding amount
(underlying units) to balanceIncrease
(effectively derived from an already scaled balance) leads to an inconsistent total that does not properly reflect either the true scaled value or the underlying amount.
Interest Accrual Inaccuracy:
The intention is to account for accrued interest on the user's previous debt by increasing their minted amount. However, because of the unit mismatch, the accrued interest is either over- or under-estimated, which can result in:
Undercharging Interest: Users may not be charged the correct interest on their outstanding debt.
Overcharging Interest: Alternatively, the protocol might mint too many tokens, disrupting the debt accounting mechanism.
Potential Downstream Effects:
This inconsistency can lead to errors in overall debt accounting, inaccurate total supply calculations, and potential vulnerabilities where users might exploit the discrepancy to reduce their effective repayment obligations.
Debt Accounting Errors:
Inaccurate minting due to mixed unit calculations can lead to significant discrepancies in users’ debt positions and the protocol’s overall debt supply.
Economic Exploitation:
If interest accrual is miscalculated, borrowers might repay less than their true debt, potentially undermining the revenue model of the lending protocol.
Manual review
Consistent Unit Usage:
Ensure that all calculations in the mint function are performed using the same unit basis. For instance, if the stored balances are in scaled units, then both the new mint amount and the accrued interest should be calculated in scaled units:
Correct Minting Process:
Use the scaled amounts for the _mint
call so that the ERC20 internal bookkeeping remains consistent:
This ensures that the underlying debt (when later computed via the overridden balanceOf
) correctly reflects both the new debt and the accrued interest.
Below is a detailed proof‐of‐concept that shows—with concrete numbers—how the calculation
leads to an over‐minting of debt tokens due to a unit mismatch between “underlying” and “scaled” values.
Scaling Factor (“RAY”):
Usage/Interest Index:
Old index:
New index:
(This represents a doubling of the interest index.)
Normalized Debt:
We assume that the LendingPool’s normalized debt equals the current usage index.
When index is , normalized debt = .
When index is , normalized debt = .
User’s Initial Borrow:
The user initially mints a debt of 100 underlying units at the old index.
The conversion in a correct implementation would be:
$$
\text{scaled amount} = \text{amount}.\texttt{rayDiv(oldIndex)} \quad \Rightarrow \quad 100.\texttt{rayDiv}(1e27) \approx 100
$$
The contract stores an internal “scaled” balance of 100.
When queried via the overridden balanceOf
, the underlying debt is computed as:
$$
\text{underlying} = \text{scaled balance}.\texttt{rayMul(normalizedDebt)} \quad \Rightarrow \quad 100.\texttt{rayMul}(1e27) = 100
$$
Time passes and interest accrues so that the new usage index becomes .
The user’s existing scaled balance remains at 100.
However, when recalculated using the new normalized debt, the underlying debt becomes:
$$
100.\texttt{rayMul}(2e27) = \frac{100 \times 2e27}{1e27} = 200 \quad \text{underlying units.}
$$
This means that due solely to interest accrual, the user’s outstanding debt should have grown from 100 to 200 underlying units.
Now, the user borrows an additional 100 underlying units at the new index (\left(2 \times 10^{27}\right)).
Convert the new amount to scaled units:
$$
\text{newScaled} = 100.\texttt{rayDiv}(2e27) \approx 50.
$$
Update the stored scaled balance:
$$
\text{New internal scaled balance} = \text{Old scaled balance} + \text{newScaled} = 100 + 50 = 150.
$$
Compute the new underlying debt:
Interpretation:
The original 100 (minted at the old index) grows to 200 under the new index,
Plus the newly borrowed 100 gives a total of 300 underlying units.
This is the expected correct behavior.
Inside the buggy mint
function, the following steps occur:
Compute amountScaled
:
(Though calculated, it is not used for minting.)
$$
\text{amountScaled} = 100.\texttt{rayDiv}(2e27) \approx 50.
$$
Retrieve the current underlying debt:
The function calls balanceOf(onBehalfOf)
, which returns:
$$
\text{scaledBalance (underlying)} = 100.\texttt{rayMul}(2e27) = 200.
$$
Calculate the accrued interest (balanceIncrease
):
Using:
Compute each term:
Therefore:
$$
\text{balanceIncrease} = 400 - 200 = 200.
$$
Add the new amount and accrued interest:
$$
\text{amountToMint} = \text{amount} + \text{balanceIncrease} = 100 + 200 = 300.
$$
Call _mint
:
The contract mints 300 tokens (in its internal “scaled” units) for the user.
Update the user’s internal scaled balance:
The new stored scaled balance becomes:
$$
\text{Old scaled balance} + 300 = 100 + 300 = 400.
$$
Recalculate the underlying debt:
When queried via balanceOf
, it becomes:
$$
400.\texttt{rayMul}(2e27) = \frac{400 \times 2e27}{1e27} = 800 \quad \text{underlying units.}
$$
Expected:
New internal scaled balance: 150
Underlying debt: (150 \times 2 = 300)
Buggy (Actual):
New internal scaled balance: 400
Underlying debt: (400 \times 2 = 800)
This means the user ends up with an outstanding debt of 800 underlying units instead of the correct 300, effectively overcharging the user by a factor of more than 2.5×.
The bug arises because the function mixes underlying and scaled units:
amount
is provided in underlying units.
balanceIncrease
is derived from the user’s underlying debt (which already factors in interest via the normalized debt).
Adding these together results in a value that is then treated as if it were in scaled units when calling _mint
.
This unit mismatch causes the protocol to mint far more tokens than intended—dramatically inflating the borrower’s debt. An attacker or even an honest user interacting with the protocol would see their debt balloon incorrectly, leading to both inaccurate accounting and potential economic exploitation.
By following the above POC with concrete numbers, it’s clear that the calculation of
is incorrect and must be revised to consistently operate in one unit (preferably scaled units) to prevent over‐minting.
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.