MyCut

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

closePot has no closed state and never resets remainingRewards, so it can run again and pay out twice

Root + Impact

Description

  • closePot is meant to run once, after the 90-day period, to pay the manager cut and the claimants' share of the leftover.

  • It keeps no "closed" state and never sets remainingRewards to 0, so it can be called again and again. Each extra call pays the manager cut and the claimant cut a second time, out of whatever balance is still in the Pot, until a transfer reverts.

function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
@> if (remainingRewards > 0) { // still > 0 on the second call
uint256 managerCut = remainingRewards / managerCutPercent;
i_token.transfer(msg.sender, managerCut);
uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
@> } // remainingRewards never reset, no closed flag
}

Risk

Likelihood:

  • Requires the owner to call closeContest more than once for the same Pot (e.g. a retry or script re-run); there is no guard against it.

Impact:

  • Payouts are made twice from the same accounting: claimants and the manager receive more than their share, draining the balance that should back other obligations (e.g. late claims, rescue of leftover funds).

Proof of Concept

function test_F3b_repeatCloseDoublePays() public {
(address[] memory ps, uint256[] memory rs) = _four(); // 4 x 100 ether
Pot pot = _pot(ps, rs, 400 ether);
vm.prank(p1);
pot.claimCut();
vm.warp(block.timestamp + 91 days);
vm.startPrank(owner);
cm.closeContest(address(pot));
cm.closeContest(address(pot)); // second close succeeds
vm.stopPrank();
assertEq(token.balanceOf(p1), 100 ether + 2 * 67.5 ether);
assertEq(token.balanceOf(address(cm)), 2 * 30 ether);
}

Recommended Mitigation

+ bool private s_closed;
+ error Pot__AlreadyClosed();
function closePot() external onlyOwner {
+ if (s_closed) revert Pot__AlreadyClosed();
+ s_closed = true;
...
+ remainingRewards = 0;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 9 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!