Summary
A user loses funds when he transfers RToken.
Vulnerability Details
RToken.sol#_update() function is as follows.
function _update(address from, address to, uint256 amount) internal override {
uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
super._update(from, to, scaledAmount);
}
This function is called when mint, burn and transfer.
Here, it used LendingPool.sol#getNormalizedIncome() function for liquidity index.
File: LendingPool
function getNormalizedIncome() external view returns (uint256) {
return reserve.liquidityIndex;
}
As we can see above, getNormalizedIncome() returns old liquidityIndex, not updated index with currentTime.
And liquidityIndex is updated with following code.
File: ReserveLibrary.sol
function updateReserveInterests(ReserveData storage reserve,ReserveRateData storage rateData) internal {
@> uint256 timeDelta = block.timestamp - uint256(reserve.lastUpdateTimestamp);
if (timeDelta < 1) {
return;
}
uint256 oldLiquidityIndex = reserve.liquidityIndex;
if (oldLiquidityIndex < 1) revert LiquidityIndexIsZero();
@> reserve.liquidityIndex = calculateLiquidityIndex(
rateData.currentLiquidityRate,
@> timeDelta,
reserve.liquidityIndex
);
reserve.usageIndex = calculateUsageIndex(
rateData.currentUsageRate,
timeDelta,
reserve.usageIndex
);
reserve.lastUpdateTimestamp = uint40(block.timestamp);
emit ReserveInterestsUpdated(reserve.liquidityIndex, reserve.usageIndex);
}
Impact
Therefore, if a user transfers RToken, he uses old liquidity index. So more scaledAmount is transfered.
This is user's loss.
Tools Used
Manual review
Recommendations
Modify LendingPool.sol#getNormalizedIncome() to return current liquidity index.