The intended behavior: selectWinner() should always be able to conclude a round, pay the winner, and reset the raffle.
The bug: the prize is pushed to the winner with a low-level call guarded by require(success). If the selected winner is a contract that reverts on receipt (no payable receive/fallback, or a deliberate revert), the call fails and the whole transaction reverts. Because the earlier state changes (delete players, timer reset) roll back with it, the contest gets stuck: the duration has passed and there are 4+ players, but selectWinner() reverts on every call.
Likelihood: Medium. It requires a contract winner that rejects ETH; combined with the weak randomness finding, an attacker can guarantee their reverting contract is the winner.
Impact: Medium. Denial of service on the core draw, locking all participants' funds with no recovery path.
Run forge test --match-test test_M2_reverting_winner_bricks_selectWinner. A contract with a reverting fallback enters as a player; the test grinds a timestamp so that contract is selected, then selectWinner() reverts with "Failed to send prize pool to winner".
Use a pull-payment pattern: record the winner's prize and let them withdraw it, instead of pushing ETH inside selectWinner(). Update state and mint the NFT independently of the prize transfer.
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.