Ethernaut#3 - Coin Flip
Ethernaut #3 - Coin Flip
This is a coin flipping game where you need to build up your winning streak by guessing the outcome of a coin flip. To complete this level you’ll need to use your psychic abilities to guess the correct outcome 10 times in a row.
1 | // SPDX-License-Identifier: MIT |
Contract Breakdown
Let us breakdown the contract to understand what each function and piece of code does. The code consists of a single contract CoinFlip compiled with solidity version ^0.8.0.
Variables
1 | uint256 public consecutiveWins; |
consecutiveWins is a public variable that tracks how many correct guesses we have made in a row. lastHash stores the blockhash from the previous flip to prevent multiple flips in the same block. The FACTOR is a very large number, specifically 2^255, which is used to divide the blockhash and essentially reduce it to either 0 or 1.
Constructor
1 | constructor() { |
The constructor simply initializes consecutiveWins to 0.
Functions
1 | function flip(bool _guess) public returns (bool) { |
The flip function takes in a boolean _guess which is our prediction of the coin flip result. It computes blockValue from the blockhash of the previous block. If lastHash equals the current blockValue, it reverts, meaning we can only flip once per block. Then it divides the blockValue by FACTOR to get either 0 or 1, which determines the side of the coin. If our guess matches, consecutiveWins is incremented, otherwise it resets to 0.
Solution
The vulnerability here is that the “randomness” is entirely deterministic. The contract uses blockhash(block.number - 1) which is publicly available information on the blockchain. There is nothing random about it.
The key insight is that if we compute the coin flip result in the same block as the flip call, we will always know the answer.
Since block.number is the same for all transactions within a block, we can deploy an attacker contract that pre-calculates the flip and then calls the CoinFlip contract with the correct guess.
1 | // SPDX-License-Identifier: MIT |
Steps to pass:
- Copy the
HackCoinFlipcontract to Remix and compile it. - Deploy it with the CoinFlip instance address from Ethernaut.
- Call
hackFlip()10 times, once per block. You have to wait for each transaction to be mined before sending the next one. - After 10 successful calls, our
consecutiveWinswill be 10 and the challenge is passed.
Never rely on block variables like
block.number,block.timestamp, orblockhashfor randomness in smart contracts. Miners and other contracts can predict or manipulate these values. For true randomness on-chain, use an oracle like Chainlink VRF.