receive() or fallback() function, the call use to send the funds to the winner will fail to execute, compromising the functionality of the protocol.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.
a smart contract address which wins the raffle may not be able to receive their winnings, and will forfeit their win and nft.
If the winner is a smart contract that does not have a fallback or receive function it can not receive eth, therefore the selectWinner function will revert. however on the next call to selectWinner, a new winner will be chosen. this means the player is guaranteed to lose their entry fee, and whoever called selectWinner will have wasted gas.
It is relatively low impact, but can lead to some unexpected situations.
1)If there are very few players in the raffle(2-5), even just 1 of them is a smart contract which does not accept
it can take many calls to successfully select a winner, wasting gas.
2)There is also a scenario, where if a raffle happens to have all players being contracts with no fallback, the contract will be stuck permanently on that specific raffle, unable to ever select a winner.
this situation is possible in a raffle contract that does not get much interaction, it is not the most farfetched thing in the world, for a contract bootstrapping itself to have low amount of users interacting with it and where it may have very few entries in raffles.
3)A malicious actor may also try to heavily populate a raffle with contract addresses with no fallback, in an attempt to DOS the raffle, although it is most likely that there would be no reason to do this for the sake of profit, and may cost a prohibitive amount of eth.
Manual Review
One solution is to accept it as a known limitations and try to document and make as clear as possible that a contract address in the raffle must be able to receive ether.
One potential issue that could be fixed is to remove players who prove to not have callback functions, to ensure that their address will not be selected again as a winner.
this would require a rework from the way the ether is called.
you could have an event like :
event ForfeitedWin(address indexed winner);
and use the low level call which does not revert.
This solution has it's own complexities which need to be dealt with (reentrancy).
And in the selectWinner function instead of sendValue:
(bool success, ) = winner.call{value: address(this).balance}("");
if (!success) {
// Remove the 'winner' from the 'players' array.
players[index] = players[players.length - 1];
players.pop
## 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.