Summary
The PuppyRaffle::constructor doesn't check the input parameters and accept also 0 value. The input parameters should always be validated to prevent the creation/initialization of a contract in a wrong/inconsistent state.
Vulnerability Details
@> constructor(uint256 _entranceFee, address _feeAddress, uint256 _raffleDuration) ERC721("Puppy Raffle", "PR") {
entranceFee = _entranceFee;
feeAddress = _feeAddress;
raffleDuration = _raffleDuration;
raffleStartTime = block.timestamp;
...
}
Impact
Case 1: PuppyRaffle::_entranceFee = 0
function testCanEnterRaffle_Free() public {
address[] memory players = new address[](4);
players[0] = playerOne;
players[1] = playerTwo;
players[2] = playerThree;
players[3] = playerFour;
puppyRaffle.enterRaffle{value: entranceFee * 4}(players);
assertEq(puppyRaffle.players(0), playerOne);
assertEq(puppyRaffle.players(1), playerTwo);
assertEq(puppyRaffle.players(2), playerThree);
assertEq(puppyRaffle.players(3), playerFour);
uint256 balanceBefore = address(playerFour).balance;
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
uint256 expectedPayout = ((entranceFee * 4) * 80 / 100);
puppyRaffle.selectWinner();
assertEq(address(playerFour).balance, balanceBefore + expectedPayout);
console.log("The balance of playerFour:", address(playerFour).balance);
}
Case 2: PuppyRaffle::_feeAddress = 0x0000000000000000000000000000000000000000
function testWithdrawFeesUsingZeroAddress() public playersEntered {
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
uint256 expectedFeeAmount = ((entranceFee * 4) * 20) / 100;
puppyRaffle.selectWinner();
puppyRaffle.withdrawFees();
assertEq(address(feeAddress).balance, expectedFeeAmount);
console.log("The fee address is:", address(feeAddress));
console.log("The fee address balance is:", address(feeAddress).balance);
}
Case 3: PuppyRaffle::_raffleDuration = 0
function testCanSelectWinnerImmediatelyWhenDurationIsZero() public {
address[] memory players = new address[](4);
players[0] = playerOne;
players[1] = playerTwo;
players[2] = playerThree;
players[3] = playerFour;
puppyRaffle.enterRaffle{value: entranceFee * 4}(players);
uint256 blockNumberBefore = block.number;
uint256 timestampBefore = block.timestamp;
puppyRaffle.selectWinner();
uint256 blockNumberAfter = block.number;
uint256 timestampAfter = block.timestamp;
assertEq(puppyRaffle.previousWinner(), players[0]);
assertEq(blockNumberBefore, blockNumberAfter);
}
Tools Used
Manual review
Recommendations
Consider implementing the following changes:
constructor(uint256 _entranceFee, address _feeAddress, uint256 _raffleDuration) ERC721("Puppy Raffle", "PR") {
+ require(_entranceFee > 0, "PuppyRaffle: Entrance fee must be greater than 0");
+ require(_feeAddress != address(0), "PuppyRaffle: Fee address cannot be 0");
+ require(_raffleDuration > 0, "PuppyRaffle: Raffle duration must be greater than 0");
entranceFee = _entranceFee;
feeAddress = _feeAddress;
raffleDuration = _raffleDuration;
raffleStartTime = block.timestamp;
...
}