Core Contracts

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

Balance is double scaled in RToken

Summary

The RToken contract implements an interest-bearing token for the RAAC lending protocol. However, there is a critical issue in the transfer and _update functions where the token amount is scaled twice, leading to incorrect calculations. Specifically, the transfer function scales the amount before calling super.transfer, which internally invokes the _update function, where the amount is scaled again. This duplicate scaling results in users losing tokens during transfers, as the final amount is significantly smaller than intended.

Vulnerability Details

The transfer function scales the amount using ILendingPool(_reservePool).getNormalizedIncome(), which represents the current liquidity index (cumulative interest).

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

The scaled amount (scaledAmount) is then passed to super.transfer, which internally calls the _update function.

The _update function scales the amount again using the same ILendingPool(_reservePool).getNormalizedIncome().

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

This results in the amount being scaled twice, leading to an incorrect final value.

The same issue exits in transferFrom , mint and burn .

POC

add the following test case in RToken.test.js

it("balance is double scaled", async function () {
const WadRayMathMock = await ethers.getContractFactory("WadRayMathMock");
const wadRayMathMock = await WadRayMathMock.deploy();
const mintAmount = ethers.parseEther("100");
//crvUSD minted to user1
const liquidityIndex = ethers.parseUnits("2", 27);
await mockLendingPool.mockMint(reservePool.address, user1.address, mintAmount, liquidityIndex);
await mockLendingPool.mockGetNormalizedIncome(liquidityIndex);
const transferAmount = ethers.parseEther("50");
await rToken.connect(user1).transfer(user2.address, transferAmount)
const scaledBalanceUser1 = await rToken.scaledBalanceOf(user2.address);
// amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome())
const expected = await wadRayMathMock.rayDiv(transferAmount,liquidityIndex);
console.log("scaledBalanceUser1:",scaledBalanceUser1);
console.log("expected:",expected);
expect(scaledBalanceUser1).to.equal(expected);
});

run npx hardhat test --grep "balance is double scaled"

RToken
Basic functionality
scaledBalanceUser1: 12500000000000000000n
expected: 25000000000000000000n
1) balance is double scaled
0 passing (11s)
1 failing
1) RToken
Basic functionality
balance is double scaled:
AssertionError: expected 12500000000000000000 to equal 25000000000000000000.
+ expected - actual
-12500000000000000000
+25000000000000000000

The balance is much bigger than expected.

Impact

  • Incorrect Token Transfers: The duplicate scaling causes the final transferred amount to be much smaller than intended, as the amount is divided by the normalized income twice.

  • User Losses: Users lose tokens during transfers due to the incorrect scaling logic.

  • Logical Inconsistency: The contract's behavior deviates from its intended design, where token amounts should be scaled only once to reflect the current interest rates.

The impact is High, the likelihood is High, so the severity is High.

Tools Used

Manual Review

Recommendations

Consider removing the _update function.

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.