MyCut

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

[M-7] `fundContest()` Can Be Called Multiple Times, Overfunding Pot

[M-7] fundContest() Can Be Called Multiple Times, Overfunding Pot

Description

  • Normal behavior: The fundContest() function should only fund each contest once, transferring the exact totalRewards amount to the pot contract.

  • Issue: The function lacks a check to prevent multiple funding calls for the same contest. This allows the owner to repeatedly call fundContest() for the same contest index, transferring additional tokens beyond the intended totalRewards.

function fundContest(uint256 index) public onlyOwner {
// No check for existing balance
token.transferFrom(msg.sender, address(pot), totalRewards);
}

Expected Behavior

  • Each contest can be funded exactly once.

  • After funding, the pot contains exactly totalRewards tokens.

  • Subsequent calls to fundContest() for the same contest should revert.


Actual Behavior

  • fundContest() can be called multiple times for the same contest.

  • Each call transfers totalRewards tokens from owner to pot.

  • Pot becomes overfunded (balance > recorded totalRewards).

  • Extra tokens are permanently locked in the contract.


Risk Assessment

Likelihood — Medium

  • Can occur if owner accidentally calls function twice.

  • Possible in automated scripts or multi-step contest creation processes.

  • No technical barriers prevent multiple calls.

Impact — Medium

  • Financial waste: Owner loses extra tokens sent to pot.

  • Locked funds: Overfunded tokens cannot be recovered.

  • Accounting mismatch: Pot balance ≠ recorded totalRewards.

Severity — Medium (M)

  • Direct financial loss for contest owner.

  • No recovery mechanism for overfunded tokens.

  • Breaks core contract invariant: one funding per contest.


Proof of Concept

This PoC demonstrates the bug in a real deployment scenario:

  1. Normal operation: First fundContest() transfers correct amount.

  2. Bug demonstration: Second fundContest() transfers another full amount, overfunding the pot.

  3. Result: Pot contains double the intended tokens.

function test_fundContest_CanBeCalledMultipleTimes_PoC() public {
// -------------------------------
// Setup: Create contest requiring 1 ETH
// -------------------------------
address[] memory players = new address[](1);
uint256[] memory rewards = new uint256[](1);
players[0] = player1;
rewards[0] = 1 ether;
uint256 totalRewards = 1 ether;
vm.prank(user);
contestManager.createContest(players, rewards, weth, totalRewards);
// Get pot address
address[] memory contests = contestManager.getContests();
address potAddr = contests[0];
// Owner approves ContestManager
vm.prank(user);
weth.approve(address(contestManager), type(uint256).max);
console.log("Initial pot balance:", weth.balanceOf(potAddr));
console.log("Expected final balance: 1 ETH");
// -------------------------------
// First funding: normal operation
// -------------------------------
vm.prank(user);
contestManager.fundContest(0);
uint256 balanceAfterFirst = weth.balanceOf(potAddr);
console.log("Balance after first funding:", balanceAfterFirst);
assertEq(balanceAfterFirst, totalRewards, "First funding correct");
// -------------------------------
// BUG: Second funding - should revert but doesn't!
// -------------------------------
vm.prank(user);
contestManager.fundContest(0); // ⚠️ TRANSFERS ANOTHER 1 ETH!
uint256 balanceAfterSecond = weth.balanceOf(potAddr);
console.log("Balance after second funding:", balanceAfterSecond);
// -------------------------------
// PROOF: Pot is overfunded by 100%
// -------------------------------
assertEq(
balanceAfterSecond,
totalRewards * 2,
"BUG: fundContest() transferred double the intended amount"
);
console.log("CONFIRMED: Pot overfunded by", balanceAfterSecond - totalRewards, "tokens");
}

Expected Output:

Initial pot balance: 0
Expected final balance: 1 ETH
Balance after first funding: 1000000000000000000
Balance after second funding: 2000000000000000000
CONFIRMED: Pot overfunded by 1000000000000000000 tokens

Recommended Mitigation

Add a balance check before transferring tokens:

  1. Prevents waste: Owner cannot accidentally send extra tokens.

  2. Clear feedback: Reverts with descriptive error message.

  3. Maintains invariant: One funding per contest guaranteed.

  4. Backward compatible: Doesn't break existing contest logic.

function fundContest(uint256 index) public onlyOwner {
Pot pot = Pot(contests[index]);
IERC20 token = pot.getToken();
uint256 totalRewards = contestToTotalRewards[address(pot)];
// ADD: Prevent multiple funding
require(
token.balanceOf(address(pot)) == 0,
"Contest already funded"
);
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 1 day 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!