MyCut

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

No `players.length == rewards.length` validation causes silent data loss or revert

Description

The Pot constructor loops over i_players.length to map rewards but never validates that the players and rewards arrays have the same length. A mismatch causes either a revert (wasting gas and blocking contest creation) or silent reward drops that lock tokens permanently.

Vulnerability Details

The constructor iterates over i_players and indexes into i_rewards at each step:

// src/Pot.sol, line 22-34
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;
for (uint256 i = 0; i < i_players.length; i++) {
playersToRewards[i_players[i]] = i_rewards[i]; // @> no bounds check on i_rewards
}
}

Two failure modes exist depending on which array is longer:

Case 1 — rewards.length < players.length: The loop accesses i_rewards[i] at an index beyond the array bounds. Solidity 0.8.20 reverts on out-of-bounds array access. The entire createContest() transaction fails, wasting the admin's gas. The admin must redeploy with corrected arrays, but receives no descriptive error message explaining what went wrong.

Case 2 — rewards.length > players.length: The loop terminates after processing players.length entries. The extra reward values in positions players.length through rewards.length - 1 are stored in i_rewards but never mapped to any address in playersToRewards. If totalRewards was calculated to include these extra amounts, fundContest() transfers more tokens into the Pot than any player can claim. Those excess tokens are permanently locked — the Pot has no sweep or rescue function.

Risk

Likelihood:

  • This requires the admin to pass mismatched array lengths when calling createContest(). The admin is trusted, so this is an accidental misconfiguration rather than an attacker-exploitable vector. But with no validation check, a simple off-by-one error in array construction silently creates a broken contest.

Impact:

  • In the short-rewards case, the contest creation reverts and must be retried. In the long-rewards case, tokens are silently locked in the Pot with no recovery path. The impact scales with the total value of the excess reward entries that were never mapped.

PoC

The test shows that when the admin provides 3 players but 4 reward entries (with the 4th worth 500 tokens), the constructor silently ignores the 4th reward. The Pot is funded with the full totalRewards (which includes the 4th entry), but only 3 players can claim. The 500 extra tokens are permanently locked.

function testExploit_ArrayLengthMismatch() public {
address[] memory players = new address[](3);
players[0] = makeAddr("p1");
players[1] = makeAddr("p2");
players[2] = makeAddr("p3");
// 4 reward entries but only 3 players — extra 500 silently dropped
uint256[] memory rewards = new uint256[](4);
rewards[0] = 1000e18;
rewards[1] = 1000e18;
rewards[2] = 1000e18;
rewards[3] = 500e18; // never mapped to any player
uint256 totalRewards = 3500e18; // includes the orphaned 500
vm.startPrank(admin);
address contest = conMan.createContest(players, rewards, IERC20(token), totalRewards);
conMan.fundContest(0);
vm.stopPrank();
// All 3 players claim successfully
for (uint256 i = 0; i < 3; i++) {
vm.prank(players[i]);
Pot(contest).claimCut();
}
// 500 tokens permanently locked — no player can claim them
assertEq(token.balanceOf(contest), 500e18, "500 tokens locked forever");
}

Recommendations

Add a length check at the start of the constructor to fail fast on mismatched inputs. This gives the admin a clear revert reason instead of silent data loss or a confusing out-of-bounds error.

constructor(address[] memory players, uint256[] memory rewards, IERC20 token, uint256 totalRewards) {
+ if (players.length != rewards.length) {
+ revert("Pot: array length mismatch");
+ }
i_players = players;
i_rewards = rewards;
Updates

Lead Judging Commences

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