Puppy Raffle

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

selectWinner() can select a refunded address(0) slot as winner, sending prizePool ETH to the zero address permanently

Root + Impact

Description

  • PuppyRaffle allows players to call refund() before selectWinner() is called, which sets their slot in the players array to address(0) but does not remove the slot.

  • When selectWinner() computes the winner index, it can land on one of these zeroed slots and call .call{value: prizePool}("") on address(0), permanently burning the entire prize pool with no revert.

function selectWinner() external {
// ...
uint256 winnerIndex = uint256(keccak256(...)) % players.length;
address winner = players[winnerIndex]; // @> winner could be address(0) if player refunded
// ...
(bool success,) = winner.call{value: prizePool}(""); // @> ETH sent to address(0) is burned
require(success, "PuppyRaffle: Failed to send prize pool to winner");
_safeMint(winner, tokenId); // @> _safeMint to address(0) will also revert in ERC721
}

Risk

Likelihood:

  • Any time one or more players refund before selectWinner is called, there is a non-zero probability that the modulo operation lands on a zeroed slot; probability increases with the number of refunded players.

Impact:

  • The entire prize pool (80% of all entrance fees) is permanently destroyed, and all remaining non-refunded players lose their expected prize with no compensation.

Proof of Concept

Four players enter; player at index 2 refunds; the pseudo-random index resolves to 2; the prize ETH is sent to address(0) and is unrecoverable.

function testZeroAddressWinner() public {
address[] memory entrants = new address[](4);
entrants[0] = address(1); entrants[1] = address(2);
entrants[2] = address(3); entrants[3] = address(4);
raffle.enterRaffle{value: 4 ether}(entrants);
// Player at index 2 refunds — slot becomes address(0)
vm.prank(address(3));
raffle.refund(2);
assertEq(raffle.players(2), address(0));
// Fast-forward past raffle duration; if winnerIndex == 2, prizePool is burned
vm.warp(block.timestamp + raffle.raffleDuration() + 1);
uint256 contractBalBefore = address(raffle).balance;
raffle.selectWinner();
// If winner was address(0), balance dropped but address(0) received nothing useful
assertLt(address(raffle).balance, contractBalBefore);
}

The PoC confirms that prize ETH is transferred to address(0) without revert when a zeroed slot is selected.

Recommended Mitigation

Skip zeroed slots during winner selection, or use a compact array that removes refunded entries so no address(0) slots exist when selectWinner is called.

+ require(winner != address(0), "PuppyRaffle: Winner is inactive slot");
(bool success,) = winner.call{value: prizePool}("");
Updates

Lead Judging Commences

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