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

Potentially overflowing the `totalFees` variable results in lost fees

Summary

The variable totalFees in PuppyRaffle::selectWinner can overflow and the value of the totalFees will be set to zero.

Vulnerability Details

Checks for integer overflow and underflow are the default since the solidity version 0.8.0. But the PuppyRaffle.sol contract uses solidity version ^0.7.6, therefore checks for overflow are required. In Solidity, an integer overflow occurs when the result of an arithmetic operation exceeds the maximum value that can be stored in the integer's bit representation. For instance, if we add 1 to the maximum value of uint256 (which is 2^256 - 1), an integer overflow occurs, and the result wraps around to 0.
In the selectWinner() function, the line totalFees = totalFees + uint64(fee); could potentially cause an overflow if totalFees variable is already at its maximum value or the fee variable has the maximum value of uint64. If an overflow occurs, totalFees would wrap around to 0.

function selectWinner() external {
require(block.timestamp >= raffleStartTime + raffleDuration, "PuppyRaffle: Raffle not over");
require(players.length >= 4, "PuppyRaffle: Need at least 4 players");
uint256 winnerIndex =
uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
address winner = players[winnerIndex];
uint256 totalAmountCollected = players.length * entranceFee;
uint256 prizePool = (totalAmountCollected * 80) / 100;
uint256 fee = (totalAmountCollected * 20) / 100;
@> totalFees = totalFees + uint64(fee);
uint256 tokenId = totalSupply();
// We use a different RNG calculate from the winnerIndex to determine rarity
uint256 rarity = uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100;
if (rarity <= COMMON_RARITY) {
tokenIdToRarity[tokenId] = COMMON_RARITY;
} else if (rarity <= COMMON_RARITY + RARE_RARITY) {
tokenIdToRarity[tokenId] = RARE_RARITY;
} else {
tokenIdToRarity[tokenId] = LEGENDARY_RARITY;
}
delete players;
raffleStartTime = block.timestamp;
previousWinner = winner;
(bool success,) = winner.call{value: prizePool}("");
require(success, "PuppyRaffle: Failed to send prize pool to winner");
_safeMint(winner, tokenId);
}

Impact

If the totalFees variable exceeds the maximum value a uint64 can hold, it could lead to incorrect calculations and lost fees.
Also, an integer overflow could potentially be exploited by an attacker. For example, if an attacker can control the fee that is added to totalFees, they might be able to cause an overflow and manipulate the contract's behavior.
Additionaly, in the withdrawFees() function, the line require(address(this).balance == uint256(totalFees), "PuppyRaffle: There are currently players active!"); checks if the contract's balance equals totalFees. If totalFees overflows and wraps around to 0, this requirement will fail, and the function will revert with the message "PuppyRaffle: There are currently players active!". Because of that the function withdrawFees() will not be able to withdraw any fees.
The following test demonstrates the integer overflow of totalFees in the selectWinner() function. It shows that a uint64 totalFees value exceeds the maximum value a uint64 can hold without any errors, but the result is an integer overflow, where the uint64 value is set to 0. Add the test function testIntegerOverflowInSelectWinner() to the file PuppyRaffleTest.t.sol.

function testIntegerOverflowInSelectWinner() public {
uint256 fee = 2**64 - 1;
uint64 totalFees = 1;
// overflow by addition
totalFees = totalFees + uint64(fee);
// Assert that the add operation results in an overflow
assertTrue(totalFees == 0);
}

Tools Used

VS Code, Foundry

Recommendations

To prevent integer overflows in Solidity, you can use Solidity 0.8 or later. From Solidity 0.8 onwards, the compiler includes built-in overflow and underflow checks. If an overflow or underflow occurs during an arithmetic operation, the operation will fail and the transaction will revert.
Another way to mitigate an overflow is to use SafeMath library that provide safe arithmetic operations that check for overflows and underflows before executing the operation. If an overflow is detected, the function will revert the transaction.
Also, after performing an arithmetic operation, you can check if an overflow occurred and handle it appropriately.
Here's an example of how you might use SafeMath to prevent integer overflows:

import "@openzeppelin/contracts/math/SafeMath.sol";
...
function selectWinner() external {
//....
// Use SafeMath to safely perfom the operation
+ totalFees = totalFees.add(uint64(fee));
- totalFees = totalFees + uint64(fee);
//....
}

This function the add function from the SafeMath library is used to add two numbers. If an overflow occurs, the function will revert the transaction.

Updates

Lead Judging Commences

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

overflow-uint64

Support

FAQs

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