Core Contracts

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

Double Scaling of `totalSupply` in `RToken::calculateDustAmount` Function

Summary

The calculateDustAmount function in the RToken contract incorrectly scales the totalSupply value twice, leading to inaccurate calculations of the dust amount. This issue arises because the totalSupply function already returns a scaled value, and scaling it again results in an incorrect totalRealBalance. This could lead to incorrect dust calculations, affecting the protocol's accounting and the ability to rescue dust tokens.

Vulnerability Details

In the calculateDustAmount function, where the totalSupply value is scaled twice:

function calculateDustAmount() public view returns (uint256) {
// Calculate the actual balance of the underlying asset held by this contract
uint256 contractBalance = IERC20(_assetAddress).balanceOf(address(this)).rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
// Calculate the total real obligations to the token holders
@>> uint256 currentTotalSupply = totalSupply();
// Calculate the total real balance equivalent to the total supply
@>> uint256 totalRealBalance = currentTotalSupply.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
// All balance, that is not tied to rToken are dust (can be donated or is the rest of exponential vs linear)
return contractBalance <= totalRealBalance ? 0 : contractBalance - totalRealBalance;
}
//...
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
// Already scaled amount
@>> return super.totalSupply().rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}

The totalSupply function in the RToken contract already returns a scaled value (i.e., the total supply multiplied by the normalized income). By scaling it again using rayMul, the totalRealBalance becomes excessively large, leading to incorrect dust calculations. This double scaling results in an underestimation of the dust amount, which is the difference between the contract's actual balance and the total obligations to token holders.

Consider the following scenario:

  1. The lending pool has a normalized income of 1.1e27 (10% interest accrued).

  2. The contract holds 100e18 units of the underlying asset.

  3. The total supply of RTokens is 90e18, which corresponds to 99e18 units of the underlying asset when scaled by the normalized income (90e18 * 1.1e27 / 1e27 = 99e18).

In this case, the dust amount should be 1e18 (100e18 - 99e18). However, due to the double scaling, the totalRealBalance is calculated as:

uint256 currentTotalSupply = totalSupply(); // Returns 99e18 (already scaled)
uint256 totalRealBalance = currentTotalSupply.rayMul(1.1e27); // 99e18 * 1.1e27 = 108.9e45

The dust amount is then calculated as:

uint256 dustAmount = 100e18 - 108.9e45; // Underflow revert

This results in an incorrect dust amount, potentially causing an underflow revert or a significantly underestimated value.

Impact

  1. Incorrect Dust Calculation: The dust amount may be significantly underestimated, leading to incorrect accounting of the contract's balance.

  2. Underflow Reverts: If the double-scaled totalRealBalance exceeds the contract's actual balance, an underflow revert may occur, preventing the function from executing.

Tools Used

Manual review

Recommendations

The calculateDustAmount function should use the scaledTotalSupply function instead of totalSupply. The scaledTotalSupply function returns the non-scaled total supply, which can then be correctly scaled by the normalized income. The corrected function should look like this:

function calculateDustAmount() public view returns (uint256) {
// Calculate the actual balance of the underlying asset held by this contract
uint256 contractBalance = IERC20(_assetAddress).balanceOf(address(this));
// Calculate the total real obligations to the token holders
- uint256 currentTotalSupply = totalSupply();
+ uint256 currentTotalSupply = scaledTotalSupply(); // Use scaledTotalSupply instead of totalSupply
uint256 totalRealBalance = currentTotalSupply.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
// All balance, that is not tied to rToken are dust (can be donated or is the rest of exponential vs linear)
return contractBalance <= totalRealBalance ? 0 : contractBalance - totalRealBalance;
}
Updates

Lead Judging Commences

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

RToken::calculateDustAmount incorrectly applies liquidity index, severely under-reporting dust amounts and permanently trapping crvUSD in contract

Support

FAQs

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

Give us feedback!