Core Contracts

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

Reward Deflation During Minting Creates Depositor Loss Risk

Summary

StabilityPool's reward distribution mechanism has a potential vulnerability where rewards can decrease after minting new tokens. This violates the core invariant that user rewards should never decrease after new token minting. The issue stems from the interaction between RAACMinter's excess token tracking and StabilityPool's reward calculation.

The vulnerability arises in the interaction between:

  • StabilityPool's calculateRaacRewards() function which uses total deposits for distribution

  • RAACMinter's tick() function which mints new tokens and tracks excess

  • DEToken's total supply changes affecting reward calculations

This creates a scenario where users could lose rewards they were entitled to, violating the economic assumption that minting new tokens should only increase or maintain reward levels.

Vulnerability Details

StabilityPool acts as a linchpin managing deposits and rewards distribution. However, I've identified insufficiency in how rewards are calculated during minting events.

RAACMinter.sol

function tick() external nonReentrant whenNotPaused {
// 1️ First checkpoint: Check if emission rate needs updating
if (emissionUpdateInterval == 0 || block.timestamp >= lastEmissionUpdateTimestamp + emissionUpdateInterval) {
updateEmissionRate();
}
// 2️ Calculate blocks passed and tokens to mint
uint256 currentBlock = block.number;
uint256 blocksSinceLastUpdate = currentBlock - lastUpdateBlock;
if (blocksSinceLastUpdate > 0) {
uint256 amountToMint = emissionRate * blocksSinceLastUpdate;
if (amountToMint > 0) {
// 3️ Critical state changes:
excessTokens += amountToMint; // Track excess tokens
lastUpdateBlock = currentBlock; // Update block checkpoint
raacToken.mint(address(stabilityPool), amountToMint); // Mint to StabilityPool
emit RAACMinted(amountToMint);
}
}
}

StabilityPool.sol

function calculateRaacRewards(address user) public view returns (uint256) {
// 4️ Reward calculation components:
uint256 userDeposit = userDeposits[user]; // Individual user's deposit
uint256 totalDeposits = deToken.totalSupply(); // Total deposits via DEToken
uint256 totalRewards = raacToken.balanceOf(address(this)); // Current pool rewards
if (totalDeposits < 1e6) return 0;
// 5️ Key vulnerability point:
// Changes in totalDeposits can decrease rewards even as totalRewards increases
return (totalRewards * userDeposit) / totalDeposits;
}

When RAACMinter.tick() is called to mint new tokens, the StabilityPool recalculates rewards based on the current DEToken total supply. Notice how this creates a window where a user's rewards can actually decrease, despite new tokens being minted. This violates a core protocol invariant that rewards should never decrease from minting events.

The exact scenario:

  1. RAACMinter.tick() mints new tokens

  2. This increases totalRewards in StabilityPool

  3. However, if totalDeposits changes (via DEToken supply), the reward calculation ratio can decrease

  4. This creates a scenario where rewards can decrease despite new tokens being minted

This interaction between RAACMinter's emission mechanism and StabilityPool's reward calculation creates the core vulnerability in the protocol's reward distribution system.

This means users could lose entitled rewards during high deposit volatility periods. The root cause lies in how reward calculation interacts with supply changes during minting events.

Impact

When RAACMinter.tick() executes a minting event, it updates the total token supply. The StabilityPool then recalculates rewards using this new total supply value. This means that a user who had 100 RAAC in rewards before minting might end up with only 90 RAAC after, effectively losing value during what should be a positive event for the protocol.

This vulnerability directly impacts the protocol's core value proposition of providing stable, predictable yields for real estate-backed assets.

Recommendations

Implement proper reward checkpointing or moving to a cumulative rate model, ensuring rewards maintain their expected growth properties during minting events.

In StabilityPool.sol

// Add reward tracking state
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
// Update reward tracking on user actions
function updateReward(address account) internal {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
}
// Modified reward calculation
function earned(address account) public view returns (uint256) {
return (userDeposits[account] *
(rewardPerTokenStored - userRewardPerTokenPaid[account]) / 1e18)
+ rewards[account];
}

In RAACMinter.sol

function tick() external nonReentrant whenNotPaused {
// Update rewards before minting
stabilityPool.updateReward(address(0));
// Existing minting logic
if (blocksSinceLastUpdate > 0) {
uint256 amountToMint = emissionRate * blocksSinceLastUpdate;
if (amountToMint > 0) {
excessTokens += amountToMint;
raacToken.mint(address(stabilityPool), amountToMint);
}
}
}
Updates

Lead Judging Commences

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

StabilityPool::calculateRaacRewards is vulnerable to just in time deposits

Support

FAQs

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

Give us feedback!