The intended behavior is that the selected winner receives the prize pool and the puppy NFT, concluding the raffle.
The specific issue is that selectWinner pays the prize with a low-level call wrapped in require(success). If the winner is a smart contract that has no payable receive/fallback function, the ETH transfer fails, so selectWinner reverts and the raffle can never be concluded.
The winner is chosen from players[], which anyone can enter, including a contract address with no payable fallback. Once such an address is selected, the prize transfer fails and selectWinner reverts; because the winner is fixed by the RNG for that block, the raffle is stuck and the collected funds are locked.
Likelihood: Medium
Any participant can enter as a contract that cannot receive ETH; nothing restricts entrants to EOAs.
Impact: High
When such an address wins, selectWinner permanently reverts: no winner is paid, no NFT minted, and all entrants' funds remain locked. A griefer can guarantee this by entering such a contract.
Use a pull-payment pattern: record the winner's prize and let them withdraw it in a separate call, so a failing transfer to one winner cannot block the draw.
## Description If a player submits a smart contract as a player, and if it doesn't implement the `receive()` or `fallback()` function, the call use to send the funds to the winner will fail to execute, compromising the functionality of the protocol. ## Vulnerability Details The vulnerability comes from the way that are programmed smart contracts, if the smart contract doesn't implement a `receive() payable` or `fallback() payable` functions, it is not possible to send ether to the program. ## Impact High - Medium: The protocol won't be able to select a winner but players will be able to withdraw funds with the `refund()` function ## Recommendations Restrict access to the raffle to only EOAs (Externally Owned Accounts), by checking if the passed address in enterRaffle is a smart contract, if it is we revert the transaction. We can easily implement this check into the function because of the Adress library from OppenZeppelin. I'll add this replace `enterRaffle()` with these lines of code: ```solidity 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++) { require(Address.isContract(newPlayers[i]) == false, "The players need to be EOAs"); 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); } ```
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.