MyCut

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

fundContest can be called more than once per contest, overfunding the Pot and breaking the reward accounting

Root + Impact

fundContest is expected to deposit the recorded total rewards into a Pot exactly once, after which the Pot holds exactly what contestToTotalRewards declares. The function has no already-funded check, so a second call moves another full totalRewards into the same Pot while contestToTotalRewards remains unchanged. The Pot token balance diverges from the internal accounting, the surplus is either locked with no refund path or becomes the fuel for the repeated-closePot drain (reported separately).

function fundContest(uint256 index) public onlyOwner {
Pot pot = Pot(contests[index]);
IERC20 token = pot.getToken();
uint256 totalRewards = contestToTotalRewards[address(pot)];
if (token.balanceOf(msg.sender) < totalRewards) {
revert ContestManager__InsufficientFunds();
}
@> token.transferFrom(msg.sender, address(pot), totalRewards); // no guard against repeat calls
}

Risk

Likelihood: High. A second call to fundContest with the same index succeeds unconditionally; retries, automation or owner error all trigger it, and nothing records that a contest was already funded.

Impact: Medium. The Pot holds more tokens than the protocol accounts for, so rewards are either permanently over-locked or, combined with the closePot accounting bug, drained out of the Pot.

Proof of Concept

Verified locally with forge 1.7.1 / solc 0.8.28 (no cheatcodes):

function test_fundContestCanBeCalledRepeatedly() public {
cm.fundContest(0);
cm.fundContest(0);
// Internal accounting still says 60, the Pot holds 120.
assertEq(token.balanceOf(pot), 120);
assertEq(cm.contestToTotalRewards(pot), 60);
}

Recommended Mitigation

+ mapping(address => bool) public funded;
+
function fundContest(uint256 index) public onlyOwner {
Pot pot = Pot(contests[index]);
+ if (funded[address(pot)]) revert ContestManager__AlreadyFunded();
+ funded[address(pot)] = true;
IERC20 token = pot.getToken();
uint256 totalRewards = contestToTotalRewards[address(pot)];
if (token.balanceOf(msg.sender) < totalRewards) {
revert ContestManager__InsufficientFunds();
}
token.transferFrom(msg.sender, address(pot), totalRewards);
}
Updates

Lead Judging Commences

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