Puppy Raffle

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

HIGH-01: Reentrancy in selectWinner() - External Call Before State Update

HIGH-01: Reentrancy in selectWinner() - External Call Before State Update

Description

The selectWinner() function makes an external call to transfer the prize pool to the winner before updating critical state variables (raffleStartTime, players array, previousWinner). If the winner is a malicious contract with a fallback function, it can re-enter selectWinner() or other functions.

// Root cause in the codebase with @> marks to highlight the relevant section
function selectWinner() external {
// ... calculations ...
@> (bool success, ) = winner.call{value: prizePool}("");
require(success, "PuppyRaffle: Failed to send prize pool to winner");
// State updates AFTER external call
@> raffleStartTime = block.timestamp;
@> players = new address[](0);
@> previousWinner = winner;
}

Risk

Likelihood:

  • Requires winner to be a malicious contract

  • Attacker can manipulate randomness to ensure they win (see CRIT-01)

Impact:

  • Re-entrancy could allow double-spending of prize pool

  • State corruption if re-entering other functions

  • Potential to drain contract funds

Proof of Concept

contract MaliciousWinner {
PuppyRaffle raffle;
function attack() external {
// Enter raffle, manipulate to win
// Then call selectWinner()
}
fallback() external payable {
if (address(raffle).balance > 0) {
raffle.selectWinner(); // Re-enter
}
}
}

Recommended Mitigation

function selectWinner() external {
// ... existing checks and calculations ...
// Update state BEFORE external call (checks-effects-interactions)
raffleStartTime = block.timestamp;
address[] memory oldPlayers = players;
players = new address[](0);
previousWinner = winner;
// Now make external call
(bool success, ) = winner.call{value: prizePool}("");
require(success, "PuppyRaffle: Failed to send prize pool to winner");
// Fee transfer also after state update
(bool feeSuccess, ) = feeAddress.call{value: fee}("");
require(feeSuccess, "PuppyRaffle: Failed to send fee to feeAddress");
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 7 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!