createContest / Pot constructor + denied claims, misallocated bonus or a pot that can never closeA contest pays rewards[i] to players[i], and the owner funds totalRewards. For the accounting to hold, five conditions must be met:
the two arrays have equal length;
players are unique and non-zero;
rewards are non-zero;
sum(rewards) == totalRewards;
the player list is not empty.
The repo's own tests use consistent inputs, such as [3,1] for 4 and [500,500] for 1000.
Neither createContest nor the Pot constructor checks any of these conditions. As a result:
remainingRewards is seeded from totalRewards independently of the rewards array.
Duplicate players silently overwrite each other in playersToRewards.
Every array entry, including zero-reward, zero-address and duplicate entries, counts in the i_players.length divisor that closePot uses.
An empty players array makes that divisor zero.
Likelihood:
Every limb happens when the trusted owner passes inconsistent parameters to createContest. Examples are a typo in the rewards array, a duplicated address in a long player list, or a totalRewards that differs from the sum of rewards. No unprivileged actor can trigger it.
Neither contract catches the slip, and the Pot has no setter, so the bad parameters stay for the life of the pot.
Impact:
Sum mismatch:
With [600,600] against 1000, the later claimer's remainingRewards -= reward underflows (Panic 0x11). p2 receives 0 of the 600 promised, even after a second funding.
With [300,300] against 1000, both players claim in full, yet 400 is treated as forfeited. 40 goes to the manager and each player gets a 180 bonus that nobody forfeited.
A rewards array longer than players is silently ignored, and the extra amount becomes the same kind of phantom remainder.
Bad player entries:
With [p1,p1,p2] and [500,200,300], p1's 500 is overwritten by 200 and can never be claimed. At close the divisor is 3 instead of 2, and 150 stays stuck.
One zero-reward entry plus one zero-address entry halve a claimant's bonus (180 becomes 90).
An empty players array on a funded pot makes every closeContest revert with division by zero (Panic 0x12), which locks all 1000 tokens.
The divisor effects overlap with the separate i_players.length divisor issue. This report is about the missing validation.
The test deploys ContestManager and the repo's test/ERC20Mock.sol and goes through createContest, fundContest and closeContest. All eight tests pass:
test_baseline_consistentInputWorks (control): consistent input pays everyone and leaves the Pot empty.
test_limb1_sumAboveTotal_laterClaimerUnderflows: p2's claim reverts with stdError.arithmeticError, before and after a second funding. p2 ends with 0 while checkCut(p2) is 600.
test_limb2_sumBelowTotal_phantomRemainder: 400 is reported unclaimed although both players claimed. The manager takes 40 and each player gets 180 extra.
test_limb3_duplicatePlayers_overwriteAndDivisorInflation: checkCut(p1) is 200 and p1's second claim reverts with Pot__RewardNotFound. After close p1 has 350, p2 has 450, and 150 is stuck.
test_limb4_zeroEntriesShrinkBonus: the bonus is 180 with a clean list and 90 with the dead entries.
test_limb5_emptyPlayers_closeDivByZero_fundsLocked: closeContest reverts with stdError.divisionError at day 90 and again ten years later, and 1000 stays locked.
test_limb6a_shorterRewardsRevertsAtDeploy: deployment reverts with stdError.indexOOBError, which is the benign case.
test_limb6b_longerRewardsSilentlyIgnored: the extra 500 entry is ignored and paid out as a phantom remainder (manager 50, p1 950).
Save the file as test/PoC_MC-06.t.sol and run from the repository root:
Validate the parameters once, in the Pot constructor, so every deployment path is covered. The duplicate check reuses playersToRewards, which keeps the cost O(n).
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.