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

enterRaffle() - Dos Attack

Summary

The PuppyRaffle::enterRaffle() function exhibits gas inefficiency due to its use of a nested loop for duplicate address checks. This inefficiency can create a vulnerability to Denial-of-Service (DoS) attacks. A malicious user can submit numerous personal addresses, causing a significant increase in gas costs for entering the raffle. Consequently, this can block the raffle allowing the attacker to win the prize.

Vulnerability Details

function enterRaffle(address[] memory newPlayers) public payable {
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++) {
@> for (uint256 j = i + 1; j < players.length; j++) {
require(players[i] != players[j], "PuppyRaffle: Duplicate player");
}
}
emit RaffleEnter(newPlayers);
}

Impact

Actors:

  • Attacker: malicious user.

  • Victim: the raffle players.

Working Test Case:

//The test demonstrates the significant increase in gas costs when processing the first 100 players and the second round of 100 players.
function testEnterRaffleGasCost() public {
// Prepare the first 100 players
address[] memory players = new address[](100);
for (uint256 i = 0; i < 100; i++) {
players[i] = address(i + 1); // Assign unique addresses for each player
}
// Get the initial gas left
uint256 initialGas = gasleft();
// Enter the raffle
puppyRaffle.enterRaffle{value: entranceFee * 100}(players);
// Get the final gas left
uint256 finalGas = gasleft();
// Calculate the gas used
uint256 gasUsed = initialGas - finalGas;
// Assume a gas price of 1 gwei for testing purposes
uint256 gasPrice = 1e9; // 1 gwei = 1e9 wei
// Calculate the gas cost
uint256 gasCost = (gasUsed * gasPrice) / 1e9;
// Output the gas cost for debugging purposes
console.log("Gas cost for enterRaffle (First 100 players): ", gasCost, " gwei");
// Prepare the second batch of 100 players
for (uint256 i = 0; i < 100; i++) {
players[i] = address(i + 101); // Assign unique addresses for each player
}
// Get the initial gas left
initialGas = gasleft();
// Enter the raffle
puppyRaffle.enterRaffle{value: entranceFee * 100}(players);
// Get the final gas left
finalGas = gasleft();
// Calculate the gas used
gasUsed = initialGas - finalGas;
// Calculate the gas cost
gasCost = (gasUsed * gasPrice) / 1e9;
// Output the gas cost for debugging purposes
console.log("Gas cost for enterRaffle (Second 100 players): ", gasCost, " gwei");
}

Tools Used

Manual review

Recommendations

Introduce a mapping to keep track of previously entered player addresses, and utilize an if statement to verify the address's status.

+ mapping(address => bool) public s_players;
function enterRaffle(address[] memory newPlayers) public payable {
require(msg.value == entranceFee * newPlayers.length, "PuppyRaffle: Must send enough to enter raffle");
for (uint256 i = 0; i < newPlayers.length; i++) {
+ if (!s_players[newPlayers[i]]) {
players.push(newPlayers[i]);
+ s_players[newPlayers[i]] = true;
+ emit RaffleEnter(newPlayers);
+ } else {
+ revert("PuppyRaffle: Duplicate player");
}
// Check for duplicates
- for (uint256 i = 0; i < players.length - 1; i++) {
- for (uint256 j = i + 1; j < players.length; j++) {
- require(players[i] != players[j], "PuppyRaffle: Duplicate player");
- }
- }
- emit RaffleEnter(newPlayers);
}
Updates

Lead Judging Commences

Hamiltonite Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

denial-of-service-in-enter-raffle

Support

FAQs

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