Summary
In the RToken#burn()
, amountScaled
is not explicitly computed as amount.rayDiv(index)
, and there is no check to ensure it is non-zero. If amount < liquidityIndex
, the computed amountScaled
can be zero.
This can lead to incorrect token burning behavior and allows users to receive reserve tokens without actually burning any RTokens.
Vulnerability Details
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
...
@> uint256 amountScaled = amount.rayMul(index);
_userState[from].index = index.toUint128();
@> _burn(from, amount.toUint128());
if (receiverOfUnderlying != address(this)) {
@> IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
emit Burn(from, receiverOfUnderlying, amount, index);
return (amount, totalSupply(), amount);
}
function _update(address from, address to, uint256 amount) internal override {
@> uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
super._update(from, to, scaledAmount);
}
Incorrect Computation of amountScaled
The function multiplies by index
instead of dividing:
uint256 amountScaled = amount.rayMul(index);
This overestimates the burn amount when index > 1e27
(WadRay precision).
User Receives Reserve Tokens Without Burning RTokens.
If amount < liquidityIndex
, then: amountScaled = 0. The user receives amount of reserve tokens and no RTokens are burned.
Impact
Users can receive reserve tokens without reducing their RToken balance.
Thus users can exploit this to drain liquidity.
Tools Used
manual
Recommendations
Modify burn()
to:
Properly compute amountScaled
using .rayDiv(index)
.
Ensure amountScaled
is not zero.
Revert if the burn amount is too small.
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
if (amount == 0) {
return (0, totalSupply(), 0);
}
uint256 userBalance = balanceOf(from);
_userState[from].index = index.toUint128();
if(amount > userBalance){
amount = userBalance;
}
- uint256 amountScaled = amount.rayMul(index);
+ uint256 amountScaled = amount.rayDiv(index);
+ if (amountScaled == 0) revert InvalidAmount();
_userState[from].index = index.toUint128();
_burn(from, amount.toUint128());
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
emit Burn(from, receiverOfUnderlying, amount, index);
return (amount, totalSupply(), amount);
}