PuppyRaffle::enterRaffleThe PuppyRaffle::enterRaffle function allows users to enter the raffle by providing an array of addresses. To maintain the integrity of the raffle, the contract performs a check to ensure that no duplicate player addresses are added to the players list.
However, this duplicate check is implemented using nested for loops. For every new player being added, the contract iterates through the entire players array. As the number of players increases, the number of operations required for each new entry grows quadratically ().
Specifically, the first player added requires 1 check, the 100th player requires 100 checks, and the 1,000th player requires 1,000 checks. When adding a batch of players, the total number of operations scales exponentially relative to the total number of participants already in the raffle.
Likelihood: High. This is a fundamental architectural flaw. Any successful raffle will naturally accumulate enough players to trigger this condition. It does not require a malicious actor; it will happen through normal usage.
Impact: High. Once the players array reaches a certain size (typically a few hundred entries), the gas cost for the enterRaffle function will exceed the block gas limit (30,000,000 on Ethereum).
Denial of Service (DoS): New users will be unable to join the raffle as their transactions will consistently revert due to "Out of Gas" errors.
Protocol Bricking: If the raffle logic requires a certain number of players to proceed to the winner selection phase, the protocol could be permanently stuck in an unfinishable state.
The vulnerability is caused by the nested loop structure in the duplicate check logic of src/PuppyRaffle.sol.
##Proof of Concept (PoC)
The following Foundry test demonstrates the exponential growth in gas costs. By comparing the gas cost of the first 100 players versus the next 100 players, we can see that the second batch is significantly more expensive, proving the complexity.
##Recommended Mitigation
To resolve this issue, the search must be replaced with an lookup. This can be achieved by using a mapping(address => bool) to track active players.
mapping(address => bool) public isPlayerInRaffle;
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++) {
}
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.