MyCut

AI First Flight #8
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

The Pot constructor never validates `players.length == rewards.length` or `sum(rewards) == totalRewards`, allowing malformed pots that permanently brick claims

Root + Impact

Description

  • When a contest is created, the per-player rewards should correspond one-to-one with players, and their sum should equal totalRewards (the amount the pot is funded with), so that every player can be paid exactly what they are owed.

  • ContestManager.createContest forwards its arrays straight into new Pot(...), and the Pot constructor sets state with no invariant checks. A configuration where sum(rewards) > totalRewards is accepted silently: the pot is funded with only totalRewards, so once cumulative claims exceed it, remainingRewards -= reward underflows (Solidity 0.8 Panic 0x11) and/or the token transfer reverts on insufficient balance, permanently bricking later claimants.

constructor(address[] memory players, uint256[] memory rewards, IERC20 token, uint256 totalRewards) {
i_players = players;
i_rewards = rewards;
i_token = token;
i_totalRewards = totalRewards;
remainingRewards = totalRewards; // trusts totalRewards blindly
i_deployedAt = block.timestamp;
@> for (uint256 i = 0; i < i_players.length; i++) { // no players.length == rewards.length check
@> playersToRewards[i_players[i]] = i_rewards[i]; // and no sum(rewards) == totalRewards check
}
}

Risk

Likelihood:

  • When the owner creates a contest whose per-player rewards do not sum to totalRewards (or whose array lengths differ) — an unvalidated, easily-made setup mistake, since the contract enforces no relationship between the two.

  • When the mismatched pot is sum(rewards) > totalRewards, later claimants encounter the underflow/insolvency on claim.

Impact:

  • A single mis-parameterized createContest call permanently prevents affected players from claiming their promised rewards, or strands funds (sum(rewards) < totalRewards), with no on-chain warning until a claim reverts.

  • There is no recovery path — the affected player's reward is unrecoverable.

Proof of Concept

function testMismatchedRewardsSumBricksLaterClaimant() public {
// rewards = [60, 60] (sum 120) but totalRewards = 100 — accepted with no complaint.
vm.startPrank(user);
contest = ContestManager(conMan).createContest(players, rewards, IERC20(weth), totalRewards);
ContestManager(conMan).fundContest(0);
vm.stopPrank();
assertEq(weth.balanceOf(contest), 100, "pot funded with only totalRewards=100");
// First claimant succeeds: remainingRewards 100 -> 40, pot 100 -> 40.
vm.prank(a);
Pot(contest).claimCut();
// Second claimant is owed 60 but remainingRewards is only 40 -> underflow revert.
vm.expectRevert(); // panic 0x11 (arithmetic underflow)
vm.prank(b);
Pot(contest).claimCut();
}

Recommended Mitigation

Validate the invariants in the Pot constructor, computing the sum in the existing loop:

constructor(address[] memory players, uint256[] memory rewards, IERC20 token, uint256 totalRewards) {
+ require(players.length == rewards.length, "length mismatch");
i_players = players;
i_rewards = rewards;
i_token = token;
i_totalRewards = totalRewards;
remainingRewards = totalRewards;
i_deployedAt = block.timestamp;
+ uint256 sum;
for (uint256 i = 0; i < i_players.length; i++) {
playersToRewards[i_players[i]] = i_rewards[i];
+ sum += i_rewards[i];
}
+ require(sum == totalRewards, "rewards != totalRewards");
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 6 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!