MyCut

AI First Flight #8
Beginner FriendlyFoundry
EXP
View results
Submission Details
Severity: high
Valid

= Assignment Overwrites Reward for Duplicate Addresses → Incorrect Distribution and Locked Funds

Description

  • The Pot constructor iterates over the players and rewards arrays and populates playersToRewards mapping with each player's allocated reward. This mapping
    determines how much each address can claim via claimCut().

  • The assignment uses = instead of +=. If the same address appears more than once in i_players, the mapping entry is silently overwritten with the reward from
    the last occurrence — all earlier reward entries for that address are discarded. However, i_players.length still counts every occurrence including duplicates,
    inflating the denominator used in closePot's claimantCut calculation. The result: the affected player receives less than their full allocation, and the
    overwritten rewards are permanently locked in the contract.

constructor(address[] memory players, uint256[] memory rewards, IERC20 token, uint256 totalRewards) {
i_players = players; // @> stores full array including duplicates
i_rewards = rewards;
// ...
remainingRewards = totalRewards; // @> totalRewards counts ALL entries including duplicates

  for (uint256 i = 0; i < i_players.length; i++) {
      // @> = overwrites instead of accumulating — last occurrence wins
      // @> earlier rewards for duplicate addresses are silently lost
      playersToRewards[i_players[i]] = i_rewards[i];
  }

}

Risk

Likelihood:

  • Any contest setup where the same player address is listed more than once — whether through off-chain scripting error, CSV import issue, or intentional
    misconfiguration — triggers the overwrite silently with no on-chain revert

  • No input validation prevents duplicate addresses from entering the array

Impact:

  • The affected player receives only the reward from their last entry, permanently losing all earlier allocations

  • i_players.length is inflated by duplicates, shrinking claimantCut for everyone in closePot and locking the residual in the contract

Proof of Concept

function test_ConstructorOverwritesDuplicateReward() public mintAndApproveTokens {
// player1 appears at index 0 (reward 2) and index 4 (reward 6)
// Expected total for player1: 2 + 6 = 8
address[] memory sixPlayers = [player1, player2, player3, player4, player1, player5];
uint256[] memory sixRewards = [2, 3, 4, 5, 6, 7];
uint256 total = 27; // 2+3+4+5+6+7

  vm.startPrank(user);
  contest = ContestManager(conMan).createContest(
      sixPlayers, sixRewards, IERC20(ERC20Mock(weth)), total
  );
  ContestManager(conMan).fundContest(0);
  vm.stopPrank();

  // player1's mapping was overwritten — only gets reward from last occurrence (index 4 = 6)
  uint256 assigned = Pot(contest).checkCut(player1);
  assertEq(assigned, 6);   // got 6, expected 8 — 2 tokens permanently lost

}

Recommended Mitigation

for (uint256 i = 0; i < i_players.length; i++) {
  • playersToRewards[i_players[i]] = i_rewards[i];
    
  • playersToRewards[i_players[i]] += i_rewards[i];
    

    }

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-03] [M1] `Pot::constructor` Overwrites Rewards for Duplicate Players, Leading to Incorrect Distribution

## Description The `for` loop inside the `Pot::constructor` override the `playersToRewards[i_players[i]]` with new reward `i_rewards[i]`.So if a player's address appears multiple times, the reward is overwritten rather than accumulated. This results in the player receiving only the reward from the last occurrence of their address in the array, ignoring prior rewards. ## Vulnerability Details **Proof of Concept:** 1. Suppose i_players contains \[0x123, 0x456, 0x123] and i_rewards contains \[100, 200, 300]. 2. The playersToRewards mapping will be updated as follows during construction: - For address 0x123 at index 0, reward is set to 300. - For address 0x456 at index 1, reward is set to 200. - For address 0x123 at index 2, reward is updated to 100. 3. As a result, the final reward for address 0x123 in playersToRewards will be 100, not 400 (300+100).This leads to incorrect and lower reward distributions. **Proof of Code (PoC):** place the following in the `TestMyCut.t.sol::TestMyCut` ```Solidity address player3 = makeAddr("player3"); address player4 = makeAddr("player4"); address player5 = makeAddr("player5"); address[] sixPlayersWithDuplicateOneAddress = [player1, player2, player3, player4, player1, player5]; uint256[] rewardForSixPlayers = [2, 3, 4, 5, 6, 7]; uint256 totalRewardForSixPlayers = 27; // 2+3+4+5+6+7 function test_ConstructorFailsInCorrectlyAssigningReward() public mintAndApproveTokens { for (uint256 i = 0; i < sixPlayersWithDuplicateOneAddress.length; i++) { console.log("Player: %s reward: %d", sixPlayersWithDuplicateOneAddress[i], rewardForSixPlayers[i]); } /** * player1 has two occurance in sixPlayersWithDuplicateOneAddress ( at index 0 and 4) * So it's expected reward should be 2+6 = 8 */ vm.startPrank(user); contest = ContestManager(conMan).createContest(sixPlayersWithDuplicateOneAddress, rewardForSixPlayers, IERC20(ERC20Mock(weth)), totalRewardForSixPlayers); ContestManager(conMan).fundContest(0); vm.stopPrank(); uint256 expectedRewardForPlayer1 = rewardForSixPlayers[0] + rewardForSixPlayers[4]; uint256 assignedRewardForPlaye1 = Pot(contest).checkCut(player1); console.log("Expected Reward For Player1: %d", expectedRewardForPlayer1); console.log("Assigned Reward For Player1: %d", assignedRewardForPlaye1); assert(assignedRewardForPlaye1 < expectedRewardForPlayer1); } ``` ## Impact The overall integrity of the reward distribution process is compromised. Players with multiple entries in the i_players\[] array will only receive the reward from their last occurrence in the array, leading to incorrect and lower reward distributions. ## Recommendations **Recommended Mitigation:** Aggregate the rewards for each player inside the constructor to ensure duplicate addresses accumulate rewards instead of overwriting them.This can be achieved by using the += operator in the loop that assigns rewards to players. ```diff for (uint256 i = 0; i < i_players.length; i++) { - playersToRewards[i_players[i]] = i_rewards[i]; + playersToRewards[i_players[i]] += i_rewards[i]; } ```

Support

FAQs

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

Give us feedback!