Core Contracts

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

Un-updated liquidity index leads to incorrect amount transferred when users call `RToken::transferFrom`

Summary

The RToken contract has an updateLiquidityIndex function that is never called, while maintaining its own _liquidityIndex state variable. This creates a discrepancy with the liquidityIndex tracked in ReserveLibrary, leading to incorrect amount calculation in RToken's transferFrom function.

Vulnerability Details

The issue stems from two parallel but disconnected liquidity index tracking mechanisms:

In ReserveLibrary:

struct ReserveData {
uint128 liquidityIndex; // Updated regularly through updateReserveInterests()
...
}

In RToken:

// @audit Never updated after initialization to WadRayMath.RAY
uint256 private _liquidityIndex;

The function updateLiquidityIndex is never called from the pool, which means the _liquidityIndex will forever remain stale.

// @audit this function is not being called from the pool
function updateLiquidityIndex(uint256 newLiquidityIndex) external override onlyReservePool {
_liquidityIndex = newLiquidityIndex;

But on the other hand, the ReserveLibrary correctly updates its own liquidityIndex through updateReserveInterests(), which is called before any operation, So we have inconsistency of the two separately tracked liquidity indices.

So the problem is that, RToken's _liquidityIndex remains static at WadRayMath.RAY (1e27) because updateLiquidityIndex() is never called, yet the _liquidityIndex is used in the transferFrom function, see below;

Looking at the transferFrom it uses this stale _liquidityIndex.

function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
// @audit Uses stale _liquidityIndex
uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
return super.transferFrom(sender, recipient, scaledAmount);
}

The critical discrepancy appears in RToken's transferFrom function where the stale _liquidityIndex is used for scaling amounts, while other functions like transferFrom and balanceOf correctly use the updated index from LendingPool:

PoC

  1. Alice deposits 1000 USDC into LendingPool, receives 1000 RTokens

  2. Time passes, interest accrues, ReserveData.liquidityIndex increases to 1.1e27

  3. Alice approves her 100 RTokens to Bob

  4. Bob calls the transferFrom function which uses stale _liquidityIndex (1e27), calculating incorrect scaled amount

  5. The actual value transferred will be incorrect because the scaling doesn't account for accrued interest

Impact

  • Incorrect transfer amounts when users approve their RTokens to be spent transfered from them.

Tools Used

Manual code review

Recommendations

Remove the unused _liquidityIndex from RToken and modify transfer functions to use LendingPool's index:

function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
return super.transferFrom(sender, recipient, scaledAmount);
}
Updates

Lead Judging Commences

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

RToken::updateLiquidityIndex() has onlyReservePool modifier but LendingPool never calls it, causing transferFrom() to use stale liquidity index values

Support

FAQs

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