enterRaffle() does not validate that participant addresses are non-zero:
An address(0) entry in the raffle has two problematic properties:
Unrefundable — refund() requires playerAddress == msg.sender, and no one can have msg.sender == address(0). The entrance fee for that slot is permanently locked.
selectWinner() DoS — If address(0) is selected as the winner, _safeMint(address(0), tokenId) reverts because OpenZeppelin's ERC721 _mint has require(to != address(0)).
Additionally, an address(0) entry conflicts with refunded player slots (which are also address(0)), contributing to the duplicate detection issue described in M-004.
Likelihood:
Requires intentional action (passing address(0) in the newPlayers array)
Primarily a griefing vector — attacker pays entrance fee they can never recover
Impact:
Low — entrance fee for address(0) slot is permanently locked (self-griefing)
If address(0) is selected as winner by the RNG, selectWinner() reverts for that particular msg.sender
How the issue manifests:
An attacker (or confused user) calls enterRaffle([address(0)]) with msg.value = entranceFee
address(0) is pushed to the players array — the function succeeds without validation
No one can call refund() for this slot because require(playerAddress == msg.sender) will never pass for address(0)
If selectWinner() picks the address(0) slot as winner, _safeMint(address(0), tokenId) reverts with "ERC721: mint to the zero address"
PoC code:
Expected outcome: address(0) can be entered as a raffle participant. The entrance fee for that slot is permanently locked (no one can refund it), and if the RNG selects that slot as winner, selectWinner() reverts with "ERC721: mint to the zero address".
The root cause is that enterRaffle() does not validate input addresses before pushing them to the players array. address(0) is a special sentinel value used by refund() to mark empty slots, so allowing it as a participant creates confusion between "empty slot" and "zero-address entry" — and triggers downstream failures in both refund() (no one can be msg.sender == address(0)) and selectWinner() (ERC721 rejects minting to zero address).
Primary fix — Validate addresses at entry:
Why this works: The require check prevents address(0) from entering the players array at all, which:
Eliminates the unrefundable-slot problem (no address(0) entries exist to be stuck).
Eliminates the selectWinner() DoS (no address(0) can be selected as winner).
Prevents collision with the address(0) sentinel used by refund(), reducing the interaction surface with M-004 (double-refund duplicate detection).
Additional context: This check alone does not fully resolve M-004 or M-002 — those issues require separate fixes (mapping-based dedup and active-player tracking respectively). However, this validation is a necessary foundational check that should be present regardless of other fixes. Input validation at system boundaries (where user-supplied data enters the contract) is a defense-in-depth best practice.
Gas cost: The require check adds ~200 gas per player entry — negligible compared to the existing storage operations (~20,000 gas per SSTORE).
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.