MyCut

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

Players Can Claim Rewards After the 90-Day Window and After the Pot Is Closed

Description

MyCut gives each player a 90-day window to claim their reward ("allowing authorized claimants 90 days to claim before the manager takes a cut of the remaining pool"). The intended flow is: after 90 days, closePot() redistributes the unclaimed remainder to timely claimants and the manager, and the pot is then done.

However, Pot::claimCut() has no time check and no closed-state check:

function claimCut() public {
address player = msg.sender;
uint256 reward = playersToRewards[player];
if (reward <= 0) revert Pot__RewardNotFound();
playersToRewards[player] = 0;
remainingRewards -= reward;
claimants.push(player);
_transferReward(player, reward);
}

(src/Pot.sol:38-46). playersToRewards is only zeroed when the player claims; closePot() does not clear it and does not set any closed flag. Therefore a player who never claimed can call claimCut() at any time - after the 90 days, and even after closePot() has already executed - and still receive their full original reward.

Risk

Impact: Medium - late claims drain the pool that was meant for redistribution to timely claimants and the manager. Players who followed the rules receive less (or nothing) while late players get fully paid; the core time-limit invariant of the protocol is broken.
Likelihood: Medium - any unclaimed player can trigger it; it is trivially executable and requires no privileges beyond holding a playersToRewards entry.

Proof of Concept

Foundry test testPocB_ClaimAfterClose (passes):

function testPocB_ClaimAfterClose() public {
address contest = _createAndFund();
vm.warp(91 days);
vm.prank(user);
conMan.closeContest(contest); // nobody claimed: manager takes 100, 900 left
assertEq(weth.balanceOf(contest), 900);
uint256 p2Before = weth.balanceOf(player2);
vm.prank(player2);
Pot(contest).claimCut(); // should be impossible after closure, but succeeds
assertEq(weth.balanceOf(player2) - p2Before, 500);
}

Test output: [PASS] testPocB_ClaimAfterClose - after closePot() with no claimants, player2 successfully claims 500 tokens post-closure.

Note: the same missing check also allows claims between day 90 and closePot(); the manager can never know the "final" set of claimants when computing the redistribution.

Recommended Mitigation

Enforce the claim window and add a closed flag:

bool private s_closed;
function claimCut() public {
if (block.timestamp - i_deployedAt >= 90 days) revert Pot__ClaimPeriodOver();
if (s_closed) revert Pot__Closed();
address player = msg.sender;
uint256 reward = playersToRewards[player];
if (reward <= 0) revert Pot__RewardNotFound();
...
}
function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) revert Pot__StillOpenForClaim();
...
s_closed = true;
}

Tools Used

  • Manual code review

  • Foundry (forge test) for the PoC

References

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day 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!