Puppy Raffle

AI First Flight #1
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

`selectWinner()` and `withdrawFees()` Lack Access Control — Anyone Can Trigger Premature Round Termination

selectWinner() and withdrawFees() Lack Access Control — Anyone Can Trigger Premature Round Termination

Description

PuppyRaffle.selectWinner() and PuppyRaffle.withdrawFees() are both declared external without any access modifier (onlyOwner, modifier, require(msg.sender == ...)) to restrict who may call them. While selectWinner() has a time-based check (block.timestamp >= raffleStartTime + raffleDuration) and withdrawFees() has a balance check, neither restricts who can act as the caller.

The lack of access control on selectWinner() allows any EOA or contract to forcibly end a raffle the moment the time condition is satisfied, regardless of whether the protocol operator intended to continue the round. An attacker with knowledge of the raffle's timing can strategically end rounds at moments that maximize their probability of winning (e.g., having just entered as the dominant player). withdrawFees() similarly allows anyone to initiate fee extraction, potentially in combination with griefing strategies.

// @> No access control — any address can terminate an active raffle round
function selectWinner() external {
require(block.timestamp >= raffleStartTime + raffleDuration, "...not over yet");
require(players.length >= 4, "...");
// ...
}
// @> No access control — any address can invoke fee withdrawal
function withdrawFees() external {
require(address(this).balance == uint256(totalFees), "...");
// ...
}

Risk

Likelihood: High

  • No barriers exist beyond time elapsed. Any participant can trigger this the moment the raffle duration expires.

  • Bots and MEV searchers routinely monitor protocols for permissionless termination opportunities.

Impact: High

  • An attacker can end a round exactly when their own player count dominates the array, maximizing their win probability under the weak randomness scheme (compounding with D02).

  • Prevents the protocol from accumulating a larger player pool before selecting a winner, reducing prize attractiveness.

Severity: High

Proof of Concept

An attacker monitors the blockchain for the moment block.timestamp >= raffleStartTime + raffleDuration. They call selectWinner() at a block where their own simulated output (see D02 — Weak Randomness) gives them the winning index.

// Griefer contract that terminates raffle at the optimal moment
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
interface IPuppyRaffle {
function selectWinner() external;
function raffleStartTime() external view returns (uint256);
function raffleDuration() external view returns (uint256);
}
contract RaffleTerminator {
function terminateWhenBeneficial(address target) external {
IPuppyRaffle raffle = IPuppyRaffle(target);
require(
block.timestamp >= raffle.raffleStartTime() + raffle.raffleDuration(),
"Not ready"
);
raffle.selectWinner(); // Called by attacker, not protocol
}
}

Expected outcome: The attacker terminates the raffle at the most strategically advantageous time, combining with the weak randomness finding to select the round they are most likely to win.

Recommended Mitigation

Add onlyOwner to selectWinner() if owner-driven termination is intended, or emit an event and require a designated "operator" address:

-function selectWinner() external {
+function selectWinner() external onlyOwner {
require(block.timestamp >= raffleStartTime + raffleDuration, "...");
// ...
}
-function withdrawFees() external {
+function withdrawFees() external onlyOwner {
// ...
}

If a permissionless termination model is desired (e.g., anyone can trigger after the window), document this explicitly in the protocol design and combine with verifiably random winner selection (Chainlink VRF) to prevent attacker timing advantage.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 5 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!