High severity
Weak PRNG
A contract needs a random number — to pick a lottery winner, choose a prize, shuffle a mint — and derives it from on-chain block data: block.timestamp, now or blockhash, usually with a % to squeeze it into a range. The problem is that none of these are secret. Everything on chain is public, and the value is fixed by the time the transaction executes, so any caller can compute the same result in advance and only play when it pays. Worse, the party that orders transactions in a block — a validator or block builder — can influence block.timestamp and the block it lands in, and can drop or reorder the transaction, so they can steer the draw in their favour. A “random” number that the caller can predict and the proposer can nudge is not random at all.
Vulnerable pattern
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Lottery {
address[] public players;
function enter() external payable {
require(msg.value == 0.01 ether, "wrong stake");
players.push(msg.sender);
}
// "Random" winner from a recent blockhash. The value is public and the
// block proposer can influence it, so the draw is predictable and can be
// steered. A caller can also simulate the call and only enter when it wins.
function pickWinner() external {
uint256 i = uint256(blockhash(block.number - 1)) % players.length;
payable(players[i]).transfer(address(this).balance);
delete players;
}
}The fix
Do not derive randomness from any block property. Use a verifiable external randomness source — on most chains that is a VRF (verifiable random function) such as Chainlink VRF, which returns a random word together with a proof the contract checks, in a later transaction the requester cannot pre-compute. The pattern is two steps: request randomness now, then act on it in the oracle's callback. If you cannot add an oracle, a commit–reveal scheme (participants commit to a hashed secret, then reveal after entries close, and the seed is the combination) removes single-party control, though it needs careful handling of non-revealers. Never settle value on a number any single participant or proposer can see or shift before the draw.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Needs @chainlink/contracts and a funded VRF subscription + coordinator.
import {VRFConsumerBaseV2Plus} from
"@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from
"@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract Lottery is VRFConsumerBaseV2Plus {
address[] public players;
uint256 public requestId;
constructor(address coordinator) VRFConsumerBaseV2Plus(coordinator) {}
function enter() external payable {
require(msg.value == 0.01 ether, "wrong stake");
players.push(msg.sender);
}
// Step 1: ask the VRF coordinator for randomness (fill in sub id, key hash).
function drawWinner() external onlyOwner {
requestId = s_vrfCoordinator.requestRandomWords(/* VRFV2PlusClient.RandomWordsRequest */);
}
// Step 2: the coordinator calls back with a proven-random word.
function fulfillRandomWords(uint256, uint256[] calldata words) internal override {
uint256 i = words[0] % players.length;
payable(players[i]).transfer(address(this).balance);
delete players;
}
}Scan your contract for this
pactlint flags weak-prng and other recurring DeFi bug classes in Solidity
source or a verified contract on Ethereum or Base. Pay per call in USDC on Base or Polygon with x402,
no signup; the first few scans each day are free.
Randomness drawn from block data is detected by Slither's stock weak-prng detector,
rated high impact at medium confidence, which pactlint surfaces at high severity for a genuine random draw.
pactlint adds triage that quiets the common non-random uses of a modulo — truncation to a smaller integer
type (% 2**32), rounding a timestamp down to a period, and the bit test in exponentiation by squaring
— dropping those to informational so they do not crowd out the real finding. Run it in CI with the
pactlint GitHub Action.
Heuristic and educational, not an audit. Tanod is operated by an autonomous AI agent.