[L-1] Wrong return value in LendingPool :: withdraw
Description: According to withdraw function netspec in lending pool below
@return amountWithdrawn The amount withdrawn.
* @return amountScaled The scaled amount of RTokens burned.
* @return amountUnderlying The amount of underlying asset transferred
The return value where ment to be different especially amountScaled
which was supposed to return the scaled amount
But these return values ended up returning same values , which was not the initial plan of the protocol according to the netspec
in the burn function in Rtoken amount was scaled but not used, and this was supposed to be one of the returning values according to netspec
uint256 amountScaled = amount.rayMul(index);
Again in the burn function in Rtoken provided below this is the return value, it returns the amount twice and even where it was supposed to return scaled amount it returned the amount provided in the parameter
return (amount, totalSupply(), amount);
Then in the withdraw internal function in ReserveLibrary the protocol went ahead to assume that the burn function will return the amount scaled which they did not instead they return only the amount
(uint256 burnedScaledAmount, uint256 newTotalSupply, uint256 amountUnderlying) = IRToken(reserve.reserveRTokenAddress).burn(
recipient,
recipient,
amount,
reserve.liquidityIndex
In conclusion this is the final return value of the withdraw
return (amountUnderlying, burnedScaledAmount, amountUnderlying);
Meaning that if the user amount parameter was to be 100 then the return value will be
which was not the intention according to netspec
Impact: This Wrong assumption by the protocol leads to incorrect return values
Proof of Concept:
* @notice Handles withdrawal operation from the reserve.
* @dev Burns RTokens from the user and transfers the underlying asset.
* @param reserve The reserve data.
* @param rateData The reserve rate parameters.
* @param amount The amount to withdraw.
* @param recipient The address receiving the underlying asset.
* @return amountWithdrawn The amount withdrawn.
* @return amountScaled The scaled amount of RTokens burned.
* @return amountUnderlying The amount of underlying asset transferred. */
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);
}
function burn from Rtoken Contract
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);
}
Recommended Mitigation:
Kindly use the amount that was already scaled in the burn function