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

Expensive check in enterRaffle

[M-1] Gas expensive duplicate check in PuppyRaffle::enterRaffle causes denial of service attack, causing new players to spend more gas to enter the raffle

Description:

The function loops through the players array to check for duplicates. With the array getting longer more checks should be done. This increases gas cost, so for newcomers at later stage of the round of the raffle, gas cost will be higher.

Impact:

Makes it less profitable for players that enter at later stage

Tools used:
foundry

Proof of Concept:

function testEnterRaffleGasCostInefficient() public {
uint256 playersCount = 100;
address[] memory newPlayers = new address[](playersCount);
// make 3 iterations of adding 100 players
uint iterations = 3;
uint previousIterationUsedGas = 0;
for(uint iter = 0; iter < iterations; iter++) {
for (uint i=0; i < playersCount; i++){
newPlayers[i] = address((iter * playersCount) + i);
}
uint gasStart = gasleft();
puppyRaffle.enterRaffle{value: playersCount * entranceFee}(newPlayers);
uint gasEnd = gasleft();
uint gasUsed = gasStart - gasEnd;
console.log("Gas used for next 100 players", gasUsed);
assert(gasUsed > previousIterationUsedGas);
previousIterationUsedGas = gasUsed;
}
}

Recommended Mitigation:

  1. Consider using duplicates. Users can make new wallet addresses, so a duplicate check doesnt prevent the same person from entering multiple times, only the same address.

  2. Consider using mapping to check for duplicates. You could have mapping for address -> raffleId.

diff --git a/src/PuppyRaffle.sol b/src/PuppyRaffle.sol
index ff05053..b37922b 100644
--- a/src/PuppyRaffle.sol
+++ b/src/PuppyRaffle.sol
@@ -30,6 +30,9 @@ contract PuppyRaffle is ReentrancyGuard, ERC721, Ownable {
address public feeAddress;
uint64 public totalFees = 0;
+ uint256 public raffleId = 1;
+ mapping(address => uint256) addressToRaffleId;
+
// mappings to keep track of token traits
mapping(uint256 => uint256) public tokenIdToRarity;
mapping(uint256 => string) public rarityToUri;
@@ -80,15 +83,10 @@ contract PuppyRaffle is ReentrancyGuard, ERC721, Ownable {
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++) {
+ require(addressToRaffleId[newPlayers[i]] != raffleId, "PuppyRaffle: Duplicate player");
+ addressToRaffleId[newPlayers[i]] = raffleId;
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);
}
@@ -125,6 +123,7 @@ contract PuppyRaffle is ReentrancyGuard, ERC721, Ownable {
function selectWinner() external {
require(block.timestamp >= raffleStartTime + raffleDuration, "PuppyRaffle: Raffle not over");
require(players.length >= 4, "PuppyRaffle: Need at least 4 players");
+ raffleId = raffleId + 1;
uint256 winnerIndex =
uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
address winner = players[winnerIndex];
Updates

Lead Judging Commences

Hamiltonite Lead Judge almost 2 years 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.