Puppy Raffle

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

Weak Randomness in PuppyRaffle::selectWinner allows winner prediction

[High] Weak Randomness in PuppyRaffle::selectWinner allows winner prediction

Description

The PuppyRaffle::selectWinner function attempts to generate a random winner index using keccak256 hashing of msg.sender, block.timestamp, and block.difficulty.

In the EVM, these variables are not secure sources of entropy. block.timestamp can be slightly manipulated by miners/validators, and block.difficulty (replaced by prevrandao post-Merge) is known before the transaction is finalized. Because an attacker can calculate the result of this hash off-chain using current block data, they can choose to only call selectWinner or enter the raffle when the calculation guarantees them a win.

Root Cause

The vulnerability stems from using deterministic on-chain variables for randomness in src/PuppyRaffle.sol. The hash calculation can be perfectly replicated by any external observer.

// src/PuppyRaffle.sol: line 128
@> uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;

Risk

Likelihood: High. Any sophisticated user or bot can monitor the state of the blockchain and compute the winner index in real-time.

Impact: High. The fairness of the raffle is compromised. An attacker can ensure they (or a specific address) win the prize every time, effectively stealing the pool from honest participants.

Proof of Concept (PoC)

The following Foundry test proves that an attacker can pre-calculate the winner using the exact same logic as the contract.

function test_WeakRandomnessIsPredictable() public {
// 1. Setup 4 players to satisfy the requirement
address[] memory players = new address[](4);
for(uint160 i=0; i<4; i++) players[i] = address(i + 1);
puppyRaffle.enterRaffle{value: 4 ether}(players);
// 2. Fast forward time to allow selection
vm.warp(block.timestamp + 1 days + 1);
// 3. Attacker pre-calculates the winner off-chain using contract logic
uint256 expectedWinnerIndex = uint256(
keccak256(abi.encodePacked(address(this), block.timestamp, block.difficulty))
) % 4;
// 4. Contract executes and picks the EXACT same winner predicted
puppyRaffle.selectWinner();
address actualWinner = puppyRaffle.previousWinner();
assertEq(actualWinner, players[expectedWinnerIndex]);
console.log("Randomness verified as deterministic/predictable");
}

Recommended Mitigation

The protocol should utilize a Verifiable Random Function (VRF), such as Chainlink VRF, to obtain provably fair and tamper-proof randomness from an off-chain oracle.

  • uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;

  • // Call Chainlink VRF and handle the random number in a callback

Updates

Lead Judging Commences

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