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]);
}
@> 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:
Working Test Case:
function testEnterRaffleGasCost() public {
address[] memory players = new address[](100);
for (uint256 i = 0; i < 100; i++) {
players[i] = address(i + 1);
}
uint256 initialGas = gasleft();
puppyRaffle.enterRaffle{value: entranceFee * 100}(players);
uint256 finalGas = gasleft();
uint256 gasUsed = initialGas - finalGas;
uint256 gasPrice = 1e9;
uint256 gasCost = (gasUsed * gasPrice) / 1e9;
console.log("Gas cost for enterRaffle (First 100 players): ", gasCost, " gwei");
for (uint256 i = 0; i < 100; i++) {
players[i] = address(i + 101);
}
initialGas = gasleft();
puppyRaffle.enterRaffle{value: entranceFee * 100}(players);
finalGas = gasleft();
gasUsed = initialGas - finalGas;
gasCost = (gasUsed * gasPrice) / 1e9;
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);
}