Core Contracts

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

Exchange Rate Calculation Mismatch Between RToken and DEToken Minting

Summary

The Stability Pool's exchange rate calculation between RToken and DEToken deposits contains a fundamental flaw in decimal handling. When users deposit RTokens, the contract incorrectly scales the amount when minting DETokens, leading to a mismatch between expected and actual DEToken amounts.

function calculateDeCRVUSDAmount(uint256 rcrvUSDAmount) public view returns (uint256) {
// CRITICAL: Incorrect decimal scaling creates inflated DEToken minting
// The scaling factor calculation compounds the decimal adjustment with the base 18 decimals
uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
// IMPACT: This calculation results in a 10% inflation of DETokens vs RTokens
// For 1e18 RToken input (1 token), returns 1.1e18 DETokens due to double decimal scaling
return (rcrvUSDAmount * scalingFactor) / getExchangeRate();
}

The core issue lies in its decimal handling. It applies an 18-decimal scaling on top of the token decimal adjustment, effectively double-counting the base decimal places. This creates the systematic overvaluation of DETokens relative to deposited RTokens.

Vulnerability Details

The exchange rate calculation in StabilityPool.sol uses: StabilityPool.sol#L192-L193

// Incorrect scaling factor calculation
uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
return (rcrvUSDAmount * scalingFactor) / getExchangeRate();

This creates a mismatch when decimal places differ between RToken and DEToken.

The exchange rate miscalculation means users depositing into the Stability Pool receive an incorrect amount of DETokens. This creates an arbitrage opportunity where users could extract excess value by depositing and withdrawing at advantageous rates, directly impacting the protocol's stability mechanism.

Consider this attack Flow

A user deposits 1 RToken (1e18 units) into the Stability Pool. Due to the decimal scaling error, instead of receiving 1 DEToken, they receive 1.1 DETokens (1.1e18 units). This 10% inflation of DETokens dilutes other depositors' share of the pool.

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
// Exchange rate calculation happens here
_update();
// First transfers rToken from user
rToken.safeTransferFrom(msg.sender, address(this), amount);
// The VULNERABILITY: Incorrect DEToken amount calculation
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
// Mints inflated amount of DETokens
deToken.mint(msg.sender, deCRVUSDAmount);
// Tracks user deposits in rToken terms
userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}

The calculateDeCRVUSDAmount function applies an incorrect scaling factor when converting between token decimals.

function calculateDeCRVUSDAmount(uint256 rcrvUSDAmount) public view returns (uint256) {
// Incorrect decimal scaling creates inflated DEToken minting
// The scaling factor calculation compounds the decimal adjustment with the base 18 decimals
uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
// THE IMPACT: This calculation results in a 10% inflation of DETokens vs RTokens
// For 1e18 RToken input (1 token), returns 1.1e18 DETokens due to double decimal scaling
return (rcrvUSDAmount * scalingFactor) / getExchangeRate();
}

When Alice deposits 1 RToken into the Stability Pool, she expects to receive 1 DEToken in return. The protocol's exchange rate is meant to maintain this 1:1 peg. However, due to the subtle decimal scaling error, she actually receives 1.1 DETokens. This 10% inflation creates an arbitrage opportunity that undermines the entire stability mechanism.

The exchange rate calculation starts in StabilityPool.sol where the deposit() function accepts RTokens. The crucial miscalculation happens in calculateDeCRVUSDAmount()

uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
return (rcrvUSDAmount * scalingFactor) / getExchangeRate();

This seemingly innocent decimal adjustment creates a compounding error. Each deposit increases the DEToken supply more than it should, diluting the claims on the underlying RToken reserves.

Real-World Impact

Think of this like a bank accidentally giving out $110 in withdrawal receipts for every $100 deposited. Over time, the bank would have more claims against its reserves than actual deposits. In the protocol's case, this means the Stability Pool meant to be the backbone of the system's price stability becomes increasingly undercollateralized.

We caught this by comparing:

  • Expected DETokens: 1000000000000000000 (1e18)

  • Actual minted: 1100000000000000000 (1.1e18)

This 10% difference compounds with each deposit, potentially leading to a complete destabilization of the protocol's core mechanism.

Impact

The decimal precision mismatch between RToken and DEToken leads to incorrect DEToken minting amounts. Users could receive more or fewer DETokens than intended based on their RToken deposits.

Recommendations

The correct implementation should normalize the token decimals without the additional 18-decimal scaling

function calculateDeCRVUSDAmount(uint256 rcrvUSDAmount) public view returns (uint256) {
// Only adjust for token decimal differences
uint256 normalizedAmount = rcrvUSDAmount * 10**deTokenDecimals;
return normalizedAmount / getExchangeRate();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Incorrect scaling factor formula in StabilityPool::calculateRcrvUSDAmount function

Both tokens have 18 decimals. Info

Support

FAQs

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