Summary
The DebtToken::burn()
function emits incorrect event parameters and returns improperly ordered values, which can lead to inconsistencies in downstream contract logic.
Vulnerability Details
As indicated by the @>1
marker, the function emits the Burn
event using amountScaled
, while the event documentation specifies that it should emit the amount burned in underlying asset units (amount
).
Incorrect Event Emission
* @notice Emitted when debt tokens are burned
* @param from The address from which tokens are burned
@>1 * @param amount The amount burned (in underlying asset units)
* @param index The usage index at the time of burning
*/
event Burn(address indexed from, uint256 amount, uint256 index);
* @notice Burns debt tokens from a user
* @param from The address from which tokens are burned
* @param amount The amount to burn (in underlying asset units)
* @param index The usage index at the time of burning
* @return A tuple containing:
@>2 * - uint256: The amount of scaled tokens burned
* - uint256: The new total supply after burning
@>2 * - uint256: The amount of underlying tokens burned
* - uint256: The balance increase due to interest
*/
function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
if (from == address(0)) revert InvalidAddress();
if (amount == 0) {
return (0, totalSupply(), 0, 0);
}
uint256 userBalance = balanceOf(from);
uint256 balanceIncrease = 0;
if (_userState[from].index != 0 && _userState[from].index < index) {
uint256 borrowIndex = ILendingPool(_reservePool).getNormalizedDebt();
balanceIncrease = userBalance.rayMul(borrowIndex) - userBalance.rayMul(_userState[from].index);
amount = amount;
}
_userState[from].index = index.toUint128();
if(amount > userBalance){
amount = userBalance;
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
_burn(from, amount.toUint128());
@>1 emit Burn(from, amountScaled, index);
@>2 return (amount, totalSupply(), amountScaled, balanceIncrease);
}
Incorrect Return Values
As indicated by @>2
, the function returns values in an incorrect order. The contract returns (amount, totalSupply(), amountScaled, balanceIncrease)
, but in the LendingPool
contract, it is expected that amountScaled
should be the first return value.
Downstream Impact in LendingPool
Contract
The DebtToken::burn()
function is invoked in the LendingPool
contract, where the order of return values is expected as (amountScaled, newTotalSupply, amountBurned, balanceIncrease)
, which does not match the actual return order of burn()
.
Code Snippet from LendingPool
function _repay(uint256 amount, address onBehalfOf) internal {
@> (uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
}
function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
@> (uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) = IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
}
Impact
Incorrect event emission: The event logs misleading data, as it emits amountScaled
instead of amount
(which represents the actual amount burned in underlying asset units). This could lead to incorrect accounting in off-chain tracking systems.
Misaligned return values: Functions in LendingPool
rely on a specific return order from burn()
. Since the actual order is different, it could cause miscalculations or unintended behavior in downstream contract logic.
Tools Used
Manual Review
Recommendations
Update burn()
to ensure the event emission and return values match expected behavior.
function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
if (from == address(0)) revert InvalidAddress();
if (amount == 0) {
return (0, totalSupply(), 0, 0);
}
uint256 userBalance = balanceOf(from);
uint256 balanceIncrease = 0;
if (_userState[from].index != 0 && _userState[from].index < index) {
uint256 borrowIndex = ILendingPool(_reservePool).getNormalizedDebt();
balanceIncrease = userBalance.rayMul(borrowIndex) - userBalance.rayMul(_userState[from].index);
amount = amount;
}
_userState[from].index = index.toUint128();
if(amount > userBalance){
amount = userBalance;
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
_burn(from, amount.toUint128());
- emit Burn(from, amountScaled, index);
+ emit Burn(from, amount, index);
- return (amount, totalSupply(), amountScaled, balanceIncrease);
+ return (amountScaled, totalSupply(), amount, balanceIncrease);
}