Puppy Raffle

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

## [H-4] `selectWinner()` Can Select Refunded Player (`address(0)`) as Winner

Root + Impact

Description

  • The selectWinner() function is intended to select an active player as the winner

  • When players refund, their slot in the players array is set to address(0) but the array length remains the same

  • The winner selection does not skip address(0) entries, so a refunded slot can be selected as the winner

@> uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
@> address winner = players[winnerIndex]; // Can be address(0) if player refunded
// ...
(bool success,) = winner.call{value: prizePool}(""); // Sends ETH to address(0)
require(success, "PuppyRaffle: Failed to send prize pool to winner");
_safeMint(winner, tokenId); // Mints NFT to address(0)

Risk

Likelihood:

  • Occurs whenever players refund and the random selection lands on their index

  • More likely as more players refund from the raffle

Impact:

  • Prize pool ETH is permanently burned (sent to address(0))

  • NFT is minted to address(0) and permanently lost

  • Legitimate players lose their chance to win

Proof of Concept

function test_SelectZeroAddressWinner() public {
// 4 players enter
address[] memory players = new address[](4);
players[0] = address(1);
players[1] = address(2);
players[2] = address(3);
players[3] = address(4);
puppyRaffle.enterRaffle{value: entranceFee * 4}(players);
// Player at index 0 refunds
vm.prank(address(1));
puppyRaffle.refund(0);
// Now players[0] == address(0)
// If winnerIndex == 0, address(0) becomes winner
vm.warp(block.timestamp + duration + 1);
// Manipulate conditions so winnerIndex = 0
puppyRaffle.selectWinner();
// Prize sent to address(0), NFT minted to address(0)
}

Recommended Mitigation

function selectWinner() external {
require(block.timestamp >= raffleStartTime + raffleDuration, "PuppyRaffle: Raffle not over");
require(players.length >= 4, "PuppyRaffle: Need at least 4 players");
+ // Count active players and ensure minimum
+ uint256 activeCount = 0;
+ for (uint256 i = 0; i < players.length; i++) {
+ if (players[i] != address(0)) activeCount++;
+ }
+ require(activeCount >= 4, "PuppyRaffle: Need at least 4 active players");
uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
address winner = players[winnerIndex];
+ require(winner != address(0), "PuppyRaffle: Winner cannot be zero address");
// ... rest of function
}
Updates

Lead Judging Commences

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