Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: high
Valid

refund puts address(0) in the players array - resulting in the selectWinner function to potentially revert

Summary

When a player requests a refund by calling the 'PuppyRaffle::refund' function, it puts address(0) in the players array of that players index. This means that address(0) could potentially win the lottery once selectWinner is called. If this address wins it would cause the function to revert with a 'ERC721: mint to the zero address' error.

Vulnerability Details

Because the refund function sets the index of the refunding player to address(0), it can potentially win the raffle when selectWinner is called. If address(0) wins the raffle the function will revert with an error of 'ERC721: mint to the zero address'. This is due to the _saftMint function being called, it comes with built in protections to stop the token from being minted to the zero address.

function refund(uint256 playerIndex) public {
address playerAddress = players[playerIndex];
require(playerAddress == msg.sender, "PuppyRaffle: Only the player can refund");
require(playerAddress != address(0), "PuppyRaffle: Player already refunded, or is not active");
payable(msg.sender).sendValue(entranceFee);
@> players[playerIndex] = address(0);
emit RaffleRefunded(playerAddress);
}
function selectWinner() external {
require(block.timestamp >= raffleStartTime + raffleDuration, "PuppyRaffle: Raffle not over");
require(players.length >= 4, "PuppyRaffle: Need at least 4 players");
uint256 winnerIndex =
uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
@> address winner = players[winnerIndex];
@> _safeMint(winner, tokenId);
}

Impact

This test returns as true, proving that the zero address could be selected as the winner when one or more players have refunded from the raffle.

modifier playersEnteredFive() {
address[] memory players = new address[](5);
players[0] = playerOne;
players[1] = playerTwo;
players[2] = playerThree;
players[3] = playerFour;
players[4] = playerFive;
puppyRaffle.enterRaffle{value: entranceFee * 5}(players);
_;
}
function testAddressZeroCanWin() public playersEnteredFive {
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
uint256 indexOfPlayerThree = puppyRaffle.getActivePlayerIndex(playerThree);
vm.prank(playerThree);
puppyRaffle.refund(indexOfPlayerThree);
vm.expectRevert("ERC721: mint to the zero address");
puppyRaffle.selectWinner();
}

[PASS] testAddressZeroCanWin() (gas: 231293)

Tools Used

-Foundry

Recommendations

If the logic of the refund function cannot change, then it would be suggested in the selectWinner function to check if address(0) has been selected as the winner, if it has, then revert with a custom error to avoid confusion.

+ error PuppyRaffle__AddressZeroCannotWin();
function selectWinner() external {
require(block.timestamp >= raffleStartTime + raffleDuration, "PuppyRaffle: Raffle not over");
require(players.length >= 4, "PuppyRaffle: Need at least 4 players");
uint256 winnerIndex =
uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
address winner = players[winnerIndex];
+ if(winner == address(0)) {
+ revert PuppyRaffle__AddressZeroCannotWin();
+ }
uint256 totalAmountCollected = players.length * entranceFee;
uint256 prizePool = (totalAmountCollected * 80) / 100;
uint256 fee = (totalAmountCollected * 20) / 100;
totalFees = totalFees + uint64(fee);
uint256 tokenId = totalSupply();
// We use a different RNG calculate from the winnerIndex to determine rarity
uint256 rarity = uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100;
if (rarity <= COMMON_RARITY) {
tokenIdToRarity[tokenId] = COMMON_RARITY;
} else if (rarity <= COMMON_RARITY + RARE_RARITY) {
tokenIdToRarity[tokenId] = RARE_RARITY;
} else {
tokenIdToRarity[tokenId] = LEGENDARY_RARITY;
}
delete players;
raffleStartTime = block.timestamp;
previousWinner = winner;
(bool success,) = winner.call{value: prizePool}("");
require(success, "PuppyRaffle: Failed to send prize pool to winner");
_safeMint(winner, tokenId);
}
Updates

Lead Judging Commences

Hamiltonite Lead Judge about 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

refund-doesnt-reduce-players-array-size-causing-protocol-to-freeze

zero address can win the raffle

Funds are locked to no one. If someone gets the refund issue, they also got this issue. IMPACT: High Likelihood: High

Support

FAQs

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

Give us feedback!