Core Contracts

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

Incorrect double scaling during transfers in RToken contract

Summary

The transfer functions in RToken contract performs doble scaling of the provided amount resulting in recipients receiving incorrect amounts.

Vulnerability Details

The protocol overrides the _update() function in RToken contract as follows:

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

The comment states cleraly that this will be applied for all operations including transfers.

The scaling done here is based on the current liquidityIndex in the lendingpool returned by getNormalizedIncome() and then the scaledAmount is what the is sent to the to address.

However, the protocol again defines _liquidityIndex which is set to WadRayMath.RAY during initialization.
Now when performing transfers, this _liquidityIndex is first used as shown here:

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

The amount provided is in underlying asset units but gets scaled by the currect liquidityIndex given by ILendingPool(_reservePool).getNormalizedIncome() in transfer() and the set _liquidityIndex in transferFrom() before being passed into the overriden transfer functions as scaledAmount.

However, internally, these functions will invoke the _update() function which again has been overriden as shown above.

As such, the amount gets scaled twice:

  1. Before calling super.transfer() and super.transferFrom() functions

  2. In the _update() function that calls super._update()

Impact

The intended funtionality is to scale the amount in underlying units during transfer based on the liquidityIndex. However, the double scaling results in the recipient of the transsfers ending up with incorrect amounts.

Tools Used

Manual Review

Recommendations

Scale once:

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

Lead Judging Commences

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