Summary
According to the event definition, the Withdraw event is supposed to emit burnedScaledAmount, but it currently emits amount due to incorrect calculation in IRToken.burn().
Vulnerability Details
event Withdraw(address indexed user, uint256 amount, uint256 liquidityBurned);
The RToken.burn() returns normalized amount as first param and it doesn't return scaled amount. In ReserveLibrary.withdraw() function, it retrieves burnedScaledAmount from the first return value of RToken.burn().
Then emit Withdraw with incorrect value burnedScaledAmount.
function withdraw(
ReserveData storage reserve,
ReserveRateData storage rateData,
uint256 amount,
address recipient
) internal returns (uint256 amountWithdrawn, uint256 amountScaled, uint256 amountUnderlying) {
if (amount < 1) revert InvalidAmount();
updateReserveInterests(reserve, rateData);
>> (uint256 burnedScaledAmount, uint256 newTotalSupply, uint256 amountUnderlying) = IRToken(reserve.reserveRTokenAddress).burn(
recipient,
recipient,
amount,
reserve.liquidityIndex
);
amountWithdrawn = burnedScaledAmount;
updateInterestRatesAndLiquidity(reserve, rateData, 0, amountUnderlying);
>> emit Withdraw(recipient, amountUnderlying, burnedScaledAmount);
return (amountUnderlying, burnedScaledAmount, amountUnderlying);
}
Furthermore, the calculation of amountScaled is incorrect. To get scaled balance, we should divide normalized amount by index.
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);
_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);
}
Impact
Loss of Trust and Transparency
One of the key purposes of emitting events in smart contracts is to provide transparency and ensure all actions within the contract can be tracked and verified by external observers (like users or dApps). If the wrong event is emitted:
Users will be misled, which could result in a lack of trust in the contract or the dApp.
The emitted logs may show inaccurate or incorrect information, damaging the overall reliability of the system.
Tools Used
Manual Review
Recommendations
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);
_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);
+ return (amountScaled, totalSupply(), amount);
}