MyCut

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

claimCut() enforces no claim deadline and no post-close guard, so players can still claim after closePot(), breaking the settled distribution

Root + Impact

Description

The protocol's design gives players "90 days to claim before the manager takes a cut." That deadline is never enforced in claimCut():

function claimCut() public {
address player = msg.sender;
uint256 reward = playersToRewards[player];
if (reward <= 0) {
revert Pot__RewardNotFound();
}
@> // no check on block.timestamp / 90-day window, and no check that the Pot is closed
playersToRewards[player] = 0;
remainingRewards -= reward;
claimants.push(player);
_transferReward(player, reward);
}

claimCut() has no time check and no closed-state check, and closePot() never zeroes a player's playersToRewards entry. So a player who missed the 90-day window can still call claimCut() after closePot() has run and settled the distribution. This contradicts the spec and corrupts the post-close accounting: the close already computed manager and claimant cuts assuming the final state, then a late claimer pulls their original reward out of the remaining balance, taking funds the close earmarked for the manager / in-time claimants.

Risk

Likelihood: Medium - any registered player who did not claim before close can do this; it requires no special privilege, only that the Pot still holds enough balance.

Impact: Medium - breaks the settled distribution and lets late claimers extract funds that should have been forfeited, at the expense of the manager cut and in-time claimants (and can revert/DoS other late claims once the balance is drained).

Proof of Concept

No one claims in time; the owner closes the Pot; player1 then still claims 500 after the close. Runnable Foundry test (drop into test/TestMyCut.t.sol):

function test_PoC_claimAfterCloseNoDeadline() public mintAndApproveTokens {
vm.startPrank(user);
rewards = [500, 500];
totalRewards = 1000;
contest = ContestManager(conMan).createContest(players, rewards, IERC20(ERC20Mock(weth)), totalRewards);
ContestManager(conMan).fundContest(0);
vm.stopPrank();
// nobody claims within the window
vm.warp(91 days);
// owner closes the pot - the claim period is supposedly over
vm.prank(user);
ContestManager(conMan).closeContest(contest);
// player1 STILL claims after close: no deadline / closed guard in claimCut()
uint256 beforeBal = ERC20Mock(weth).balanceOf(player1);
vm.prank(player1);
Pot(contest).claimCut();
uint256 afterBal = ERC20Mock(weth).balanceOf(player1);
assertEq(afterBal - beforeBal, 500); // claimed 500 AFTER the contest was closed
}

Run forge test --mt test_PoC_claimAfterCloseNoDeadline -vv; it passes, proving a claim succeeds after the Pot is closed.

Recommended Mitigation

Enforce the claim window in claimCut() and block claims once the Pot is closed:

function claimCut() public {
+ if (block.timestamp - i_deployedAt >= 90 days) {
+ revert Pot__StillOpenForClaim(); // or a dedicated ClaimPeriodOver error
+ }
address player = msg.sender;
uint256 reward = playersToRewards[player];
...
}

Additionally finalize the Pot on close (set a closed flag / zero remainingRewards) so no claims are possible afterward.

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!