Puppy Raffle

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

Puppy rarity is derived from predictable randomness (msg.sender, block.difficulty), so an attacker can grind the call to mint a legendary NFT on demand

Predictable rarity randomness lets an attacker grind a guaranteed LEGENDARY puppy

Description

PuppyRaffle::selectWinner computes puppy rarity as uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100 (src/PuppyRaffle.sol:139). Both inputs are known to the caller, so an attacker can compute the resulting rarity in advance and choose calling conditions (sender, target block) that guarantee a LEGENDARY mint.

uint256 rarity = uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100; // @> attacker-predictable
if (rarity <= COMMON_RARITY) { ... }
else if (rarity <= COMMON_RARITY + RARE_RARITY) { ... }
else { tokenIdToRarity[tokenId] = LEGENDARY_RARITY; } // @> grindable target

Risk

Likelihood:

Medium. The attacker must be (or control) the winner-selecting caller, but given that, predicting and grinding the rarity is trivial — they iterate msg.sender candidates or wait for a block whose block.difficulty yields rarity > 95.

Impact:

Medium. The intended 70/25/5 rarity distribution is defeated. An attacker reliably mints LEGENDARY puppies, devaluing legitimately-earned rare and legendary NFTs and undermining the collection's scarcity and economic value.

Proof of Concept

The attacker checks the rarity formula off-chain and only proceeds when it resolves to LEGENDARY.

function willBeLegendary() public view returns (bool) {
uint256 rarity = uint256(
keccak256(abi.encodePacked(address(this), block.difficulty))
) % 100;
return rarity > 95; // @> LEGENDARY band; submit only when true
}

Recommended Mitigation

Derive rarity from a verifiable randomness source (Chainlink VRF) or a commit-reveal value rather than predictable on-chain data.

- uint256 rarity = uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100;
+ uint256 rarity = vrfRandomWord % 100; // verified randomness from VRF fulfillment
Updates

Lead Judging Commences

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