MyCut

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

Missing input validation in createContest allows creation of broken contests

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • The normal behavior should be: validate all inputs before deployment to ensure the contest can function correctly.

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

  • The createContest() function does not validate critical input parameters before deploying a Pot contract.

// Root cause in the codebase with @> marks to highlight the relevant section
// ContestManager.sol - Lines 14-26
function createContest(
address[] memory players,
uint256[] memory rewards,
IERC20 token,
uint256 totalRewards
) public onlyOwner returns (address) {
// @audit NO VALIDATION of inputs
// Create a new Pot contract
Pot pot = new Pot(players, rewards, token, totalRewards);
contests.push(address(pot));
contestToTotalRewards[address(pot)] = totalRewards;
return address(pot);
}

Risk

Likelihood:

  • Reason 1

  • Manual data entry mistakes (very common)

  • Frontend bugs

  • CSV import errors

  • Reason 2

Impact:

  • Impact 1

  • Array length mismatch:

  • Impact 2

Proof of Concept

// Test 1: Array length mismatch
function testArrayLengthMismatch() public {
address[] memory players = new address[](5);
uint256[] memory rewards = new uint256[](3);
for (uint i = 0; i < 5; i++) {
players[i] = address(uint160(i + 1));
}
for (uint i = 0; i < 3; i++) {
rewards[i] = 1000;
}
vm.prank(owner);
// Will either revert or create broken contest
address pot = contestManager.createContest(players, rewards, token, 3000);
// Players 4 and 5 have no rewards
assertEq(Pot(pot).checkCut(players[3]), 0);
assertEq(Pot(pot).checkCut(players[4]), 0);
}
// Test 2: Rewards sum mismatch
function testRewardsSumMismatch() public {
address[] memory players = new address[](3);
uint256[] memory rewards = new uint256[](3);
players[0] = alice;
players[1] = bob;
players[2] = charlie;
rewards[0] = 100;
rewards[1] = 200;
rewards[2] = 300;
// Sum = 600
vm.prank(owner);
// Create with totalRewards = 1000 (400 more than needed)
address pot = contestManager.createContest(players, rewards, token, 1000);
token.transfer(pot, 1000);
// All players claim
vm.prank(alice);
Pot(pot).claimCut(); // 100
vm.prank(bob);
Pot(pot).claimCut(); // 200
vm.prank(charlie);
Pot(pot).claimCut(); // 300
// 400 tokens locked forever (1000 - 600)
assertEq(token.balanceOf(pot), 400);
}
// Test 3: Duplicate players
function testDuplicatePlayersLockFunds() public {
address[] memory players = new address[](3);
uint256[] memory rewards = new uint256[](3);
players[0] = alice;
players[1] = bob;
players[2] = alice; // Duplicate!
rewards[0] = 1000;
rewards[1] = 2000;
rewards[2] = 3000;
vm.prank(owner);
address pot = contestManager.createContest(players, rewards, token, 6000);
token.transfer(pot, 6000);
// Alice's first reward (1000) was overwritten by third (3000)
assertEq(Pot(pot).checkCut(alice), 3000); // Not 1000!
// Alice claims 3000
vm.prank(alice);
Pot(pot).claimCut();
// Bob claims 2000
vm.prank(bob);
Pot(pot).claimCut();
// 1000 tokens locked (alice's first reward that got overwritten)
assertEq(token.balanceOf(pot), 1000);
}
// Test 4: Empty arrays
function testEmptyArraysLockAllFunds() public {
address[] memory players = new address[](0);
uint256[] memory rewards = new uint256[](0);
vm.prank(owner);
address pot = contestManager.createContest(players, rewards, token, 10000);
token.transfer(pot, 10000);
// No one can ever claim
// 10,000 tokens locked forever
assertEq(token.balanceOf(pot), 10000);
// Can't even close pot (division by zero - see H-03)
}

Recommended Mitigation

- remove this code
+ add this code// ContestManager.sol
function createContest(
address[] memory players,
uint256[] memory rewards,
IERC20 token,
uint256 totalRewards
) public onlyOwner returns (address) {
+ // Validate arrays are non-empty
+ require(players.length > 0, "No players");
+ require(rewards.length > 0, "No rewards");
+
+ // Validate arrays have matching lengths
+ require(
+ players.length == rewards.length,
+ "Array length mismatch"
+ );
+
+ // Validate token address
+ require(address(token) != address(0), "Invalid token");
+
+ // Validate rewards sum equals totalRewards
+ uint256 rewardsSum = 0;
+ for (uint256 i = 0; i < rewards.length; i++) {
+ require(rewards[i] > 0, "Zero reward not allowed");
+ rewardsSum += rewards[i];
+ }
+ require(
+ rewardsSum == totalRewards,
+ "Rewards sum does not match total"
+ );
+
+ // Check for duplicate players
+ for (uint256 i = 0; i < players.length; i++) {
+ require(players[i] != address(0), "Invalid player address");
+
+ // Check against all previous players
+ for (uint256 j = 0; j < i; j++) {
+ require(
+ players[i] != players[j],
+ "Duplicate player address"
+ );
+ }
+ }
// Create a new Pot contract
Pot pot = new Pot(players, rewards, token, totalRewards);
contests.push(address(pot));
contestToTotalRewards[address(pot)] = totalRewards;
+
+ emit ContestCreated(address(pot), players.length, totalRewards);
return address(pot);
}
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!