MyCut

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

Contract not funded in constructor allows unfunded contest operation

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • The normal behavior should be: when a Pot is created, it immediately receives its full allocation of tokens from the creator.

  • Explain the specific issue or problem in one or more sentences

  • The Pot constructor has the critical funding line commented out, meaning newly created contests are never funded automatically.

// Root cause in the codebase with @> marks to highlight the relevant section
// Pot.sol - Lines 22-35
constructor(address[] memory players, uint256[] memory rewards, IERC20 token, uint256 totalRewards) {
i_players = players;
i_rewards = rewards;
i_token = token;
i_totalRewards = totalRewards;
remainingRewards = totalRewards;
i_deployedAt = block.timestamp;
// @audit CRITICAL: Funding line is commented out!
// i_token.transfer(address(this), i_totalRewards); // Line 29
for (uint256 i = 0; i < i_players.length; i++) {
playersToRewards[i_players[i]] = i_rewards[i];
}
}

Risk

Likelihood:

  • Reason 1

  • This happens in EVERY pot deployment unless the ContestManager's fundContest() is explicitly called AND succeeds.

  • Reason 2

Impact:

  • Impact 1

  • Silent claim failures: Players call claimCut() successfully but receive 0 tokens

  • Broken accounting: remainingRewards decrements but no tokens actually transfer

  • Impact 2

Proof of Concept

// Test: Unfunded pot accepts claims
function testUnfundedPotOperation() public {
// Create pot without funding
address[] memory players = new address[](3);
uint256[] memory rewards = new uint256[](3);
players[0] = alice;
players[1] = bob;
players[2] = charlie;
rewards[0] = 1000;
rewards[1] = 1000;
rewards[2] = 1000;
// Constructor does NOT transfer tokens (line is commented out)
pot = new Pot(players, rewards, token, 3000);
// Verify pot has 0 balance
assertEq(token.balanceOf(address(pot)), 0);
// But contract thinks it has 3000!
assertEq(pot.getRemainingRewards(), 3000);
// Alice tries to claim
vm.prank(alice);
pot.claimCut();
// Claim "succeeds" but Alice receives nothing
assertEq(token.balanceOf(alice), 0); // Alice got 0 tokens
assertEq(pot.checkCut(alice), 0); // But marked as claimed!
assertEq(pot.getRemainingRewards(), 2000); // Accounting broken
// Alice can NEVER claim again
vm.prank(alice);
vm.expectRevert(Pot__RewardNotFound.selector);
pot.claimCut(); // Reverts - reward is 0
// Result: Alice permanently lost 1,000 tokens she was entitled to
}

Recommended Mitigation

- remove this code
+ add this code// Pot.sol
constructor(address[] memory players, uint256[] memory rewards, IERC20 token, uint256 totalRewards) {
i_players = players;
i_rewards = rewards;
i_token = token;
i_totalRewards = totalRewards;
remainingRewards = totalRewards;
i_deployedAt = block.timestamp;
- // i_token.transfer(address(this), i_totalRewards);
+ // Fund the pot immediately (caller must have approved tokens)
+ require(
+ i_token.transferFrom(msg.sender, address(this), i_totalRewards),
+ "Pot funding failed"
+ );
+
+ // Verify funding succeeded
+ require(
+ i_token.balanceOf(address(this)) >= i_totalRewards,
+ "Insufficient pot balance"
+ );
for (uint256 i = 0; i < i_players.length; i++) {
playersToRewards[i_players[i]] = i_rewards[i];
}
}
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!