Core Contracts

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

Incorrect `RToken` Balances Due to Missing Index Adjustment in `balanceOf` and `totalSupply`

Summary

The RToken contract stores user balances in a scaled form based on a liquidity index but fails to correctly adjust the reported balances in its balanceOf and totalSupply functions. As a result, users see significantly lower balances than expected, which can lead to incorrect fund distributions during withdrawals.

Vulnerability Details

The RToken contract is designed to store balances in a scaled form using a liquidity index. In the _update function, the amount is scaled by dividing by the current normalized income:

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

Then, the balanceOf and totalSupply functions should convert the stored scaled values back to actual balances by multiplying by the current liquidity index:

function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
return super.totalSupply().rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}

Due to an implementation error, the actual adjustments are not being correctly applied. Consequently, users see the raw scaled balances rather than the properly adjusted actual balances.

Proof-of-Concept (PoC)

  1. Initial Deposit:

    • A user deposits 100 crvUSD when the liquidity index is 1e27 (RAY).

    • The _update function scales the amount as follows:

      scaledAmount = 100e18 / 1e27 = 100 / 1e9 = 1e-7 (scaled units)
    • Expected Behavior:
      The actual balance should be recovered by multiplying the scaled balance by the liquidity index:

      balance = 1e-7 * 1e27 = 100e18 (i.e., 100 tokens)
    • Actual Behavior:
      Due to missing or incorrect index adjustment, balanceOf(user) erroneously returns approximately 1e-7 tokens, which is far lower than the expected 100 tokens.

  2. Index Increase:

    • Later, the liquidity index increases to 1.1e27.

    • Expected Actual Balance:

      balance = 1e-7 * 1.1e27 = 110e18 tokens (110 tokens)
    • Actual Behavior:
      The system still reports the balance as the raw scaled value (≈1e-7 tokens), ignoring the updated liquidity index.

  3. Withdrawal Attempt:

    • When the user attempts to withdraw tokens, the incorrect balance causes the system to miscalculate the withdrawal amount, leading to significant underpayment.

Impact

Users see drastically lower token balances than they should, leading to potential financial losses during withdrawals.

Tools Used

Manual Review

Recommendations

Ensure that the balanceOf and totalSupply functions properly apply the liquidity index when converting stored scaled balances to actual balances. Verify the implementation of rayDiv and rayMul functions and ensure they are used consistently in all balance-related operations. The intended functions should look like this:

Updates

Lead Judging Commences

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

DebtToken::totalSupply incorrectly uses rayDiv instead of rayMul, severely under-reporting total debt and causing lending protocol accounting errors

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

DebtToken::totalSupply incorrectly uses rayDiv instead of rayMul, severely under-reporting total debt and causing lending protocol accounting errors

Support

FAQs

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