Core Contracts

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

Incorrect Calculation in `calculateDustAmount` Function Leads to Dust Transfer Failure in the `RToken.sol` and `LendingPool` Contracts

Summary

The calculateDustAmount function in the RToken contract contains two related calculation errors:

  1. Redundant calculation of the interest rate index (index).

  2. Comparison between different units (standardized RToken vs non-standardized underlying token ).

These two errors combined cause the function to always return 0, making the dust transfer functionality completely ineffective and preventing the system from clearing dust assets.

Vulnerability Details

The calculateDustAmount function contains two major issues:

  1. Redundant Calculation of the Interest Rate Index: The index (interest rate) is already accounted for in totalSupply(). The function incorrectly multiplies currentTotalSupply by the index again, which results in an overestimation of the totalRealBalance.

  2. Comparison Between Different Units: The function compares contractBalance (in the RToken unit without interest) with totalRealBalance (in the underlying asset unit with interest), leading to mismatched units and an incorrect result. The function should be comparing values of the same unit.

Vulnerability Code
contracts/core/tokens/RToken.sol:calculateDustAmount#L317-L328

function calculateDustAmount() public view returns (uint256) {
uint256 contractBalance = IERC20(_assetAddress)
.balanceOf(address(this))
.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());// @audit: → RToken (standardized)
// @audit: Error 1: totalSupply() already includes the index
uint256 currentTotalSupply = totalSupply();
//@audit: Error 1 - Redundant multiplication by the index
uint256 totalRealBalance = currentTotalSupply.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
// @audit: Error 2 - Comparing different units (standardized RToken vs. Non-standardized underlying asset)
return contractBalance <= totalRealBalance ? 0 : contractBalance - totalRealBalance;
}

contracts/core/tokens/RToken.sol:totalSupply#L204

function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
// @audit: the function has calculated interest and returns totalSupply * index
return super.totalSupply().rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}

contracts/core/tokens/RToken.sol:transferAccruedDust#L354

function transferAccruedDust(address recipient, uint256 amount) external onlyReservePool {
// @audit: call the `calculateDustAmount` function
uint256 poolDustBalance = calculateDustAmount();

contracts/core/pools/LendingPool/LendingPool.sol:transferAccruedDust#L747

function transferAccruedDust(address recipient, uint256 amount) external onlyOwner {
// @audit: call the transferAccruedDust function in the RToken.sol contract
IRToken(reserve.reserveRTokenAddress).transferAccruedDust(recipient, amount);

Impact

  • Dust Transfer Failure: The dust transfer functionality (transferAccruedDust) fails because calculateDustAmount always returns 0.

  • Locked Funds: Dust funds, which are small leftover amounts in the contract, cannot be cleared or transferred, potentially leaving funds locked in the contract permanently.

  • System Inefficiency: The inability to clear dust funds increases contract inefficiency and can negatively impact the platform’s liquidity and asset management.

Tools Used

Manual Code Review

Recommendations

To fix this issue, the redundant calculation of the interest rate index should be removed, and the comparison should be done using consistent units. The updated implementation should look like this:

function calculateDustAmount() public view returns (uint256) {
// @audit: Compare using standardized values
uint256 contractBalance = IERC20(_assetAddress)
.balanceOf(address(this))
.rayDiv(ILendingPool(_reservePool).getNormalizedIncome()); // @audit: RToken (standardized)
uint256 currentTotalSupply = totalSupply(); @audit: totalSupply already has count for interests
- uint256 totalRealBalance = currentTotalSupply.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
+ uint256 totalRealBalance = currentTotalSupply.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
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!