Puppy Raffle

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

Non-Payable Smart Contract Winners Block Prize Distribution

Description

The selectWinner() function should successfully transfer the prize pool to any valid winner and complete the raffle.

Smart contracts without receive() or fallback() payable functions cannot accept ETH transfers. When such a contract wins, the prize transfer fails and reverts the entire transaction.

function selectWinner() external {
// ... winner selection ...
address winner = players[winnerIndex];
@> (bool success,) = winner.call{value: prizePool}(""); // Fails for non-payable contracts
@> require(success, "PuppyRaffle: Failed to send prize pool to winner");
_safeMint(winner, tokenId);
}

Risk

Likelihood: Low

  • Requires a smart contract without payable functions to be selected as winner

  • Users entering via smart contracts is less common but possible

Impact: High

  • The raffle cannot complete and no winner is selected

  • All participants' funds remain locked until they individually refund

Proof of Concept

  1. A user enters the raffle using a smart contract address (e.g., a multisig without payable functions)

  2. The raffle duration ends

  3. selectWinner() is called and the smart contract is randomly selected

  4. winner.call{value: prizePool}("") fails because the contract cannot receive ETH

  5. The transaction reverts with "Failed to send prize pool to winner"

  6. No winner can be selected; the raffle is stuck

Recommended Mitigation

+ mapping(address => uint256) public pendingWithdrawals;
function selectWinner() external {
// ... winner selection logic ...
- (bool success,) = winner.call{value: prizePool}("");
- require(success, "PuppyRaffle: Failed to send prize pool to winner");
+ pendingWithdrawals[winner] = prizePool;
_safeMint(winner, tokenId);
}
+ function claimPrize() external {
+ uint256 amount = pendingWithdrawals[msg.sender];
+ require(amount > 0, "PuppyRaffle: No prize to claim");
+ pendingWithdrawals[msg.sender] = 0;
+ (bool success,) = msg.sender.call{value: amount}("");
+ require(success, "PuppyRaffle: Transfer failed");
+ }
Updates

Lead Judging Commences

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