PuppyRaffle puppyRaffle;
+ SelfdestructTest selfdestructTest;
uint256 entranceFee = 1e18;
address playerOne = address(1);
address playerTwo = address(2);
address playerThree = address(3);
address playerFour = address(4);
address feeAddress = address(99);
uint256 duration = 1 days;
function setUp() public {
puppyRaffle = new PuppyRaffle(
entranceFee,
feeAddress,
duration
);
+ selfdestructTest = new SelfdestructTest(address(puppyRaffle));
}
// rest of code of `PuppyRaffleTest`
+ function testGiftLeadsToLossOfFees() public playersEntered {
+ vm.prank(playerOne);
+ vm.warp(block.timestamp + duration + 1);
+ vm.roll(block.number + 1);
+ puppyRaffle.selectWinner();
+ (bool s, ) = address(selfdestructTest).call{value: 1}("");
+ require(s, "Transfer failed");
+ vm.expectRevert();
+ puppyRaffle.withdrawFees();
+ }
// rest of code of `PuppyRaffleTest`
+ contract SelfdestructTest {
+ PuppyRaffle puppyRaffle;
+ constructor(address _puppyRaffle) {
+ puppyRaffle = PuppyRaffle(_puppyRaffle);
+ }
+ receive() external payable {
+ selfdestruct(payable(address(puppyRaffle)));
+ }
+ }
address[] public players;
uint256 public raffleDuration;
uint256 public raffleStartTime;
address public previousWinner;
+ bool public raffleState;
// rest of the code of `PuppyRaffle`
constructor(uint256 _entranceFee, address _feeAddress, uint256 _raffleDuration) ERC721("Puppy Raffle", "PR") {
entranceFee = _entranceFee;
feeAddress = _feeAddress;
raffleDuration = _raffleDuration;
raffleStartTime = block.timestamp;
+ raffleState = true;
rarityToUri[COMMON_RARITY] = commonImageUri;
rarityToUri[RARE_RARITY] = rareImageUri;
rarityToUri[LEGENDARY_RARITY] = legendaryImageUri;
rarityToName[COMMON_RARITY] = COMMON;
rarityToName[RARE_RARITY] = RARE;
rarityToName[LEGENDARY_RARITY] = LEGENDARY;
}
/// @notice this is how players enter the raffle
/// @notice they have to pay the entrance fee * the number of players
/// @notice duplicate entrants are not allowed
/// @param newPlayers the list of players to enter the raffle
function enterRaffle(address[] memory newPlayers) public payable {
+ require(raffleState, "Raffle not open");
require(msg.value == entranceFee * newPlayers.length, "PuppyRaffle: Must send enough to enter raffle");
for (uint256 i = 0; i < newPlayers.length; i++) {
players.push(newPlayers[i]);
}
// Check for duplicates
for (uint256 i = 0; i < players.length - 1; i++) {
// @audit: potential DoS attack vector
for (uint256 j = i + 1; j < players.length; j++) {
require(players[i] != players[j], "PuppyRaffle: Duplicate player");
}
}
emit RaffleEnter(newPlayers);
}
// rest of the code of `PuppyRaffle`
function selectWinner() external {
+ require(raffleState, "Raffle not open");
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];
uint256 totalAmountCollected = players.length * entranceFee;
uint256 prizePool = (totalAmountCollected * 80) / 100;
uint256 fee = (totalAmountCollected * 20) / 100;
totalFees = totalFees + uint64(fee); // @audit: unsafe cast
//totalFees += 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;
+ raffleState = false;
(bool success,) = winner.call{value: prizePool}("");
require(success, "PuppyRaffle: Failed to send prize pool to winner");
_safeMint(winner, tokenId);
}
- function withdrawFees() external {
+ function withdrawFees() external onlyOwner {
- require(address(this).balance == uint256(totalFees), "PuppyRaffle: There are currently players active!");
+ require(players.length == 0, "PuppyRaffle: There are currently players active!");
+ require(!raffleState, "Raffle still open");
uint256 feesToWithdraw = totalFees;
totalFees = 0;
+ raffleState = true;
(bool success,) = feeAddress.call{value: feesToWithdraw}("");
require(success, "PuppyRaffle: Failed to withdraw fees");
}