Summary
burn function in RToken contract is defined as follows:
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 amount passed as argument is the amount to burn in underlying assets (= amount to withdraw).
The issue arises because this amount is used directly for burning :
_burn(from, amount.toUint128());
This is incorrect, as only the scaled amount in RToken units should be burned :
_burn(from, amountScaled);
while amount of underlying assets is transferred back to users with:
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
amountScaled is also problematic, as it uses amount and multiplies it by the index. This is incorrect as amount should instead be divided by the index:
uint256 amountScaled = amount.rayMul(index);
Impact
The impact of this issue is high as it leads to incorrect computation that will ultimately:
This means the user will loose funds as he will redeem 1:1 while each RToken is worth more underlying assets over time.
Tools Used
Manual review.
Recommendations
Make sure to correctly compute amountScaled and use when burning RTokens:
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.rayDiv(index);
_userState[from].index = index.toUint128();
_burn(from, amountScaled);
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
emit Burn(from, receiverOfUnderlying, amount, index);
return (amountScaled, totalSupply(), amount);
}