Core Contracts

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

Stale Index Used In `RToken::transferFrom()` Due to Missing Index Updates

Summary

The transferFrom() function uses a stale _liquidityIndex that is never updated, leading to incorrect token scaling and transferred amounts.

Vulnerability Details

The transferFrom function uses _liquidityIndex storage variable on the RToken contract instead of the current index from the Lending Pool. The _liquidityIndex can only be updated by the LendingPool but there is no code to allow this so the index used to scale transerFrom will never change from the initial index.

PoC

Add this to the RToken.test.js file

it("should use consistent scaling between transfer and transferFrom", async function () {
// Setup
const mintAmount = ethers.parseEther("100");
await mockLendingPool.mockMint(reservePool.address, user1.address, mintAmount, RAY);
// Test transfer with same index
const transferAmount = ethers.parseEther("25");
const user2BalanceBeforeTransfer = await rToken.balanceOf(user2.address);
await rToken.connect(user1).transfer(user2.address, transferAmount);
const balanceAfterTransfer1 = await rToken.balanceOf(user2.address);
const netTransfer = balanceAfterTransfer1 - user2BalanceBeforeTransfer;
// Approve for transferFrom
await rToken.connect(user1).approve(user3.address, transferAmount);
// Test transferFrom
const user2BalanceBeforeTranferFrom = await rToken.balanceOf(user2.address);
await rToken.connect(user3).transferFrom(user1.address, user2.address, transferAmount);
const balanceAfterTransferFrom1 = await rToken.balanceOf(user2.address);
const netTransferFrom1 = balanceAfterTransferFrom1 - user2BalanceBeforeTranferFrom;
console.log("Balance after transfer:", balanceAfterTransfer1.toString());
console.log("Balance after transferFrom:", balanceAfterTransferFrom1.toString());
// Should be equal
expect(netTransferFrom1).to.equal(netTransfer);
// Simulate 10% interest accrual
const newIndex = RAY + (RAY / 10n); // 1.1e27
await mockLendingPool.mockGetNormalizedIncome(newIndex);
// Test transfer with updated intrest rate on reserve but not on RToken
const user2BalanceBeforeTransfer2 = await rToken.balanceOf(user2.address);
await rToken.connect(user1).transfer(user2.address, transferAmount);
const balanceAfterTransfer2 = await rToken.balanceOf(user2.address);
const netTransfer2 = balanceAfterTransfer2 - user2BalanceBeforeTransfer2;
// Approve for transferFrom
await rToken.connect(user1).approve(user3.address, transferAmount);
// Test transferFrom
const user2BalanceBeforeTranferFrom2 = await rToken.balanceOf(user2.address);
await rToken.connect(user3).transferFrom(user1.address, user2.address, transferAmount);
const balanceAfterTransferFrom2 = await rToken.balanceOf(user2.address);
const netTransferFrom2 = balanceAfterTransferFrom2 - user2BalanceBeforeTranferFrom2;
console.log("Balance after transfer:", balanceAfterTransfer2.toString());
console.log("Balance after transferFrom:", balanceAfterTransferFrom2.toString());
// Should be equal
expect(netTransfer2).to.equal(netTransferFrom2);
});

Recommendations

Update the transferFrom function to use the current index

function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
+ uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
- uint256 scaledAmount = amount.rayDiv(_liquidityIndex); //@audit note scaled amount is different than the transfer function
return super.transferFrom(sender, recipient, scaledAmount);
}
Updates

Lead Judging Commences

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