01. Relevant GitHub Links
02. Summary
When a user repays their borrowed amount, the internal _repay function mistakenly compares userScaledDebt (scaled) to amount (unscaled) to compute actualRepayAmount. This results in a wrong value for actualRepayAmount, though it is not used to burn the debt tokens. The code still calls burn with the original amount, so there is no severe impact. However, it is a clear incorrect implementation.
03. Vulnerability Details
function _repay(uint256 amount, address onBehalfOf) internal {
if (amount == 0) revert InvalidAmount();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
UserData storage user = userData[onBehalfOf];
ReserveLibrary.updateReserveState(reserve, rateData);
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
@> uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
uint256 scaledAmount = actualRepayAmount.rayDiv(reserve.usageIndex);
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
amount is an underlying (unscaled) value.
userScaledDebt is a scaled value.
Comparing these two different representations leads to an incorrect calculation of actualRepayAmount.
Despite this error, burn is called with the original amount, so the protocol still functions normally.
04. Impact
The bug is low severity since the repayment flow uses burn(..., amount, ...) rather than the incorrect actualRepayAmount.
It remains a code inconsistency that can cause confusion or bugs if logic changes in the future.
05. Tools Used
Manual Code Review and Foundry
06. Recommended Mitigation
function _repay(uint256 amount, address onBehalfOf) internal {
if (amount == 0) revert InvalidAmount();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
UserData storage user = userData[onBehalfOf];
// Update reserve state before repayment
ReserveLibrary.updateReserveState(reserve, rateData);
// Calculate the user's debt (for the onBehalfOf address)
uint256. userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
// If amount is greater than userDebt, cap it at userDebt
- uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
+ uint256 actualRepayAmount = amount > userDebt ? userDebt : amount;
uint256 scaledAmount = actualRepayAmount.rayDiv(reserve.usageIndex);
// Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
// is not actualRepayAmount because we want to allow paying extra dust and we will then cap there
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);