The MarketCreator contract enables the owner to create markets with a fixed “reward” parameter and allows users to participate by depositing a designated quote asset. When users redeem their positions after a lock period, their reward is calculated as:
reward = (user_deposit × market.reward) / market.totalDeposits
However, because the contract updates the market’s total deposits upon each redemption, the denominator in the reward calculation decreases over time. This dynamic can be manipulated by the redemption order—resulting in a situation where the final redeemer (or later redeemers) can claim a disproportionately higher reward than intended, even exceeding the fixed market.reward value.
Reward Calculation Mechanism:
When a user participates, the market’s totalDeposits increases by their deposit amount.
Upon redemption, the reward is computed as:
Immediately after, the redeemed amount is subtracted from market.totalDeposits.
Impact of Redemption Order:
Suppose two users deposit amounts A and B such that totalDeposits = A + B.
If User A redeems first, they receive: and market.totalDeposits is reduced to B.
Then, when User B redeems, they receive:
Total reward distributed becomes:
This shows that later redeemers can “capture” the full reward (or even more when aggregated across several sequential redemptions), resulting in a total distributed reward that exceeds the market’s fixed reward parameter.
Attack Scenario:
A malicious participant (or a coordinated group) could deliberately time their redemption to be the last (or among the last) to redeem their position.
By doing so, they force early redeemers to receive a reduced reward while claiming nearly the entire fixed reward themselves.
This not only dilutes the rewards for honest participants but also allows an attacker to extract more value than intended, undermining the economic fairness of the market.
In this PoC, two users deposit into a market. User A redeems first, reducing the totalDeposits, and then User B redeems, receiving a reward equal to the full market.reward—even though the fixed reward was meant to be shared proportionately.
Two users each deposit 50 tokens into the same market (total deposits = 100).
User A redeems first and receives 50% of the reward pool (50 RAAC) since the denominator is 100.
After User A’s redemption, total deposits drop to 50.
When User B redeems, they receive the full reward (100 RAAC) because the reward calculation uses the new totalDeposits (50), thereby doubling their share.
As a result, the total reward distributed becomes 150 RAAC—exceeding the market’s fixed reward of 100 RAAC.
Fix:
To prevent this exploit, the reward calculation must use a fixed denominator that does not change with each redemption. One approach is to record the total deposits at market creation (or at a defined “end” of the market) and use that fixed value for reward allocation. For example:
Add a new field (e.g., initialTotalDeposits) in the Market struct that records the total deposits when the market is finalized (or at the first redemption).
Use that fixed value in calculateReward so that every user’s reward is computed proportionally from the same baseline.
Alternatively, you could implement a mechanism that redeems rewards for all participants at once, ensuring that the total distributed reward never exceeds the intended market.reward.
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.