Core Contracts

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

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

Vulnerability Details and Impact

In RToken.sol#transferFrom, the _liquidityIndex is not updated before transfer. The scaledAmount will be overestimated, leading to the transfer of excess tokens.

/// @dev https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RToken.sol#L223
function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
// _liquidityIndex not updated
uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
return super.transferFrom(sender, recipient, scaledAmount);
}

Due to that _update function is invoked in default implementation, the amount to transfer will be divided twice.

// invoke _update in
// - super.transfer(recipient, scaledAmount);
// - super.transferFrom(sender, recipient, scaledAmount);
function _update(address from, address to, uint256 amount) internal override {
// amount * ray / index
uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
super._update(from, to, scaledAmount);
}

Tools Used

Manual.

Recommendations

contract RToken is ERC20, ERC20Permit, IRToken, Ownable {
// accrue
function _accrueIndex() internal {
_liquidityIndex = ILendingPool(_reservePool).getNormalizedIncome();
}
function transfer(address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
_accrueIndex();
uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
return super.transfer(recipient, scaledAmount);
}
function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
_accrueIndex();
uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
return super.transferFrom(sender, recipient, scaledAmount);
}
// https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RToken.sol#L307
function _update(address from, address to, uint256 amount) internal override {
// remove-next-line
// uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
super._update(from, to, scaledAmount);
}
}
Updates

Lead Judging Commences

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

RToken::transfer uses getNormalizedIncome() while transferFrom uses _liquidityIndex, creating inconsistent transfer amounts depending on function used

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

RToken::transfer uses getNormalizedIncome() while transferFrom uses _liquidityIndex, creating inconsistent transfer amounts depending on function used

Support

FAQs

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