MyCut

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

Claims succeed after close due to missing closed/timestamp enforcement

Root + Impact

Description

  • Normal behavior: claims should be restricted to the 90-day window and stop after close.

  • Issue: `claimCut() has no time/closed-state check;closePot()` does not mark the pot as closed.

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);
}
function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
@> // no closed flag is set here
if (remainingRewards > 0) {
...
}
}

Risk

Likelihood:

  • Owner closes after 90 days while unclaimed rewards remain.

  • Unclaimed players submit claims after close.

Impact:

  • Claims execute outside the intended claim window.

  • Distribution finality is broken; manager cut assumes final state but later claims still move funds.

Proof of Concept

function testLateClaimSucceedsAfterClose() public {
address owner = makeAddr("owner");
address p1 = makeAddr("p1");
address p2 = makeAddr("p2");
address p3 = makeAddr("p3");
ContestManager manager = _deployManager(owner);
ERC20Mock token = new ERC20Mock("T", "T", owner, 0);
token.mint(owner, 300);
vm.prank(owner);
token.approve(address(manager), 300);
address[] memory players = new address[](3);
players[0] = p1;
players[1] = p2;
players[2] = p3;
uint256[] memory rewards = new uint256[](3);
rewards[0] = 100;
rewards[1] = 100;
rewards[2] = 100;
vm.startPrank(owner);
address potAddr = manager.createContest(players, rewards, IERC20(token), 300);
manager.fundContest(0);
vm.stopPrank();
vm.prank(p1);
Pot(potAddr).claimCut();
vm.warp(block.timestamp + 91 days);
vm.prank(owner);
manager.closeContest(potAddr);
uint256 beforeBalance = token.balanceOf(p2);
vm.prank(p2);
Pot(potAddr).claimCut();
uint256 afterBalance = token.balanceOf(p2);
assertEq(afterBalance - beforeBalance, 100);
}

Recommended Mitigation

+ bool private closed;
...
function claimCut() public {
+ require(!closed, "Pot closed");
+ require(block.timestamp - i_deployedAt < 90 days, "Claim window ended");
...
}
function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
+ closed = true;
...
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 2 days 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!