Puppy Raffle

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

Impossible to win raffle if the winner is a smart contract without a fallback function

Title: Impossible to win raffle if the winner is a smart contract without a fallback function
Impact: Medium — Raffle cannot complete if contract is selected. Funds refundable.
Likelihood: Medium — Attacker can intentionally enter with contract lacking fallback.
Reference Files: src/PuppyRaffle.sol:151-153

Description:

Description

selectWinner() sends ETH to the winner via a low-level call. If the winner is a smart contract without a receive() or fallback() function, the call reverts and the entire selectWinner() transaction fails. The vulnerable code:

(bool success,) = winner.call{value: prizePool}("");
require(success, "PuppyRaffle: Failed to send prize pool to winner");

An attacker can deliberately enter with such a contract to block the raffle.

Risk

Impact: Medium. If a contract without ETH-receiving capability is selected as winner, selectWinner() reverts. All state changes roll back — no funds are lost. Players can still call refund() to recover their ETH.
Likelihood: Medium. An attacker can intentionally enter with a contract lacking a payable fallback to grief the protocol at no profit to themselves.
The attack has zero cost beyond gas — the attacker's entrance fee is refundable if they later call refund().

Proof of Concept

contract NoFallback {}
// Deploy NoFallback, enter raffle with its address
// If NoFallback is selected as winner → call reverts → entire tx reverts
// Attacker can later call refund() on NoFallback's index to recover entrance fee

The NoFallback contract has no receive() or fallback(), so the ETH transfer reverts and blocks the raffle.

Recommended Mitigation

Restrict entrants to externally owned accounts:

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
require(!Address.isContract(newPlayers[i]), "Must be EOA");

This prevents any smart contract from entering the raffle, ensuring the winner can always receive ETH.

Updates

Lead Judging Commences

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

[M-03] Impossible to win raffle if the winner is a smart contract without a fallback function

## 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); } ```

Support

FAQs

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

Give us feedback!