The MarketCreator
contract distributes rewards to market participants using a formula that calculates each user’s reward as reward = (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.
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:
users who participated in the market don't get any rewards
it also means that the funds that they initially deposited in the Market
will remain stuck in the contract
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.
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
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:
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.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.