enterRaffle Accepts Empty Player Array with Zero ETH, Enabling Free Event Spam and State Pollution
Severity: Medium
Description
enterRaffle validates payment as msg.value == entranceFee * newPlayers.length. When newPlayers.length is 0, the required payment is 0 * entranceFee = 0, so
any caller can invoke the function with an empty array and no ETH — passing all checks.
Every such call emits a RaffleEnter event and runs the full duplicate-check loop over existing players, wasting gas for legitimate participants and polluting
off-chain event indexers.
function enterRaffle(address[] memory newPlayers) public payable {
@> require(msg.value == entranceFee * newPlayers.length,
"PuppyRaffle: Must send enough to enter raffle");
for (uint256 i = 0; i < newPlayers.length; i++) {
players.push(newPlayers[i]);
}
@> emit RaffleEnter(newPlayers); // fires even for empty array
}
Risk
Likelihood:
Trivially exploitable — no ETH required, callable by any EOA or contract in a loop
Becomes more attractive as players.length grows because the duplicate-check loop is O(n) even for an empty input batch
Impact:
Attacker can spam thousands of zero-cost transactions, bloating event logs and degrading front-end and indexer performance
Each call burns gas for all existing players via the duplicate-check loop, compounding the O(n^2) DoS already present in enterRaffle
No funds are at direct risk, but raffle integrity and usability are degraded
Proof of Concept
The test below shows an empty-array call succeeds with zero ETH after a normal entry already exists.
function test_M3_EmptyArrayEnterFree() public {
address[] memory one = new address;
one[0] = makeAddr("p1");
vm.deal(address(this), 1 ether);
raffle.enterRaffle{value: 1 ether}(one);
// Empty array: 0 ETH required, passes fee check, emits event
address[] memory empty = new address;
raffle.enterRaffle{value: 0}(empty);
}
Recommended Mitigation
Add an explicit minimum-length guard at the top of enterRaffle so zero-player calls are rejected before any state or event processing occurs.
function enterRaffle(address[] memory newPlayers) public payable {
require(newPlayers.length > 0,
"PuppyRaffle: Must enter at least one player");
require(msg.value == entranceFee * newPlayers.length,
"PuppyRaffle: Must send enough to enter raffle");
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.