Core Contracts

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

Incorrect scaling in mint function can lead to token supply inconsistency

Summary

The RToken contract's mint function incorrectly handles token scaling, creating a mismatch between minted tokens and underlying assets. When users deposit assets, the contract should mint scaled tokens representing their share of the pool, but instead mints raw amounts, breaking the fundamental interest-bearing mechanics.

function mint(
address caller,
address onBehalfOf,
uint256 amountToMint,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256, uint256) {
if (amountToMint == 0) {
return (false, 0, 0, 0);
}
// Correctly calculates the scaled amount based on current index
uint256 amountScaled = amountToMint.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
// Calculates interest accrual for existing balance
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
// Updates user's index to current value
_userState[onBehalfOf].index = index.toUint128();
// BUG: Mints raw amount instead of scaled amount
// This breaks the relationship between minted tokens and underlying assets
_mint(onBehalfOf, amountToMint.toUint128()); //<< Should be: _mint(onBehalfOf, amountScaled.toUint128());
emit Mint(caller, onBehalfOf, amountToMint, index);
// Returns potentially incorrect values due to unscaled minting
return (isFirstMint, amountToMint, totalSupply(), amountScaled);
}

The issue is in the _mint call where it uses amountToMint instead of amountScaled. This creates a mismatch between the token supply and the underlying asset pool's value.

Vulnerability Details

The heart of this issue lies in how RToken handles interest-bearing deposits. Imagine a savings account where the bank accidentally gives you the wrong number of shares in the pool, that's exactly what's happening here.

When users deposit assets into the lending pool, the RToken contract should mint them a proportional amount of shares based on the current interest rate index. If the pool has been accruing interest and the index is 1.5, a deposit of 100 tokens should result in roughly 67 shares (100/1.5). However, the contract is minting shares 1:1 with deposits, completely ignoring the index.

Let's walk through a real scenario:

  1. The lending pool has been running for a while, accumulating an index of 1.5

  2. Alice deposits 1000 USDC

  3. Instead of receiving ~667 RTokens (1000/1.5), she gets 1000 RTokens

  4. When she withdraws, she can claim 1500 USDC (1000 * 1.5), stealing 500 USDC from other depositors

mint(caller, onBehalfOf, amountToMint, index)
→ _mint(onBehalfOf, amountToMint.toUint128())
→ balanceOf() checks

The core of the problem is in this seemingly innocent line: RToken.sol#L136

_mint(onBehalfOf, amountToMint.toUint128()); // Uses raw amount instead of scaled

This is equivalent to a bank crediting your account with $150 when you deposit $100 just because other depositors have earned interest.

Impact

Users depositing assets receive more tokens than they should, diluting existing holders' shares. For example, if the current index is 1.5 (indicating 50% interest accrual), a deposit of 100 tokens should mint ~67 scaled tokens, but the contract mints 100, giving the user excessive ownership rights.

function mint(
uint256 amountScaled = amountToMint.rayDiv(index);
// Mints raw amount instead of scaled amount
_mint(onBehalfOf, amountToMint.toUint128()); // Should use amountScaled
}

The contract fails to maintain the core invariant that scaled token supply must always equal total deposits divided by the current index. This relationship ensures fair distribution of interest among holders.

Recommendations

The fix is straightforward, mint the scaled amount instead

_mint(onBehalfOf, amountScaled.toUint128()); // Correctly scales by index
Updates

Lead Judging Commences

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

RToken::mint should mint the amountScaled not the amountToMint

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

RToken::mint should mint the amountScaled not the amountToMint

Support

FAQs

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