MyCut

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

Incorrect Final Reward Distribution Pays Claimants Instead of Unclaimed Players

Root + Impact

After the 90-day claim period, closePot() distributes the remaining rewards to addresses in the claimants array. However, this array contains players who have already claimed their original rewards. Players who never claimed their allocations are excluded from the final distribution, even though their unclaimed rewards contribute to remainingRewards.

The issue occurs whenever a contest has remaining rewards and at least one player has already claimed. The function's distribution loop explicitly targets the claimants array.

Description

The root cause is that closePot() uses the list of previous claimants to distribute remaining rewards, rather than identifying players with unclaimed allocations.

The claimants array is populated in claimCut() after a player claims their original reward.

Consequently, the final distribution rewards previous claimants instead of distributing the remaining funds according to the intended allocation rules.

uint256 claimantCut =
(remainingRewards - managerCut) / i_players.length;
​
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
claimants.push(player);

Risk

Likelihood: The incorrect recipient selection is deterministic when closePot() executes with remaining rewards and a nonempty claimant list.

Impact: The final distribution can unfairly reward players who already claimed while excluding players with unclaimed allocations.

Proof of Concept

The test assumes the test contract owns the Pot, the token is a compatible ERC20 mock, and the test environment provides Foundry's vm cheatcode.

function testClosePotPaysPreviousClaimants() public {
address alice = address(0xA1);
address bob = address(0xB2);
​
address[] memory players = new address[](2);
players[0] = alice;
players[1] = bob;
​
uint256[] memory rewards = new uint256[](2);
rewards[0] = 100;
rewards[1] = 100;
​
uint256 totalRewards = 300;
​
Pot pot = new Pot(
players,
rewards,
IERC20(address(token)),
totalRewards
);
​
token.mint(address(pot), totalRewards);
​
// Alice claims her original allocation.
vm.prank(alice);
pot.claimCut();
​
uint256 bobBefore = token.balanceOf(bob);
uint256 aliceBefore = token.balanceOf(alice);
​
// Advance beyond the claim period.
vm.warp(block.timestamp + 91 days);
​
// The owner closes the pot.
pot.closePot();
​
// Alice is in `claimants`, so she receives a final payout.
assertGt(token.balanceOf(alice), aliceBefore);
​
// Bob never claimed and is not in `claimants`.
assertEq(token.balanceOf(bob), bobBefore);
}

Recommended Mitigation

Define an explicit policy for unclaimed rewards. If those rewards should be redistributed among players who did not claim, iterate over the original player list and check each player's remaining allocation instead of iterating over claimants. Calculate payouts using the actual eligible recipients, and update the accounting before transferring tokens.

Ensure the manager's cut is accounted for consistently and remainingRewards is updated when funds are distributed. The exact redistribution policy should match the contest's documented rules.

- uint256 claimantCut =
- (remainingRewards - managerCut) / i_players.length;
-
- for (uint256 i = 0; i < claimants.length; i++) {
- _transferReward(claimants[i], claimantCut);
- }
+ // Distribute remaining rewards according to the intended
+ // policy for players who have not claimed.
+ for (uint256 i = 0; i < i_players.length; i++) {
+ address player = i_players[i];
+ uint256 unclaimedReward = playersToRewards[player];
+
+ if (unclaimedReward > 0) {
+ playersToRewards[player] = 0;
+ _transferReward(player, unclaimedReward);
+ }
+ }
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!