Core Contracts

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

Double Index Division in RToken Transfer Implementation

Summary

The RToken contract applies normalized income scaling twice during transfers, dividing by the index value twice (in transfer and _update ) instead of once, resulting in reduced transfer amounts. The reduction becomes more severe as the index grows over time.

Vulnerability Details

The transferfunction scales the transfer amountby dividing by the liquidity index.

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

However, _update also scales the already scaled amount, resulting in double scaling of the original transfer amount.

function _update(address from, address to, uint256 amount) internal override {
// Scale amount by normalized income for all operations (mint, burn, transfer)
uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
super._update(from, to, scaledAmount);
}

Impact

The actual transfer amount reduces as the index grows

Proof of concept

Copy the test to ./test/unit/core/tokens/RToken.test.js

Then run npx hardhat test ./test/unit/core/tokens/RToken.test.js

it.only("Incorrect transfer amount", async function () {
const index = ethers.parseUnits("2", 27);
mockLendingPool.mockGetNormalizedIncome(index);
const mintAmount = ethers.parseEther("100");
//crvUSD minted to user1
await mockLendingPool.mockMint(reservePool.address, user1.address, mintAmount, index);
const transferAmount = ethers.parseEther("50");
await rToken.connect(user1).transfer(user2.address, transferAmount);
const scaledBalanceUser1 = await rToken.balanceOf(user1.address);
const scaledBalanceUser2 = await rToken.balanceOf(user2.address);
// assert that the amount transferred is actually transferAmount / index
expect(scaledBalanceUser1).to.equal(mintAmount - transferAmount / 2n);
expect(scaledBalanceUser2).to.equal(transferAmount / 2n);
});
});

Tools Used

Manual

Vs code

Recommendations

The transfer amount should only be scaled once, in _update

Updates

Lead Judging Commences

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

RToken::transfer and transferFrom double-scale amounts by dividing in both external functions and _update, causing users to transfer significantly less than intended

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

RToken::transfer and transferFrom double-scale amounts by dividing in both external functions and _update, causing users to transfer significantly less than intended

Support

FAQs

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