MyCut

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

No zero-address validation on players array allows unclaimable reward slots that lock tokens

Description

The Pot constructor accepts address(0) in the players array without any validation. Rewards mapped to the zero address cannot be claimed because no externally owned account or contract controls that address. Those tokens are permanently locked in the Pot.

Vulnerability Details

The constructor loop assigns rewards to whatever addresses are provided, including address(0):

// src/Pot.sol, line 32-34
for (uint256 i = 0; i < i_players.length; i++) {
playersToRewards[i_players[i]] = i_rewards[i]; // @> no check for address(0)
}

When players[i] = address(0), the mapping sets playersToRewards[address(0)] = i_rewards[i]. Nobody can call claimCut() from the zero address, so those tokens are stuck. The fundContest() call transfers totalRewards tokens (which includes the zero-address allocation) into the Pot, but the zero-address portion can never be claimed.

This also interacts with the wrong-denominator bug (H-01): the zero-address entry counts toward i_players.length, inflating the denominator in the surplus calculation and further reducing what legitimate claimants receive.

Risk

Likelihood:

  • Requires the trusted admin to accidentally include address(0) in the players array. This is a configuration error, not an attacker-exploitable path. But common sources include uninitialized array elements in Solidity (which default to address(0)) or off-chain data errors.

Impact:

  • Tokens allocated to the zero address are permanently locked. The severity depends on the reward amount assigned to the invalid entry. Additionally, the zero-address entry inflates i_players.length, which reduces all claimants' surplus shares via H-01's wrong-denominator calculation.

PoC

The test creates a 3-player contest where one player is address(0) with a 500-token reward. The two legitimate players claim their rewards, but the 500 tokens assigned to address(0) remain permanently locked in the Pot.

function testExploit_ZeroAddressPlayer() public {
address[] memory players = new address[](3);
players[0] = makeAddr("p1");
players[1] = makeAddr("p2");
players[2] = address(0); // zero address — unclaimable
uint256[] memory rewards = new uint256[](3);
rewards[0] = 500e18;
rewards[1] = 500e18;
rewards[2] = 500e18; // this 500 is locked forever
vm.startPrank(admin);
address contest = conMan.createContest(players, rewards, IERC20(token), 1500e18);
conMan.fundContest(0);
vm.stopPrank();
// Both real players claim
vm.prank(players[0]);
Pot(contest).claimCut();
vm.prank(players[1]);
Pot(contest).claimCut();
// 500 tokens locked — nobody controls address(0)
assertEq(Pot(contest).getRemainingRewards(), 500e18, "500 tokens unclaimable");
assertEq(token.balanceOf(contest), 500e18, "Tokens stuck in Pot");
}

Recommendations

Validate each player address in the constructor loop to reject zero addresses at deployment time rather than silently locking tokens.

for (uint256 i = 0; i < i_players.length; i++) {
+ if (i_players[i] == address(0)) {
+ revert("Pot: zero address player");
+ }
playersToRewards[i_players[i]] = i_rewards[i];
}
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!