Core Contracts

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

[H-4] Wrong rewards computation in `MarketCreator` leads to loss of funds

Summary

The MarketCreator contract distributes rewards to market participants using a formula that calculates each user’s reward as reward = (amount * market.reward) / market.totalDeposits;​.

function calculateReward(uint256 marketId, uint256 amount) internal view returns (uint256) {
Market storage market = markets[marketId];
//@audit flawed logic
return (amount * market.reward) / market.totalDeposits;
}

Since the denominator (market.totalDeposits) decreases after each user claims, the later redeemers receive a larger reward percentage than intended. The problem becomes bigger because at some point the Market contract will not have enough balance to pay out the rewards which means that calls to redeemFromMarket will revert which means that users can not redeem their initial deposits, nor claim any rewards.

Vulnerability Details

MarketCreator::redeemFromMarket function uses safeTransfer. When the contract attempts to send the rewards to the recipient, the transaction will revert because there isn't enough balance. This means that:

  1. users who participated in the market don't get any rewards

  2. it also means that the funds that they initially deposited in the Market will remain stuck in the contract

function redeemFromMarket(uint256 marketId) external nonReentrant {
//..
//..
//@audit this is what the user initially deposited and they should get it back
market.quoteAsset.safeTransfer(msg.sender, amount);
//@audit they won't get their deposit back because the next line of code will revert if there aren't enough reward tokens in the contract anymore
raacToken.safeTransfer(msg.sender, reward);
emit Redeemed(marketId, msg.sender, amount, reward);
}

Root cause

This dynamic recalculation will cause the cumulative rewards paid out to exceed the intended maximum reward pool. This makes the last users unable to claim rewards because the balance of the contract will not be big enough to cover reward amounts.

The reward computation uses the current total deposits as the denominator instead of a fixed snapshot of total deposits at the time of market entry or market creation. As early redeemers reduce the total deposits, the relative share of the remaining participants increases. This approach results in later users receiving disproportionately higher rewards, exceeding the fixed reward pool that is capped at a maximum of 1000 RAAC tokens.

function redeemFromMarket(uint256 marketId) external nonReentrant {
//..
//..
//@audit this decreases after each user claims and inflates the rewards for the next user
market.totalDeposits -= amount;
//..
}

Impact

  • later claimants will get a lot more rewards than intended

  • last users are unable to claim rewards and get their initial deposits back because the balance of the contract will be less than their rewards. The transaction will revert (due to not enough balance when attempting safeTransfer), this prevents users from redeeming their initial deposits which is a direct loss of funds for users

PoC

Consider a market created with:

  • market.totalDeposits = 1,000 tokens (from 10 users each depositing 100 tokens)

  • market.reward = 1,000 RAAC tokens (the max allowed by the protocol).

Reward calculations using the current formula (amount * market.reward) / market.totalDeposits; look like this:

User1 = (100 * 1000) / 1000 = 100;
User2 = (100 * 1000) / 900 = 111.11 (it increases because `market.totalDeposits;` was decreased after User1 claimed)
User3 = (100 * 1000) / 800 = 125
User4 = (100 * 1000) / 700 = 142.85
User5 = (100 * 1000) / 600 = 166.66
User6 = (100 * 1000) / 500 = 200

845 tokens will be claimed by the first 6 users.

From this point onward when these users call the redeemFromMarket function, their transactions will revert because the contract will attempt to do raacToken.safeTransfer(msg.sender, reward); with a value of 250, but the balance of the contract will be only 155. This will always revert, and these users have their initial 100 tokens that they deposited stuck in the contract + no rewards.

User7 = (100 * 1000) / 400 = 250
User8 = (100 * 1000) / 300 = 333.33
User9 = (100 * 1000) / 200 = 500
User10 = (100 * 1000) / 100 = 1000

Recommended Mitigation:

The logic for reward distributions needs to be rethought. Consider adding a fixed snapshot of total deposits at the time of market creation or at each user’s deposit. Then compute each user’s reward based on their share of that fixed total, ensuring that the sum of all rewards does not exceed the allocated reward pool.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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