The duplicate check in enterRaffle (L96-102) uses nested loops that compare every pair of addresses in the entire players array:
This performs N * (N - 1) / 2 comparisons where N = players.length. Each comparison requires two storage reads (SLOAD: 2,100 gas cold, 100 gas warm). The gas cost grows quadratically with the number of players:
100 players: ~4,950 comparisons (~10-22M gas)
200 players: ~19,900 comparisons (~40-87M gas, exceeds 30M block gas limit)
Once players.length exceeds approximately 100-200 entries, new players cannot enter because the transaction exceeds the block gas limit. Early entrants pay low gas costs while later entrants are priced out or blocked entirely.
Additionally, players.length - 1 at L96 underflows to type(uint256).max if players is empty and newPlayers.length == 0 (Solidity 0.7.6 has no underflow protection), causing an out-of-gas revert.
An attacker enters the raffle with a large batch of unique addresses in a single transaction (e.g., 100 addresses).
Subsequent callers attempting to enter must iterate the duplicate check over 100+ existing entries plus their own.
The gas cost for new entrants exceeds the block gas limit. No further players can join.
The attacker's addresses dominate the player pool, increasing their odds of winning.
Short term: Replace the array-based duplicate check with a mapping(address => bool) that tracks whether an address has entered. This reduces the check from O(n^2) to O(1) per entry:
Long term: Clear the mapping entries when selectWinner resets the raffle, or use a round-based nonce in the mapping key to avoid clearing costs.
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.