The way the refund function deals with players that have solicited a refund breaks the following :
falsely passes the "PuppyRaffle: Need at least 4 players" require in selectWinner()
-duplicate entrants are not allowed invariant
generates the "PuppyRaffle: Failed to send prize pool to winner" revert message in selectWinner()
generates the "ERC721: mint to the zero address" revert message in selectWinner():_safeMint
The root of the problem is this line:
By replacing the address of the registered player with address(0) in the players array it gives the false impression that there are at least 4 participants, when 3 of them could have refunded their entry tickets. Leaving the number of participants = 1.
Paste the following test in PuppyRaffleTest.t.sol:
forge test --mt testRefundBreaksContract -vv
Moreover we can clearly see there are duplicates in the players array.
Add the following line to the test:
Using the same terminal command we are hit by [FAIL. Reason: PuppyRaffle: Failed to send prize pool to winner]
This happens because of the way selectWinner() tracks the prizePool:
Given that the players.length is not changed when a player refunds his ticket, then the totalAmountCollected is not an accurate representation for the balance of the contract (the totalAmountCollected in this case is 1 * entranceFee, but the function calculates it as 4 * entranceFee). Our test fails because the contract doesn't have enough funds to send to the winner.
For testing purposes let's say the contract has some previous balance, consisting of fees that the owner of the protocol didn't withdraw.
Replace the puppyRaffle.selectWinner(); line with the following lines in our test:
We again run it with forge test --mt testRefundBreaksContract -vv
The winner of the raffle was player index 3, which in our case is address(0).
The error originates in the fact that OZ's ERC721 calls _mint inside _safeMint:
duplicate entrants are not allowed is broken.
The raffle runs with less than 4 real players.
In case selectWinner doesn't fail because of the way it tracks totalAmountCollected protocol's owner funds will be used to pay the prize.
Most likely the selectWinner function will fail because you can't mint an NFT to address 0 while using OZ's ERC721 implementation.
Manual review, forge testing
Update the refund function as follows:
Funds are locked to no one. If someone gets the refund issue, they also got this issue. IMPACT: High Likelihood: High
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.