This is the third challenge on ethernaut: https://ethernaut.openzeppelin.com/level/0x4dF32584890A0026e56f7535d0f2C6486753624f
This contract is for a simple coin flip. Something like this might be used in a game where people bet money on a coin flip, thus making it valuable to try to beat the system. The contract creates a random number using the block hash of the current block number. This block hash is divided by (UINT_256_MAX - 1) / 2. Thus, there is a 50% chance for true and 50% chance for false. The flip logic looks like this
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
This attempt at randomness is not truly random and the underlying calculation to derive it is publicly available. Thus, it is trivial to run the same algorithm to predict what the next coin flip value will be
value = await web3.eth.getBlockNumber()
.then((num) => web3.eth.getBlock(num))
.then((block) => block.hash)
.then((hash) => parseInt(hash, 16) / 57896044618658097711785492504343953926634992332820282019728792003956564819968);
await contract.flip(value > 1 ? true : false);
There is no built in way for solidity to provide actual random numbers. Instead of this, we should request random numbers from an oracle such as Chainlink VRF.
