selectWinner and stranding the roundSeverity: Medium
refund marks a player's slot as address(0) but leaves it in the players array, so players.length does not shrink.
selectWinner computes the collected amount and prize from the stale players.length, which still counts refunded slots. The resulting prizePool exceeds the ETH actually held, so the prize transfer fails and the whole call reverts. The winner index can also land on a refunded (address(0)) slot.
Likelihood:
Occurs whenever at least one player refunds before the draw: the array keeps the zeroed slot, so totalAmountCollected overstates the balance and the prize transfer reverts on every selectWinner call.
Occurs on the zero-address path as well — when the RNG selects a refunded slot, the winner is address(0).
Impact:
selectWinner reverts indefinitely, so the round can never be settled and the collected ETH is stranded in the contract.
On the zero-address path the prize is sent to address(0) and _safeMint(address(0), ...) reverts.
Save as test/RefundInflatesPoC.t.sol and run forge test --mt testRefundedSlotRevertsSelectWinner. Four players enter (4 ETH), one refunds (balance → 3 ETH, length still 4), and selectWinner computes a 3.2 ETH prize against a 3 ETH balance, reverting.
Base the payout on the number of active players, not players.length. Track an active count (decremented in refund), compact the array on refund, or disallow refunds once the draw is imminent.
Also ensure the winner selection skips address(0) slots (or removes them), so the prize is never sent to the zero address.
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.