MyCut

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

Pot.claimCut() does not enforce the 90-day claim window (claims succeed after 90 days, even after close)

Root + Impact

Description

  • Normal behavior: Authorized claimants have 90 days to claim before the manager closes and distributes the remainder to those who claimed in time

  • Issue: claimCut() has no deadline/closed check; the 90-day logic only exists in closePot(), so claims can succeed after 90 days (and even after close).

function claimCut() public {
// ⊕ No 90-day deadline check and no "closed" check
uint256 reward = playersToRewards[msg.sender];
if (reward <= 0) revert Pot__RewardNotFound();
playersToRewards[msg.sender] = 0;
remainingRewards -= reward;
claimants.push(msg.sender);
_transferReward(msg.sender, reward);
}
function closePot() external onlyOwner {
// ⊕ 90-day enforcement exists only here
if (block.timestamp - i_deployedAt < 90 days) revert Pot__StillOpenForClaim();
...
}

Risk

Likelihood:

  • Occurs whenever a player claims after 90 days and before the admin closes (or after close).

  • Requires no special conditions beyond time passing.

Impact:

  • Violates the documented “90 days to claim” / “claimed in time” restriction.

  • Late claimants can still extract their reward and affect who qualifies as a “claimant in time” for remainder distribution.

Proof of Concept

forge test --match-contract 'F004' --use ./audit/tooling/solc/solc-0.8.20 --offline -vvv

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "lib/forge-std/src/Test.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {ContestManager} from "../../src/ContestManager.sol";
import {Pot} from "../../src/Pot.sol";
import {ERC20Mock} from "../../test/ERC20Mock.sol";
contract F004ClaimAfterDeadlineBeforeCloseIntegration is Test {
address private admin = makeAddr("admin");
address private player1 = makeAddr("player1");
address private player2 = makeAddr("player2");
function test_F004_integration_playerCanClaimAfter90DaysIfAdminHasNotClosed() public {
address[] memory players = new address[](2);
players[0] = player1;
players[1] = player2;
uint256[] memory rewards = new uint256[](2);
rewards[0] = 3;
rewards[1] = 1;
uint256 totalRewards = 4;
ERC20Mock token = new ERC20Mock("TKN", "TKN", admin, 0);
token.mint(admin, totalRewards);
vm.startPrank(admin);
ContestManager contestManager = new ContestManager();
address contest = contestManager.createContest(players, rewards, IERC20(address(token)), totalRewards);
token.approve(address(contestManager), totalRewards);
contestManager.fundContest(0);
vm.stopPrank();
vm.warp(block.timestamp + 91 days);
vm.prank(player1);
Pot(contest).claimCut();
assertEq(token.balanceOf(player1), rewards[0]);
}
}

Recommended Mitigation

+ bool private closed;
+
function claimCut() public {
+ if (closed) revert Pot__ClaimWindowClosed();
+ if (block.timestamp - i_deployedAt >= 90 days) revert Pot__ClaimWindowClosed();
...
}
function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) revert Pot__StillOpenForClaim();
+ closed = true;
...
}
+
+ error Pot__ClaimWindowClosed();
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!