Root + Impact
The MyCut protocol allows the owner to create contests where players can claim their allocated rewards.
However, the createContest() function in ContestManager.sol accepts an arbitrary totalRewards parameter without validating it against the actual sum of the rewards[] array. This unvalidated value is then stored and used to fund the Pot contract, creating a critical accounting mismatch.
In the Pot constructor, this mismatch becomes critical:
The contract uses totalRewards for funding and accounting (remainingRewards), but uses the individual rewards[] values for player allocations. When these don't match, the protocol breaks in two ways:
Underfunding (totalRewards < sum(rewards[])): Insufficient tokens in Pot to honor all claims
Overfunding (totalRewards > sum(rewards[])): Excess tokens permanently locked in Pot
Likelihood:
Medium-High - While the owner is trusted, this is an easy configuration mistake to make when:
Manually calculating reward totals
Updating reward values without updating totalRewards
Copy-pasting from previous contest configurations
No automated tooling validates the inputs
Impact:
High - Both scenarios cause severe consequences:
Underfunding Scenario:
Later claimants cannot receive their rewards (transfer fails or insufficient balance)
Protocol appears functional initially but fails when funds depleted
Players lose their rightfully allocated rewards
Reputation damage from failed claims
Overfunding Scenario:
Excess tokens permanently locked in Pot contract
No mechanism to recover excess funds after all claims completed
Wasted capital that could fund other contests
After 90 days, closePot() redistributes based on incorrect remainingRewards value
Scenario 1: Underfunding
Scenario 2: Overfunding with Locked Funds
Scenario 3: Real-World Calculation Error
Option 1: Validate in ContestManager (Recommended)
Add validation before creating the Pot to catch errors early:
Option 2: Calculate in ContestManager (Most Robust)
Remove the totalRewards parameter entirely and calculate it automatically:
Option 3: Validate in Pot Constructor (Defense in Depth)
Add validation at the contract boundary:
Recommended Approach: Implement Option 2 (automatic calculation) in ContestManager for the cleanest API, combined with Option 3 (validation) in Pot constructor for defense in depth. This eliminates the possibility of human error while maintaining contract-level safety checks.
## Description there are two major problems that comes with the way contests are created using the `ContestManager::createContest`. - using dynamic arrays for `players` and `rewards` leads to potential DoS for the `Pot::constructor`, this is possible if the arrays are too large therefore requiring too much gas - it is not safe to trust that `totalRewards` value supplied by the `manager` is accurate and that could lead to some players not being able to `claimCut` ## Vulnerability Details - If the array of `players` is very large, the `Pot::constructor` will revert because of too much `gas` required to run the for loop in the constructor. ```Solidity 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; i_deployedAt = block.timestamp; // i_token.transfer(address(this), i_totalRewards); @> for (uint256 i = 0; i < i_players.length; i++) { @> playersToRewards[i_players[i]] = i_rewards[i]; @> } } ``` - Another issue is that, if a `Pot` is created with a wrong `totalRewards` that for instance is less than the sum of the reward in the `rewards` array, then some players may never get to `claim` their rewards because the `Pot` will be underfunded by the `ContestManager::fundContest` function. ## PoC Here is a test for wrong `totalRewards` ```solidity function testSomePlayersCannotClaimCut() public mintAndApproveTokens { vm.startPrank(user); // manager creates pot with a wrong(smaller) totalRewards value- contest = ContestManager(conMan).createContest(players, rewards, IERC20(ERC20Mock(weth)), 6); ContestManager(conMan).fundContest(0); vm.stopPrank(); vm.startPrank(player1); Pot(contest).claimCut(); vm.stopPrank(); vm.startPrank(player2); // player 2 cannot claim cut because the pot is underfunded due to the wrong totalScore vm.expectRevert(); Pot(contest).claimCut(); vm.stopPrank(); } ``` ## Impact - Pot not created if large dynamic array of players and rewards is used - wrong totlRewards value leads to players inability to claim their cut ## Recommendations review the pot-creation design by, either using merkle tree to store the players and their rewards OR another solution is to use mapping to clearly map players to their reward and a special function to calculate the `totalRewards` each time a player is mapped to her reward. this `totalRewards` will be used later when claiming of rewards starts.
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.