The protocol specification implies the following lifecycle:
Created
↓
Funded
↓
90-day claim period
↓
Closed
↓
No more claims
However, https://github.com/CodeHawks-Contests/ai-mycut/blob/819134663950999ca5ab29a91eeceddb80274743/src/Pot.sol#L37 has no check preventing claims after the pot has been finalized.
function (https://github.com/CodeHawks-Contests/ai-mycut/blob/819134663950999ca5ab29a91eeceddb80274743/src/Pot.sol#L37) public contains no validation such as potClosed == false
Since redistribution has already occurred, remainingRewards accounting no longer reflects reality, causing tokens to become permanently stranded inside the contract.
Highly likely, as it breaks the intended protocol lifecycle.
reward accounting becomes inconsistent
users receive rewards after redistribution has completed
remaining reward calculations become invalid
tokens become permanently trapped
the contest never truly reaches a final immutable state
function test_claimAfterPotClosed() public {
address contests;
vm.startPrank(owner);
contests = _contestManager.createContest(players, rewards, IERC20(ERC20Mock(weth)), 1000);
_contestManager.fundContest(0);
vm.stopPrank();
uint256 player6Balb4 = weth.balanceOf(player6);
console.log("Player 6 Balance before Claim:", player6Balb4);
vm.startPrank(player6);
Pot(contests).claimCut();
vm.stopPrank();
uint256 player6Bal = weth.balanceOf(player6);
console.log("Player 6 Balance After Claim:", player6Bal);
vm.warp(100 days);
vm.startPrank(owner);
_contestManager.closeContest(contests);
vm.stopPrank();
uint256 player4BalB4 = weth.balanceOf(player4);
console.log("Player 4 Balance Before Claim:", player4BalB4);
uint256 player6BalAF = weth.balanceOf(player6);
console.log("Player 6 Balance After Pot Closed:", player6BalAF);
vm.startPrank(player4);
Pot(contests).claimCut();
vm.stopPrank();
uint256 player4Bal = weth.balanceOf(player4);
console.log("Player 4 Balance After Claim:", player4Bal);
assertGt(player4Bal,player4BalB4);
console::log("Player 4 Balance Before Claim:", 0) [staticcall]
│
├─ [0] console::log("Player 6 Balance After Pot Closed:", 181) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::startPrank(player4: [0x4AB770c39b683c352367fC63C468F18b201d4bd0])
│ └─ ← [Return]
├─ [48123] Pot::claimCut()
│ ├─ [23750] ERC20Mock::transfer(player4: [0x4AB770c39b683c352367fC63C468F18b201d4bd0], 100)
│ │ ├─ emit Transfer(from: Pot: [0x104fBc016F4bb334D775a19E8A6510109AC63E00], to: player4: [0x4AB770c39b683c352367fC63C468F18b201d4bd0], value: 100)
│ │ └─ ← [Return] true
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [938] ERC20Mock::balanceOf(player4: [0x4AB770c39b683c352367fC63C468F18b201d4bd0]) [staticcall]
│ └─ ← [Return] 100
├─ [0] console::log("Player 4 Balance After Claim:", 100)
}
```