Core Contracts

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

Users could receive incorrect amounts of DETokens for their deposits

Summary

StabilityPool's deposit function fails to properly track user deposits when multiple users deposit tokens. The balance tracking mechanism doesn't correctly handle the relationship between user deposits and the total pool balance. This could lead to accounting discrepancies in the stability system.

Looking at the StabilityPool deposit implementation

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update();
rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}

The issue stems from the interaction between:

  1. Token transfers (rToken)

  2. DEToken minting

  3. User deposit tracking

  4. Reward minting

The current implementation assumes a direct 1:1 relationship between deposits and DEToken minting, but the exchange rate calculation in calculateDeCRVUSDAmount can create discrepancies.

Vulnerability Details

StabilityPool contract, a core component of RAAC's real estate liquidity system, contains a significant flaw in its deposit tracking mechanism. The issue emerges at the intersection of user deposits, DEToken minting, and the protocol's reward distribution system.

Notice how the StabilityPool's deposit function attempts to maintain three critical balances:

  1. The actual rToken balance held by the pool

  2. User deposit accounting via userDeposits mapping

  3. DEToken minting that represents user shares

The mistake is in the assumption that these three values will always maintain perfect synchronization. Let's look at the deposit() key interaction:

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update(); // Updates reward state and mints new RAAC rewards via RAACMinter
// Step 1: Accept rTokens from user
rToken.safeTransferFrom(msg.sender, address(this), amount);
// Step 2: Calculate DEToken amount based on current exchange rate
// → This is where the first divergence can happen
// → Exchange rate fluctuates based on pool conditions
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
// Step 3: Mint DETokens to user through DEToken contract
// → User receives deCRVUSDAmount (can be ≠ amount)
deToken.mint(msg.sender, deCRVUSDAmount);
// Step 4: Record user deposit
// → Critical Bug: Records original amount instead of DEToken amount
// → Creates accounting mismatch between deposits and DETokens
userDeposits[msg.sender] += amount; // Should be deCRVUSDAmount
// Step 5: Mint RAAC rewards based on updated state
_mintRAACRewards();
// Step 6: Emit event with both values
// → Notice how we emit both amounts but track only one
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}

This flow involves:

  • StabilityPool.sol: Main deposit logic

  • IStabilityPool.sol: Interface defining deposit behavior

  • DEToken.sol/IDEToken.sol: Minting share tokens

  • RAACMinter.sol/IRAACMinter.sol: Reward minting through _mintRAACRewards()

The vulnerability stems from the misalignment between recorded deposits and actual DEToken minting.

This means that when a user deposits 100 rTokens, they receive DETokens based on calculateDeCRVUSDAmount(), but their userDeposits balance increases by the raw deposit amount. This can lead to a state where:

totalDETokenSupply ≠ sum(userDeposits)

The impact is precise. Users depositing during certain pool conditions could receive more or fewer DETokens than intended, directly affecting their share of stability rewards and their voting power in the protocol's governance system.

this is the trigger

function deposit(uint256 amount) external {
// User deposits 100 rTokens
rToken.safeTransferFrom(msg.sender, address(this), amount);
// But calculateDeCRVUSDAmount() returns 90 due to current pool conditions
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
// Yet we record the full 100 in user deposits
userDeposits[msg.sender] += amount; // <- The Divergence Point
}

Impact

Notice how the contract maintains three distinct balances, physical rToken holdings, DEToken supply, and user deposit records. These should move in perfect harmony, like a well-orchestrated dance. However, there's a critical misstep.

Let's explore what happens when Alice deposits 100 rTokens into the pool:

function deposit(uint256 amount) external {
rToken.safeTransferFrom(msg.sender, address(this), amount); // 100 tokens arrive
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount); // Returns 90 due to current rates
deToken.mint(msg.sender, deCRVUSDAmount); // Mints 90 DETokens
userDeposits[msg.sender] += amount; // Records 100 - The Discrepancy
}

The protocol now thinks Alice has deposited 100 tokens, but she only received 90 DETokens. The implications ripple through the entire system, her voting power in the dual-gauge system is misaligned, her share of stability rewards is incorrect, and the fundamental accounting that underlies RAAC's real estate liquidity mechanism is compromised.

Recommendations

To fix, it requires aligning deposit records with actual DEToken minting, ensuring the protocol's governance and stability mechanisms operate on accurate data. This maintains RAAC's vision of seamless real estate integration with DeFi while preserving the integrity of its economic model.

// StabilityPool.sol - Main Implementation
function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
// 1. Update Protocol State (RAACMinter.sol)
_update(); // Calls RAACMinter.tick() through IRAACMinter interface
// Updates emission rates and mints new RAAC rewards
// 2. Token Transfer (RToken -> StabilityPool)
rToken.safeTransferFrom(msg.sender, address(this), amount);
// 3. Share Calculation (StabilityPool -> DEToken)
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
// Key Interaction Point: Exchange rate affects DEToken minting amount
// 4. Share Token Minting (DEToken.sol)
deToken.mint(msg.sender, deCRVUSDAmount); // Through IDEToken interface
// 5. Deposit Recording - Current Vulnerability Point
userDeposits[msg.sender] += amount;
// Current: Records rToken amount
// Should: Record deCRVUSDAmount to match DEToken shares
// 6. Reward Distribution (RAACMinter.sol)
_mintRAACRewards(); // Distributes RAAC rewards based on recorded deposits
// 7. Event Emission
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}

The Interaction Flow:

  1. StabilityPool receives deposit call (IStabilityPool interface)

  2. RAACMinter updates rewards (`IRAACMinter` interface)

  3. DEToken mints share tokens (IDEToken interface)

  4. StabilityPool records deposit amount

  5. RAACMinter distributes rewards based on recorded deposits

The vulnerability creates a discrepancy between steps 3 and 4, where the recorded deposit amount doesn't match the actual DEToken shares minted.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!