MyCut

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

closePot() Can Be Executed Multiple Times Due to Missing Closed State

closePot() Can Be Executed Multiple Times Due to Missing Closed State

Description

Normal behavior

A contest should be closed exactly once after the 90-day claim period. Once the remaining rewards have been distributed, the Pot should permanently enter a closed state.

Specific issue

closePot() checks whether 90 days have passed, but it never records that the Pot has already been closed.

As a result, the owner can call closePot() multiple times after the 90-day period.

The function also does not set remainingRewards to zero after distributing the rewards.

This allows the contract's internal accounting to continue reporting rewards as available after they have already been distributed.

function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
​
if (remainingRewards > 0) {
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);
}
}
}
The relevant root cause is:
// @> No `s_closed` state is checked or updated
// @> `remainingRewards` is never set to zero

Risk

Likelihood:

  • The owner can call closePot() any number of times after 90 days.

  • The contract does not maintain any state indicating that closing has already occurred.

  • remainingRewards remains unchanged after the first close.

Impact:

  • Rewards can be distributed repeatedly.

  • Internal accounting becomes inconsistent with the actual token balance.

  • Additional funds sent to the Pot can potentially be consumed by subsequent closePot() calls.

Proof of Concept

function testClosePotCanBeCalledMultipleTimes() public { // Pot contains 1000 tokens. // Assume Alice is a claimant. vm.warp(block.timestamp + 90 days); pot.closePot(); uint256 remainingAfterFirstClose = pot.getRemainingRewards(); // Accounting still reports the original amount. assertEq(remainingAfterFirstClose, 1000); // The Pot can be called again. pot.closePot(); }

Recommended Mitigation

Add an explicit closed state and clear the accounting when closing.
+ bool private s_closed;
​
function closePot() external onlyOwner {
+ if (s_closed) {
+ revert Pot__AlreadyClosed();
+ }
​
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
​
if (remainingRewards > 0) {
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 = 0;
}
​
+ s_closed = true;
}
Updates

Lead Judging Commences

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