On-chain randomness you can read before you guess
A guess-the-number game with a prize pot, and why the number was never actually hidden.
The game is small enough to hold in your head. Pick a number from one to sixty, pay a fixed price, and the entry joins a pot. Guess the stored number and the whole pot transfers to you, then the game resets with a new number and an empty pot. Numbers already guessed are rejected, so each round narrows.
The number comes from hashing the block timestamp, the block difficulty and the sender address, taken modulo sixty. Every one of those is known at the moment the call executes. A contract can compute the same hash inside the same transaction, learn the answer, and only submit a guess when it matches. It never pays for a wrong one. The pot is not won, it is collected.
Marking the variable private does not help. Private controls what other
contracts can read through Solidity, not what is stored. Anyone can read the
slot directly from the chain, so the number was always visible to anyone willing
to look, quite apart from being derivable.
Using block difficulty as an entropy source also stopped meaning anything after the merge. The opcode now returns the beacon chain’s randomness value, which is better entropy and still the wrong tool, because it is known to the proposer in advance and readable by the contract at call time. Any value the contract can read, the caller can read first.
There are two real fixes. Commit to a hashed guess in one transaction and reveal it in a later one, so the answer cannot be computed and used atomically. Or take randomness from an oracle that produces it after the commitment exists.
Everything else in the contract is fine. The reset loop clears exactly sixty slots, which is bounded, and the guess bookkeeping is straightforward. The flaw is entirely in believing that a hash of on-chain values is a secret.