Puppy Raffle

AI First Flight #1
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

enterRaffle Accepts Empty Player Array with Zero ETH, Enabling Free Event Spam and State Pollution

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");

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 4 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!