The intended behavior: the prize and fee should be computed from the ETH that participants actually contributed.
The bug: refund() zeroes a player's slot but leaves the array length unchanged, so refunded entries (now address(0)) are still counted. selectWinner() computes the pot from players.length, so after any refund the prize and fee are overstated relative to the real balance.
Likelihood: Medium. Any refund before the draw triggers the miscount; refunds are a normal, intended feature.
Impact: Medium. The winner is overpaid relative to real contributions, draining the contract and leaving totalFees recording fees that are not backed by ETH, so withdrawFees() reverts. (Separately, if the random index lands on a refunded address(0) slot, _safeMint(address(0)) reverts and freezes the draw.)
Run forge test --match-test test_M4_refund_inflates_prize_and_breaks_accounting. Five players enter (5 ETH); one refunds, leaving 4 ETH. The pot is still computed as 5 ETH, so the winner receives 4 ETH (the entire balance), the contract is drained to 0, yet totalFees records 1 ETH of phantom fees, and withdrawFees() then reverts.
Track an active-player count (or compact the array on refund) and base the prize/fee math on the funds actually held rather than on players.length.
Decrement activePlayers inside refund() so the pot always reflects real contributions, and skip address(0) slots when selecting the winner.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.