Vulnerability Details
The RToken.sol
implements an interest-bearing token where users' balances increase over time as they accrue interest.
rebase mechanism:
function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}
The amount to burn is incorrect which should be determined based on the following calculation formula.
formula:
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
_burn(from, amount.toUint128());
}
Additionally, due to that the balance stored in storage is a value which has been divided by index, it will revert by Openzeppelin's default implementation if amount > userBalance
or _balance[account] < amount <= userBalance
.
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
uint256 userBalance = balanceOf(from);
if(amount > userBalance){
amount = userBalance;
}
_burn(from, amount.toUint128());
}
Impact
Due to incorrect input, the #burn
function will mint an excessive amount of tokens. Some cases may result in the inability to burn tokens.
Recommendations
contract RToken is ERC20, ERC20Permit, IRToken, Ownable {
function _accrueIndex() internal {
_liquidityIndex = ILendingPool(_reservePool).getNormalizedIncome();
}
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
_accrueIndex();
uint256 amountScaled = amountToMint.rayDiv(index);
_burn(from, amountScaled);
}
}