Skip to content

Commit 0ba0fc8

Browse files
✨ BlockHashLib (#1470)
Co-authored-by: Developer Uche <[email protected]>
1 parent 2cf5aeb commit 0ba0fc8

File tree

4 files changed

+100
-1
lines changed

4 files changed

+100
-1
lines changed

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ auto_detect_solc = false
1010
optimizer = true
1111
optimizer_runs = 1_000
1212
gas_limit = 100_000_000 # ETH is 30M, but we use a higher value.
13-
skip = ["*/*7702*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"]
13+
skip = ["*/*7702*", "*/*BlockHashLib*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"]
1414
fs_permissions = [{ access = "read", path = "./test/data"}]
1515
remappings = [
1616
"forge-std=test/utils/forge-std/"

src/utils/BlockHashLib.sol

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
/// @notice Library for accessing block hashes way beyond the 256-block limit. ref: EIP-2935
5+
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/BlockHashLib.sol)
6+
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Blockhash.sol)
7+
library BlockHashLib {
8+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9+
/* CONSTANTS */
10+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11+
12+
/// @dev Address of the EIP-2935 history storage contract.
13+
/// See: https://eips.ethereum.org/EIPS/eip-2935
14+
address internal constant HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53a10cb7A02335B175320002935;
15+
16+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
17+
/* OPERATIONS */
18+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
19+
20+
/// @dev Retrieves the block hash for any historical block within the supported range.
21+
/// The function gracefully handles future blocks and blocks beyond the history window by returning zero,
22+
/// consistent with the EVM's native `BLOCKHASH` behavior.
23+
function blockHash(uint256 blockNumber) internal view returns (bytes32 result) {
24+
unchecked {
25+
// If `blockNumber + 256` overflows:
26+
// - Typical chain height (`block.number > 255`) -> `staticcall` -> 0.
27+
// - Very early chain (`block.number <= 255`) -> `blockhash` -> 0.
28+
if (block.number <= blockNumber + 256) return blockhash(blockNumber);
29+
}
30+
/// @solidity memory-safe-assembly
31+
assembly {
32+
mstore(0x20, blockNumber)
33+
mstore(0x00, 0)
34+
pop(staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0x20, 0x20, 0x00, 0x20))
35+
result := mload(0x00)
36+
}
37+
}
38+
}

test/BlockHashLib.t.sol

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import "./utils/SoladyTest.sol";
5+
import {BlockHashLib} from "../src/utils/BlockHashLib.sol";
6+
7+
contract BlockHashLibTest is SoladyTest {
8+
uint256 internal startingBlock;
9+
10+
address internal constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE;
11+
12+
bytes private constant _HISTORY_STORAGE_BYTECODE =
13+
hex"3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500";
14+
15+
function testBlockHash(
16+
uint256 simulationBlockNumber,
17+
uint256 queryBlockNumber,
18+
uint256 savedBlockedNumber,
19+
bytes32 h
20+
) public {
21+
if (_randomChance(2)) {
22+
vm.etch(BlockHashLib.HISTORY_STORAGE_ADDRESS, _HISTORY_STORAGE_BYTECODE);
23+
}
24+
25+
savedBlockedNumber = _bound(savedBlockedNumber, 0, 2 ** 64 - 1);
26+
27+
vm.roll(savedBlockedNumber + 1);
28+
vm.prank(SYSTEM_ADDRESS);
29+
(bool success,) = BlockHashLib.HISTORY_STORAGE_ADDRESS.call(abi.encode(h));
30+
require(success);
31+
32+
vm.setBlockhash(savedBlockedNumber, h);
33+
34+
vm.roll(simulationBlockNumber);
35+
36+
assertEq(BlockHashLib.blockHash(queryBlockNumber), _blockHash(queryBlockNumber));
37+
}
38+
39+
function _blockHash(uint256 blockNumber) internal view returns (bytes32) {
40+
(bool success, bytes memory result) =
41+
address(this).staticcall(abi.encodeWithSignature("blockHash(uint256)", blockNumber));
42+
if (!success) return 0;
43+
return abi.decode(result, (bytes32));
44+
}
45+
46+
function blockHash(uint256 blockNumber) public view returns (bytes32 result) {
47+
if (block.number <= blockNumber + 256) return blockhash(blockNumber);
48+
address a = BlockHashLib.HISTORY_STORAGE_ADDRESS;
49+
if (a.code.length == 0) return 0;
50+
/// @solidity memory-safe-assembly
51+
assembly {
52+
mstore(0x20, blockNumber)
53+
mstore(0x00, 0)
54+
pop(staticcall(gas(), a, 0x20, 0x20, 0x00, 0x20))
55+
result := mload(0x00)
56+
}
57+
}
58+
}

test/utils/forge-std/Vm.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,4 +1988,7 @@ interface Vm is VmSafe {
19881988

19891989
/// Stops all safe memory expectation in the current subcontext.
19901990
function stopExpectSafeMemory() external;
1991+
1992+
/// Sets the blockhash for a given block number.
1993+
function setBlockhash(uint256 blockNumber, bytes32 blockHash) external;
19911994
}

0 commit comments

Comments
 (0)