MyCut

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

createContest never validates sum(rewards) == totalRewards, so an over-budgeted rewards array makes claimCut hit an arithmetic underflow Panic(0x11) that permanently bricks every later player's claim

Root + Impact

Description

  • Normally, the owner registers rewards[] whose sum must equal totalRewards, and funds exactly totalRewards; each claim then reduces remainingRewards until it reaches zero.

  • No entry validation exists. When sum(rewards) > totalRewards, mid-way through claiming, remainingRewards -= reward underflows. In Solidity ^0.8.20 checked arithmetic this reverts with Panic(uint256(0x11)) — permanently: the mapping entry keeps luring the player (checkCut still shows their reward), and the leftover ledger value also becomes unclaimable.

// src/ContestManager.sol
function createContest(
address[] memory players,
uint256[] memory rewards,
IERC20 token,
uint256 totalRewards
) public onlyOwner returns (address) {
...
@> Pot newPot = new Pot(players, rewards, token, totalRewards); // no sum(rewards) == totalRewards check
// src/Pot.sol
function claimCut() public {
...
playersToRewards[player] = 0;
@> remainingRewards -= reward; // Panic(0x11) when sum(rewards) > totalRewards
...

Risk

Likelihood:

  • One configuration slip when assembling the winners list (a duplicate row, a wrong total) ships silently — creation, funding and early claims all succeed, so the error surfaces only when the budget is exhausted mid-contest.

Impact:

  • Verified: total 4 with rewards [3,3] — player1 claims 3 fine; player2's claim panics forever and their 3 tokens are permanently unclaimable, while the pot keeps the leftover 1 also permanently (p2 cannot claim it, close computes from a broken ledger).

  • The reverse misconfiguration (sum(rewards) 4 is accepted silently because createContest performs no consistency validation — and funds the pot with the (smaller) 4. Step: player1 claims 3 fine, driving remainingRewards to 1. When player2 attempts to claim, the state update remainingRewards -= 3 underflows; Solidity ^0.8.20 checked arithmetic aborts with Panic(0x11), asserted via stdError.arithmeticError. Consequences proven: player2's 3 WETH are permanently unclaimable (the mapping still lures them via checkCut), and the leftover 1 is stranded too. Creation, funding and early claims all succeed, so the misconfiguration ships silently and only detonates when the budget runs out. Run with: forge test --match-test test_POC7_SumMismatch_UnderflowPanic -vv (PASS; asserts the Panic and the stuck 1 WETH).

// forge test --match-test test_POC7_SumMismatch_UnderflowPanic -> PASS
function test_POC7_SumMismatch_UnderflowPanic() public {
address[] memory players = new address[](2); players[0] = p1; players[1] = p2;
uint256[] memory rewards = new uint256[](2); rewards[0] = 3; rewards[1] = 3;
address pot = _createAndFund(players, rewards, 4); // sum=6 > total=4, accepted silently
​
vm.prank(p1);
Pot(pot).claimCut(); // +3, remainingRewards = 1
​
vm.prank(p2);
vm.expectRevert(stdError.arithmeticError); // Panic(0x11)
Pot(pot).claimCut(); // p2's 3 tokens are permanently bricked
​
assertEq(weth.balanceOf(pot), 1); // the leftover 1 is also stuck forever
}

Recommended Mitigation

// src/ContestManager.sol
function createContest(...) public onlyOwner returns (address) {
+ if (players.length == 0) revert ContestManager__EmptyPlayers(); // also fixes V9
+ if (players.length != rewards.length) revert ContestManager__LengthMismatch();
+ uint256 sum;
+ for (uint256 i; i < rewards.length; ++i) {
+ sum += rewards[i];
+ }
+ if (sum != totalRewards) revert ContestManager__RewardsMismatch();
...

⚠ Combination note: this full validation set subsumes V9 (empty players -> sum=0 != totalRewards>0) — ship both together so the close-path division-by-zero is closed at the entrance rather than at the panic site. It does NOT catch V8's duplicate addresses (sum 5+4+1=10 == totalRewards passes).

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 33 minutes 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!