Core Contracts

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

`RToken::transferFrom` Uses Different `liquidityIndex` than `RToken::transfer` Leading to Different Exchange Rates

Summary

RToken::transfer get's the current live liquidityIndex for scaling on transfer amounts, while RToken::transferFrom relies on the RToken's global variable _liquidityIndex

Vulnerability Details

[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RToken.sol#L213)

[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RToken.sol#L224)

The issue that arises is the missmatch between liquidity indexes if RToken::updateLiquidityIndex hasn't been called. This function needs to be called and manually updated making it at risk of becoming a stale index, while transfer is always up to date which will lead to different transfer ratios.

function transfer(address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
@> uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
return super.transfer(recipient, scaledAmount);
}
function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
@> uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
return super.transferFrom(sender, recipient, scaledAmount);
}

Example:
Scaled with current liquidityIndex (1.0537e27)
scaledAmount = 50e18 / 1.0537e27 = 47.45e18
Result:
User1: 50.054 RTokens (scaled up due to accrued interest)
User2: 49.890 RTokens (transfer fee + scaling)

Using transferFrom():
Scaled with stale _liquidityIndex (1e27)
scaledAmount = 50e18 / 1e27 = 50e18
Result:
User1: 50.000 RTokens (no interest accrual applied)
User2: 49.945 RTokens (only transfer fee)

Impact

  • Users can game the system by choosing the more favorable method or get unexpected token amounts

  • Protocol revenue/accounting affected by inconsistent scaling

Tools Used

Foundry

Recommendations

Change transferFrom so both functions use getNormalizedIncome

function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
- uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
+ uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
return super.transferFrom(sender, recipient, 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.