Puppy Raffle

AI First Flight #1
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Severity: high
Valid

HIGH-04: Griefing Attack - Attacker Can Block Raffle by Refunding

HIGH-04: Griefing Attack - Attacker Can Block Raffle by Refunding

Description

An attacker can enter the raffle multiple times (using different addresses) and then call refund() on all but one entry. This leaves the players array with many address(0) slots. When selectWinner() is called, there's a high probability of hitting a zero address, burning the prize pool and minting NFT to zero address. The attacker can repeat this to grief the protocol.

// Root cause in the codebase with @> marks to highlight the relevant section
function refund(uint256 playerIndex) public {
// ... checks ...
@> players[playerIndex] = address(0); // Leaves holes in array
}
function selectWinner() external {
// ...
@> uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
@> address winner = players[winnerIndex]; // Can be address(0)
}

Risk

Likelihood:

  • Easy to execute - attacker just needs to enter and refund

  • Low cost to attacker (only gas fees)

Impact:

  • Prize pool burned repeatedly

  • NFTs minted to zero address

  • Protocol becomes unreliable

  • Honest players lose funds

Proof of Concept

// Attacker creates 20 addresses
// Enters raffle with all 20 (pays 20 * entranceFee)
// Refunds 19 entries (gets back 19 * entranceFee)
// Net cost: 1 * entranceFee + gas
// players array now has 1 real address + 19 address(0)
// selectWinner() has 95% chance of picking address(0)
// Prize pool burned, NFT to zero address

Recommended Mitigation

// Option 1: Compact array on refund (swap with last element)
function refund(uint256 playerIndex) public {
// ... existing checks ...
address playerAddress = players[playerIndex];
// ...
// Swap with last element and pop
players[playerIndex] = players[players.length - 1];
players.pop();
// Update hasEntered mapping if using HIGH-03 fix
hasEntered[playerAddress] = false;
}
// Option 2: Track active player count separately
uint256 public activePlayerCount;
function enterRaffle() public payable {
// ...
activePlayerCount++;
players.push(msg.sender);
}
function refund(uint256 playerIndex) public {
// ...
players[playerIndex] = address(0);
activePlayerCount--;
}
function selectWinner() external {
require(activePlayerCount >= 4, "PuppyRaffle: Need at least 4 active players");
// Use activePlayerCount for random selection
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 7 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-01] Potential Loss of Funds During Prize Pool Distribution

## Description In the `selectWinner` function, when a player has refunded and their address is replaced with address(0), the prize money may be sent to address(0), resulting in fund loss. ## Vulnerability Details In the `refund` function if a user wants to refund his money then he will be given his money back and his address in the array will be replaced with `address(0)`. So lets say `Alice` entered in the raffle and later decided to refund her money then her address in the `player` array will be replaced with `address(0)`. And lets consider that her index in the array is `7th` so currently there is `address(0)` at `7th index`, so when `selectWinner` function will be called there isn't any kind of check that this 7th index can't be the winner so if this `7th` index will be declared as winner then all the prize will be sent to him which will actually lost as it will be sent to `address(0)` ## Impact Loss of funds if they are sent to address(0), posing a financial risk. ## Recommendations Implement additional checks in the `selectWinner` function to ensure that prize money is not sent to `address(0)`

Support

FAQs

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

Give us feedback!