Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

`RToken.sol#burn`: Incorrectly implementation for rebase token

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) {
// balance stored in storage
uint256 scaledBalance = super.balanceOf(account);
// balance * index / ray
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}

The amount to burn is incorrect which should be determined based on the following calculation formula.

formula:

/// @dev https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RToken.sol#L212
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
// ...
// incorrect input
_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 {
// accrue
function _accrueIndex() internal {
_liquidityIndex = ILendingPool(_reservePool).getNormalizedIncome();
}
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
// accrue index
_accrueIndex();
uint256 amountScaled = amountToMint.rayDiv(index);
// burn share token
_burn(from, amountScaled);
// ...
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RToken::burn incorrectly burns amount (asset units) instead of amountScaled (token units), breaking token economics and interest-accrual mechanism

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RToken::burn incorrectly burns amount (asset units) instead of amountScaled (token units), breaking token economics and interest-accrual mechanism

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.