Core Contracts

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

Issue in Exchange Rate

Summary

The getExchangeRate function is hardcoded to return 1e18, ignoring the actual balances of rToken and deTokenwhich is problematic as will be shown below.

Vulnerability Details

/**
* @notice Calculates the amount of deToken to mint for a given rToken deposit.
* @param rcrvUSDAmount Amount of rToken deposited.
* @return Amount of deToken to mint.
*/
function calculateDeCRVUSDAmount(uint256 rcrvUSDAmount) public view returns (uint256) {
uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
return (rcrvUSDAmount * scalingFactor) / getExchangeRate();
}
/**
* @notice Calculates the amount of rToken to return for a given deToken redemption.
* @param deCRVUSDAmount Amount of deToken to redeem.
* @return Amount of rToken to return.
*/
function calculateRcrvUSDAmount(uint256 deCRVUSDAmount) public view returns (uint256) {
uint256 scalingFactor = 10**(18 + rTokenDecimals - deTokenDecimals);
return (deCRVUSDAmount * getExchangeRate()) / scalingFactor;
}
/**
* @notice Gets the current exchange rate between rToken and deToken.
* @return Current exchange rate.
*/
function getExchangeRate() public view returns (uint256) {
// uint256 totalDeCRVUSD = deToken.totalSupply();
// uint256 totalRcrvUSD = rToken.balanceOf(address(this));
// if (totalDeCRVUSD == 0 || totalRcrvUSD == 0) return 10**18;
// uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
// return (totalRcrvUSD * scalingFactor) / totalDeCRVUSD;
return 1e18;
}

Assume:

  • rTokenDecimals = 18

  • deTokenDecimals = 18

  • User deposits 100 rToken (1e20 wei).

  1. deposit(100e18) calls calculateDeCRVUSDAmount(100e18).

  2. calculateDeCRVUSDAmount computes

scalingFactor = 10^(18 + 18 - 18) = 1e18
deCRVUSDAmount = (100e18 * 1e18) / 1e18 = 100e18

User receives 100 deToken

If the exchange rate were dynamic (e.g., rToken.balanceOf(pool) / deToken.totalSupply()), the first depositor would receive 100 deToken (since the pool is empty initially). This works correctly here.

Assume:

  • The pool now holds 100 rToken and 100 deToken (from previous assumption).

  • A second user deposits 100 rToken.

  1. getExchangeRate() still returns 1e18 (hardcoded).

  2. calculateDeCRVUSDAmount(100e18) again returns 100e18 deToken.

  3. User receives 100 deToken.

second depositor should receive:

deCRVUSDAmount = (100e18 * 1e18) / 2e18 = 50e18 deToken

The second depositor incorrectly mints 100 deToken instead of 50 deToken, diluting the value of existing deToken holders.

A malicious actor deposits 1 wei of rToken when the pool is empty. getExchangeRate() returns 1e18.

calculateDeCRVUSDAmount(1) returns:

(1 * 1e18) / 1e18 = 1 deToken
  1. Attacker mints 1 deToken for 1 wei rToken.

  2. The pool now has 1 wei rToken and 1 deToken.

  3. Attacker deposits 1e18 rToken (1 full token)

deCRVUSDAmount = (1e18 * 1e18) / 1e18 = 1e18 deToken

Attacker now holds 1e18 deToken but only added 1e18 rToken to a pool that already had 1 wei rToken

Attacker exploits the hardcoded rate to mint 1e18 deToken for 1e18 rToken, effectively stealing value from the pool.

Impact

The rate is static (1e18), so it doesn’t adjust to reflect the actual rToken/deToken ratio in the pool

Tools Used

Foundry

Recommendations

function getExchangeRate() public view returns (uint256) {
uint256 totalDeCRVUSD = deToken.totalSupply();
if (totalDeCRVUSD == 0) return 1e18;
return (rToken.balanceOf(address(this)) * 1e18) / totalDeCRVUSD;
}
Updates

Lead Judging Commences

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

StabilityPool::getExchangeRate hardcodes 1:1 ratio instead of calculating real rate, enabling unlimited deToken minting against limited reserves

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

StabilityPool::getExchangeRate hardcodes 1:1 ratio instead of calculating real rate, enabling unlimited deToken minting against limited reserves

Support

FAQs

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

Give us feedback!