Summary
ReserveLibrary.withdraw assigns incorrect return values from RToken.burn, leading to improper scaling of withdrawal amounts and potential miscalculations of user balances.
Details
ReserveLibrary.withdraw says in the comments that it returns the scaled amount of RTokens burned
* @return amountWithdrawn The amount withdrawn.
* @return amountScaled The scaled amount of RTokens burned.
* @return amountUnderlying The amount of underlying asset transferred.
(
@> uint256 burnedScaledAmount,
uint256 newTotalSupply,
uint256 amountUnderlying
) = IRToken(reserve.reserveRTokenAddress).burn(
recipient,
recipient,
amount,
reserve.liquidityIndex
);
amountWithdrawn = burnedScaledAmount;
Let’s check RToken::burn function:
* @notice Burns RToken from a user and transfers underlying asset
* @param from The address from which tokens are burned
* @param receiverOfUnderlying The address receiving the underlying asset
* @param amount The amount to burn (in underlying asset units)
* @param index The liquidity index at the time of burning
* @return A tuple containing:
* - uint256: The amount of scaled tokens burned
* - uint256: The new total supply after burning
* - uint256: The amount of underlying asset transferred
*/
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);
}
The problem is, uint256 amountScaled = amount.rayMul(index); is not used. Instead, we see that in the return statement the amount that’s passed in params is returned for both burnedScaledAmount and amountUnderlying values defined in ReserveLibrary.withdraw . See ReserveLibrary.withdraw function:
(
uint256 burnedScaledAmount,
uint256 newTotalSupply,
uint256 amountUnderlying
) = IRToken(reserve.reserveRTokenAddress).burn(
recipient,
recipient,
amount,
reserve.liquidityIndex
);
amountWithdrawn = burnedScaledAmount;
As it is seen, for both burnedScaledAmount and amountUnderlying vaues, the amount from RToken::burn is passed, which is wrong if we go back all the way to the comment @return amountScaled The scaled amount of RTokens burned. on ReserveLibrary::withdraw function
Impact
Incorrect scaling leads to miscalculations.