Core Contracts

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

Overriding transfer and transferFrom will send less amount to receiver of RToken

Summary

The transfer() and transferFrom() functions scale the amount using rayDiv with liquidityIndex, which is correct. However, _update() also applies rayDiv, causing the scaling to be performed twice, resulting in users receiving less than intended.

Vulnerability Details

This issue exist in transfer and transferFrom But i will only focus on transfer function.
The transfer() apply rayDiv before calling super.transfer().

/contracts/core/tokens/RToken.sol:213
213: function transfer(address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
214: uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
215: return super.transfer(recipient, scaledAmount);
216: }

_update() also applies rayDiv, effectively reducing the transfer amount.

/contracts/core/tokens/RToken.sol:308
308: function _update(address from, address to, uint256 amount) internal override {
309: // Scale amount by normalized income for all operations (mint, burn, transfer)
310: uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
311: super._update(from, to, scaledAmount);
312: }

This issue will effect the receiver in case when liquidityIndex is not 1e27.

POC

Add Following test case on rToken.test.js in describe section Basic functionality. run with command npx hardhat test .
This test case will revert as the amount minted and transfered are not same which is issue.

it.only("should allow transfers between users", async function () { // @audit : issue in case of transfer and tranfer from the index is applied twice due to whcih the user will receive less token
const mintAmount = ethers.parseEther("100");
const index = RAY;
//crvUSD minted to user1
await mockLendingPool.mockMint(reservePool.address, user1.address, mintAmount, index);
await mockLendingPool.mockGetNormalizedIncome(ethers.parseUnits("2", 27));
let currentIndex = await mockLendingPool.getNormalizedIncome();
console.log("currentIndex" , currentIndex);
const transferAmount = ethers.parseEther("50");
await (rToken.connect(user1).transfer(user2.address, transferAmount))
await (rToken.connect(user2).transfer(user3.address,ethers.parseEther("25")))
let balanceUser3 = await rToken.balanceOf(user3.address); // 25
let scaledBalanceUser3 = await rToken.scaledBalanceOf(user3.address); // 12.5
console.log("balanceUser3" , balanceUser3);
console.log("scaledBalanceUser3" , scaledBalanceUser3);
// currentIndex = await mockLendingPool.getNormalizedIncome();
await mockLendingPool.mockMint(reservePool.address, user4.address, ethers.parseEther("25"), currentIndex);
let balanceUser4 = await rToken.balanceOf(user4.address); // 25
let scaledBalanceUser4 = await rToken.scaledBalanceOf(user4.address); // 12.5
console.log("balanceUser4" , balanceUser4);
console.log("currentIndex After" , currentIndex);
console.log("scaledBalanceUser4" , scaledBalanceUser4);
expect(scaledBalanceUser4).to.equal(scaledBalanceUser3);
expect(balanceUser4).to.equal(balanceUser3);
});

Impact

Users receive less than the expected amount when transferring RToken. Which result in loss of assets for receiver.

Tools Used

Manual Review, Unit Testing

Recommendations

Remove the redundant rayDiv operation from transfer() and transferFrom(), as it is already being done in _update().

- uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
- return super.transferFrom(sender, recipient, scaledAmount);
+ return super.transferFrom(sender, recipient, amount);
- uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
- return super.transfer(recipient, scaledAmount);
+ return super.transfer(recipient, amount);
Updates

Lead Judging Commences

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

Give us feedback!