MyCut

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

`Pot` constructor does not validate `players`, `rewards` and `totalRewards`, so a mismatched contest makes claims revert and `closePot` divide by zero

Root + Impact

Description

  • createContest sets up a Pot where each player can claim rewards[i] out of a pool of totalRewards.

  • Nothing links these values. The sum of rewards can exceed totalRewards, the arrays can have different lengths, and players can be empty. A too-large sum makes remainingRewards -= reward underflow, so a player's claim reverts. A shorter rewards array makes the constructor revert. An empty players array makes closePot divide by zero.

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; // unrelated to sum(rewards)
i_deployedAt = block.timestamp;
@> for (uint256 i = 0; i < i_players.length; i++) { // no length check
playersToRewards[i_players[i]] = i_rewards[i];
}
}
function claimCut() public {
...
@> remainingRewards -= reward; // underflow -> revert

Risk

Likelihood:

  • When the admin makes a data-entry mistake in the reward table, for example a typo in totalRewards or an off-by-one in the arrays. The contract accepts it silently, and players only find out when their claim reverts.

Impact:

  • Players whose reward does not fit into remainingRewards can never claim. Their reward stays in the Pot, and whether it gets redistributed depends on closePot.

  • An empty contest cannot be closed (/ i_players.length with 0), so any tokens sent to it are locked.

Proof of Concept

totalRewards = 50 while player1 is owed 65. The first claim reverts with an arithmetic underflow.

function testTotalRewardsDoesntCheck() public mintAndApproveTokens {
vm.startPrank(user);
rewards = [65, 35];
totalRewards = 50; // less than player1's reward
contest = ContestManager(conMan).createContest(players, rewards, IERC20(ERC20Mock(weth)), totalRewards);
ContestManager(conMan).fundContest(0);
vm.stopPrank();
vm.startPrank(player1);
vm.expectRevert(); // panic 0x11
Pot(contest).claimCut();
vm.stopPrank();
}

The test uses the setup of the existing test/TestMyCut.t.sol.

Recommended Mitigation

Check the "totalRewards" value, add error type "Pot__InvalidInput"

+ error Pot__InvalidInput();
constructor(address[] memory players, uint256[] memory rewards, IERC20 token, uint256 totalRewards) {
+ if (players.length == 0 || players.length != rewards.length) revert Pot__InvalidInput();
+ uint256 sum;
+ for (uint256 i = 0; i < rewards.length; i++) sum += rewards[i];
+ if (sum != totalRewards) revert Pot__InvalidInput();
i_players = players;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 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!