Puppy Raffle

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

Public Mempool Visibility Enables Front-Running of Winner Selection

Description

Raffle participants should have equal chances of winning, with no ability to manipulate outcomes based on advance knowledge.

The public mempool allows attackers to see pending selectWinner() transactions. Combined with predictable randomness, attackers can calculate the winner and front-run with refund() calls to exit losing positions.

@> function refund(uint256 playerIndex) public { // No time restrictions
address playerAddress = players[playerIndex];
require(playerAddress == msg.sender, "PuppyRaffle: Only the player can refund");
require(playerAddress != address(0), "PuppyRaffle: Player already refunded, or is not active");
payable(msg.sender).sendValue(entranceFee);
players[playerIndex] = address(0);
emit RaffleRefunded(playerAddress);
}
@> function selectWinner() external { // Predictable outcome
// ... uses predictable randomness ...
}

Risk

Likelihood: Medium

  • Requires monitoring mempool and calculating outcomes, but tools exist for this

  • MEV bots routinely perform similar front-running attacks

Impact: High

  • Attackers gain risk-free participation by exiting losing raffles

  • Honest participants face unfair odds

  • Prize pool calculations become corrupted

Proof of Concept

  1. Attacker enters the raffle with multiple addresses

  2. Attacker monitors the mempool for selectWinner() transactions

  3. Attacker calculates the winner using the same predictable randomness formula

  4. Attacker determines their addresses will not win

  5. Attacker submits higher-gas refund() transactions to front-run

  6. Attacker exits the raffle before the winner is selected

  7. Attacker repeats, only remaining in raffles they will win

Recommended Mitigation

+ uint256 public refundCutoffTime;
+ function setRefundCutoff() internal {
+ refundCutoffTime = raffleStartTime + raffleDuration - 1 hours;
+ }
function refund(uint256 playerIndex) public {
+ require(block.timestamp < refundCutoffTime, "PuppyRaffle: Refund window closed");
address playerAddress = players[playerIndex];
// ...
}

Additionally, implement Chainlink VRF for unpredictable randomness.

Updates

Lead Judging Commences

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