Puppy Raffle

AI First Flight #1
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

totalAmountCollected is not calculated correctly making impossible to select winner in some cases

Vulnerability Details

totalAmountCollected is an important variable, other variables depend on it's value to carry the proper execution of the program. This variable is calculated by measuring the length of the players array. In the scenario that no single players gets refunded, nothing happens the program executes as normal, but if a single player gets refunded, the entranceFee is refunded to the player, and the players position in the Array is set to address(0), but the players array length doesn't change, so when calculating totalAmountCollected it would get an incorrect value, and making impossible to have a winner, and the funds of the players can only be accesed by getting refunded.

  1. The issue lies in the selectWinner function, the winner select calculation does not consider the presence of empty addresses within the array. Consequently, an empty address can be chosen as the winner, causing active players to be unfairly excluded from winning. To resolve this, the function should create a separate array that only includes non-empty addresses for selecting the winner.

    https://github.com/Cyfrin/2023-10-Puppy-Raffle/blob/069cf56ec051216ccab79b550fba5e2a188ade9c/src/PuppyRaffle.sol#L125-132

    Impact

    • Unfair Results: The presence of empty addresses allows the selection of an empty address as the winner, which is unjust for active participants.

    • Loss of Trust: Unfair outcomes undermine trust in the smart contract and the integrity of the raffle system.

    • Manipulation: Malicious actors may exploit this vulnerability by intentionally adding empty addresses to increase their chances of winning or disrupt the fairness of the raffle.

    Tools Used

    Manual Review

    Recommendations

    To address this vulnerability, the selectWinner function should be modified as follows:

    1. Create a new array, named activePlayers, to store the non-empty addresses from the players array.

    2. Iterate over the players array and populate the activePlayers array with the non-empty addresses.

    3. Calculate the winnerIndex using the size of the activePlayers array instead of the players array.

    4. Select the winner from the activePlayers array using the calculated winnerIndex.

function selectWinner() public {
require(block.timestamp >= raffleStartTime + raffleDuration, "PuppyRaffle: Raffle not over");
address[] memory activePlayers = new address[](players.length);
uint activeCount = 0;
// Populate activePlayers array with non-empty addresses
for (uint i = 0; i < players.length; i++) {
if (players[i] != address(0)) {
activePlayers[activeCount] = players[i];
activeCount++;
}
}
// Ensure at least one active player exists
require(activeCount >= 4, "PuppyRaffle: Need at least 4 players");
uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % activeCount;
address winner = activePlayers[winnerIndex];
// Perform additional logic with the winner...
}

Proof of Concept

Copy this test into PuppyRaffleTest.t.sol.
function testCanYouMakeRaffleIfAPlayerRefunds() public playersEntered {
uint256 indexOfPlayer = puppyRaffle.getActivePlayerIndex(playerOne);
vm.prank(playerOne);
puppyRaffle.refund(indexOfPlayer);
vm.warp(puppyRaffle.raffleStartTime() + puppyRaffle.raffleDuration());
puppyRaffleRun the test, it should fail..selectWinner();
}
Run the test, it should fail.

Impact

High - compromising the protocol's functionality

Recommendations

There's two possible solutions to this problem;

  1. 'Sanitize' the players array every time a players get refunded

  2. 'Sanitize' the players array in the selectWinner() function, and check for non Address(0) adresses to be greater or equal to 4.

I recommend for the second solution as it can consume less gas.

I would add some lines of code to selectWinner().

These will have the following execution flow;

Runs a for loop through the players array and counts the number of invalid addresses

  • If the number is bigger than zero

    • create a new players array and populated with the valid addresses from the players array

    • change the players array to this 'cleaned' array.

    • execute selectWinner()

  • If the number is zero

    • execute selectWinner()

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 2 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!