Core Contracts

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

TransferFrom operations in RToken contract are based on stale liquidity index

Summary

The updateLiquidityIndex() function in RToken contract is meant be only be called by the reserve pool contract. However, there is no such functionality implemented there to invoke it. This means that once initialized, the liquidity index used during transferFrom operations will remain constant throughout the contract liferime.

Vulnerability Details

In RToken contract, _liquidityIndex is initialized as follows:

_liquidityIndex = WadRayMath.RAY;

This liquidity index is then used in transferFrom() function as shown here:

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);
}

The protocol further implements a setter function for the liquidity index as shown here:

function updateLiquidityIndex(uint256 newLiquidityIndex) external override onlyReservePool {
if (newLiquidityIndex < _liquidityIndex) revert InvalidAmount();
>> _liquidityIndex = newLiquidityIndex;
emit LiquidityIndexUpdated(newLiquidityIndex);
}

The onlyReservePool modifier ensures that this function can only be called from the reserve pool (lending pool). However, there is no such functionality in lending pool that invokes this update function.

The lending pool mostly interacts with the ReserveLibrary.updateReserveState() to update the liquidityIndex for reserves during operations such as deposit, withdrawal, borrowing, liquidation etc as shown here:

// Update liquidity index using linear interest
>> reserve.liquidityIndex = calculateLiquidityIndex(
rateData.currentLiquidityRate,
timeDelta,
reserve.liquidityIndex
);

However after this is updated, it does not proceed to update it in the RToken contract.

In contrast, the transfer() function utilizes the updated liquidityIndex by fetching it directly during the operation:

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

This ensures that all transfers are based on the current accurate liquidity index while the approach in transferFrom() does not.

Impact

All transferFrom operations therefore in RToken will be performed based on the liquidity index set during initialization throughout the lifetime of the contract without any means to make modifications to it. This goes against the intended objective of the protocol.

Tools Used

Manual Review

Recommendations

Implement a functionality in lending pool to invoke the updateLiquidityIndex() in RToken contract or modify the transferFrom() as shown here.

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 7 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

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

inallhonesty Lead Judge 7 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

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.

Give us feedback!