Puppy Raffle

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

HIGH-03: Denial of Service via O(n²) Gas Growth in enterRaffle()

HIGH-03: Denial of Service via O(n²) Gas Growth in enterRaffle()

Description

The enterRaffle() function performs a linear scan of the entire players array to check for duplicate entries:

for (uint256 i = 0; i < players.length; i++) {
require(players[i] != msg.sender, "PuppyRaffle: Already entered");
}

As the array grows, gas cost increases quadratically. With 1000+ players, gas exceeds block limit, making enterRaffle() unusable.

// Root cause in the codebase with @> marks to highlight the relevant section
function enterRaffle() public payable {
require(msg.value == entranceFee, "PuppyRaffle: Must send exact entrance fee");
@> for (uint256 i = 0; i < players.length; i++) {
@> require(players[i] != msg.sender, "PuppyRaffle: Already entered");
}
players.push(msg.sender);
}

Risk

Likelihood:

  • Certain to occur as player count grows

  • Attackers can intentionally spam entries to increase gas costs

Impact:

  • enterRaffle() becomes unusable at scale

  • Denial of service for legitimate players

  • Protocol becomes non-functional

Proof of Concept

// Gas cost analysis:
// 100 players: ~50,000 gas for loop
// 500 players: ~250,000 gas for loop
// 1000 players: ~500,000 gas for loop
// 2000 players: ~1,000,000+ gas - exceeds typical block limits

Recommended Mitigation

// Use a mapping for O(1) duplicate check
mapping(address => bool) public hasEntered;
function enterRaffle() public payable {
require(msg.value == entranceFee, "PuppyRaffle: Must send exact entrance fee");
- for (uint256 i = 0; i < players.length; i++) {
- require(players[i] != msg.sender, "PuppyRaffle: Already entered");
- }
+ require(!hasEntered[msg.sender], "PuppyRaffle: Already entered");
+ hasEntered[msg.sender] = true;
players.push(msg.sender);
}
// Also update refund() to clear the mapping
function refund(uint256 playerIndex) public {
// ... existing checks ...
address playerAddress = players[playerIndex];
// ...
players[playerIndex] = address(0);
+ hasEntered[playerAddress] = false;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 7 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!