Puppy Raffle

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

Zero Address can Enter A Raffle

Root + Impact

Description

  • The enterRaffle function iterates through an array of new players but fails to implement a sanity check to ensure the addresses are valid.

  • A user or a flawed frontend could accidentally pass address(0) into the players array.

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]);
}
// Check for duplicates
for (uint256 i = 0; i < players.length - 1; i++) {
for (uint256 j = i + 1; j < players.length; j++) {
require(players[i] != players[j], "PuppyRaffle: Duplicate player");
}
}
emit RaffleEnter(newPlayers);
}

Risk

  • If address(0) is drawn as the winner during selectWinner, the contract will attempt to send the 80% Ether prize pool and mint the NFT to the zero address.

  • This permanently burns the NFT and traps the Ether, making it unrecoverable for the protocol and the users.


Proof of Concept

1. Arrange The test simulates a user (spider) preparing a batch of participants to enter the raffle. An array of 6 addresses is constructed in memory. The first 5 slots are filled with legitimate user addresses (spider, alice, bob, dan, eli), but the final slot at index 5 is explicitly set to the zero address (address(0)).

2. Act The user calls the enterRaffle function and funds the transaction with 6 Ether (1 Ether per player) to cover the entrance fees. After the transaction executes, the test checks the actual Ether balance of the PuppyRaffle contract to see if it processed the entries.

3. Assert The test successfully asserts that the contract's balance is exactly 6 Ether. This mathematically proves that the enterRaffle function did not revert when it encountered address(0). Instead, it blindly accepted the zero address as a valid player, took the entrance fee, and stored address(0) in the active players state array.

function test_If_A_Address_Is_A_ZeroAddress() public {
//Arrange
vm.startPrank(spider);
address[] memory the_players = new address[](6);
the_players[0] = spider;
the_players[1] = alice;
the_players[2] = bob;
the_players[3] = dan;
the_players[4] = eli;
the_players[5] = address(0);
//Act
pRaffle.enterRaffle{value: STARTING_AMOUNT * 6}(the_players);
uint256 balance_of_contract_after_Allowing_a_zeroAddress = address(
pRaffle
).balance;
console2.log(balance_of_contract_after_Allowing_a_zeroAddress);
vm.stopPrank();
//Assert
assertEq(
balance_of_contract_after_Allowing_a_zeroAddress,
6 ether,
"should be 5 ether as spider is duplicate"
);
}
[PASS] test_If_A_Address_Is_A_ZeroAddress() (gas: 190430)
Logs:
6000000000000000000
Traces:
[190430] TestPRaffle::test_If_A_Address_Is_A_ZeroAddress()
├─ [0] VM::startPrank(spider: [0xB279F90f644e63EAca636d78E1d3fcC206632F63])
│ └─ ← [Return]
├─ [155933] PuppyRaffle::enterRaffle{value: 6000000000000000000}([0xB279F90f644e63EAca636d78E1d3fcC206632F63, 0x70E1B74cD0d17f05De348115B5Cd2772812B906F, 0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e, 0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd, 0xEa61F454C2B4A5A16AB556DBE8DBB176C1D02177, 0x0000000000000000000000000000000000000000])
│ ├─ emit RaffleEnter(newPlayers: [0xB279F90f644e63EAca636d78E1d3fcC206632F63, 0x70E1B74cD0d17f05De348115B5Cd2772812B906F, 0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e, 0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd, 0xEa61F454C2B4A5A16AB556DBE8DBB176C1D02177, 0x0000000000000000000000000000000000000000])
│ └─ ← [Stop]
├─ [0] console::log(6000000000000000000 [6e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.20ms (1.13ms CPU time)

Recommended Mitigation

  • To prevent the zero address from entering the raffle, you must implement a strict input validation check inside the enterRaffle function. By adding a require statement within the loop that processes newPlayers, the contract will immediately revert any transaction attempting to register address(0). This ensures that no NFTs or prize funds can ever be permanently burned or trapped.

  • Update the enterRaffle function to include the zero-address check right before pushing the new player to the state array:


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++) {
// Add this explicit zero-address validation check
require(newPlayers[i] != address(0), "PuppyRaffle: Cannot enter zero address");
players.push(newPlayers[i]);
}
// ... (the rest of the duplicate check loop remains exactly the same) ...
emit RaffleEnter(newPlayers);
}
Updates

Lead Judging Commences

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