Cover photo

Ethernaut - Coin Flip Writeup

Coin Flip is the third Ethernaut challenge, and it involves having to guess a “random” coin flip correctly 10 times in a row. This might seem impossible (or improbable), but we’ll learn that being random in a public blockchain is harder than it sounds.

As always, you can find the full source code for this challenge below on Github.

https://github.com/imam-abbudi/ethernaut-web3py-solutions/tree/main/coinflip

You’re going to want to download the challenge’s source code locally for this one, keep a folder that looks something like this:

coinflip/
|_ Coinflip.sol
|_ CoinflipExploit.sol
|_ coinflip.py

And yes, we are going to be writing the exploit in Solidity.

coinflip.sol

coinflip.sol source code
coinflip.sol source code

This file should include the source code, like this. Reading through the code, we can see that it reads the block hash of the previous block, divides it by FACTOR, and checks if the division is equals to 1. That’s how it picks the side of the coin.

It is important to note that we can’t just guess the same side ten times in a row in the same block. Because the contract will revert if it notices the same blockValue is generated twice.

coinflipExploit.sol

coinflipExploit.sol source code
coinflipExploit.sol source code

We can create an interface with the Ethernaut’s challenge by importing the source code, and entering the challenge instance’s address in the constructor when we deploy the contract.

The function flipGuess() has the exact same logic as the challenge, and since the two contracts are running on the same network, blockValue will be the same value for both contracts. Since we already know FACTOR, we can directly import it as it doesn’t change values.

If someone were to call flipGuess(), it would create all the variables, and call the challenge contract’s flip() function with the correct side. Now we just have to write a Python script to call our flipGuess() function 10 blocks in a row.

coinflip.py

As always we’ll import the necessary modules and variables.

post image

We’ll also need to deploy our contract for this challenge. To do this, we’ll need to get both the ABI and bytecode from our exploit script.

A quick reminder:

ABI: An interface to interact with deployed Solidity contracts on the Ethereum network.

Bytecode: The compiled version of our Solidity program, designed for computers to read instructions rather than humans.

post image

This function will read our exploit contract, and return a tuple of both the ABI and bytecode. We will use both of these variables when deploying our contract.

post image

We’ll also use this function from previous challenges, it will sign the transaction we want and return the receipt.

post image

Moving onto our main function, we will first save our ABI and bytecode from our exploit contract, build our transaction by supplying gas and its price, and deploy the contract.

In the constructor of our exploit contract, we will pass in the address of the Ethernaut challenge instance, which will let our exploit contract know which contract address to attack.

post image

Using the receipt of the exploit contract deployment transaction, we can print the address of our contract if it was deployed successfully, or exit if the transaction failed.

post image

If it was successfully deployed, we’ll use the contract address and the ABI we generated at the top of our main function to create an instance.

We will also keep track of there number of correct guesses in correct_guesses. In the while loop, we call our exploit function. If the transaction goes through, we assume the guess is correct and increment correct_guesses by one.

Once we reach 10 guesses, we print a completion message and exit the program. The challenge is now complete, and you can try submitting on Ethernaut.

Note: You could also call the number of correct guesses from the Ethernaut instance itself, but I found that the guesses are always right so it makes more sense to keep a local track of the correct guesses.