Skip to content

Commit 248ee8b

Browse files
✨ Add support for EIP-2935 serving historical block hashes (#1466)
1 parent 813c66f commit 248ee8b

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

src/utils/LibBlockHash.sol

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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/LibBlockHash.sol)
6+
library LibBlockHash {
7+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
8+
/* CONSTANTS */
9+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
10+
11+
/// @dev Address of the EIP-2935 history storage contract.
12+
/// See: https://eips.ethereum.org/EIPS/eip-2935
13+
address internal constant HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53a10cb7A02335B175320002935;
14+
15+
/// @dev Retrieves the block hash for any historical block within the supported range.
16+
/// The function gracefully handles future blocks and blocks beyond the history window by returning zero,
17+
/// consistent with the EVM's native `BLOCKHASH` behavior.
18+
function blockHash(uint256 blockNumber) internal view returns (bytes32 hash) {
19+
assembly {
20+
let current := number()
21+
let distance := sub(current, blockNumber)
22+
23+
// Check if distance < 257
24+
if lt(distance, 257) {
25+
// Return blockhash(blockNumber)
26+
mstore(0x00, blockhash(blockNumber))
27+
return(0x00, 0x20)
28+
}
29+
30+
// Store the blockNumber in scratch space
31+
mstore(0x00, blockNumber)
32+
mstore(0x20, 0)
33+
34+
// call history storage address
35+
pop(staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0x00, 0x20, 0x20, 0x20))
36+
37+
// load result
38+
hash := mload(0x20)
39+
}
40+
}
41+
}

src/utils/LibTransient.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ library LibTransient {
406406
_compat(ptr)._spacer = 0;
407407
}
408408

409+
/// @dev
410+
409411
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
410412
/* ADDRESS OPERATIONS */
411413
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

test/LibBlockHash.t.sol

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import "./utils/SoladyTest.sol";
5+
import {LibBlockHash} from "../src/utils/LibBlockHash.sol";
6+
7+
contract LibBlockHashTest 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 setUp() public {
16+
vm.roll(block.number + 100);
17+
startingBlock = block.number;
18+
vm.etch(LibBlockHash.HISTORY_STORAGE_ADDRESS, _HISTORY_STORAGE_BYTECODE);
19+
}
20+
21+
function __blockHash(uint256 blockNumber, bytes32 expectedHash, bytes32 sysExpectedHash)
22+
internal
23+
view
24+
returns (bool)
25+
{
26+
if (expectedHash != sysExpectedHash) return false;
27+
return sysExpectedHash == LibBlockHash.blockHash(blockNumber);
28+
}
29+
30+
function testFuzzRecentBlocks(uint8 offset, uint64 currentBlock, bytes32 expectedHash) public {
31+
// Recent blocks (1-256 blocks old)
32+
uint256 boundedOffset = uint256(offset) + 1;
33+
vm.assume(currentBlock > boundedOffset);
34+
vm.roll(currentBlock);
35+
36+
uint256 targetBlock = currentBlock - boundedOffset;
37+
vm.setBlockhash(targetBlock, expectedHash);
38+
39+
assertTrue(__blockHash(targetBlock, expectedHash, blockhash(targetBlock)));
40+
}
41+
42+
function testFuzzVeryOldBlocks(uint256 offset, uint256 currentBlock) public {
43+
// Very old blocks (>8191 blocks old)
44+
offset = _bound(offset, 8192, type(uint256).max);
45+
vm.assume(currentBlock > offset);
46+
vm.roll(currentBlock);
47+
48+
uint256 targetBlock = currentBlock - offset;
49+
assertTrue(__blockHash(targetBlock, bytes32(0), bytes32(0)));
50+
}
51+
52+
function testFuzzFutureBlocks(uint256 offset, uint256 currentBlock) public {
53+
// Future blocks
54+
offset = _bound(offset, 1, type(uint256).max);
55+
currentBlock = _bound(currentBlock, 0, type(uint256).max - offset);
56+
vm.roll(currentBlock);
57+
58+
unchecked {
59+
uint256 targetBlock = currentBlock + offset;
60+
assertTrue(__blockHash(targetBlock, blockhash(targetBlock), blockhash(targetBlock)));
61+
}
62+
}
63+
64+
function testUnsupportedChainsReturnZeroWhenOutOfRange() public {
65+
vm.etch(LibBlockHash.HISTORY_STORAGE_ADDRESS, hex"");
66+
67+
vm.roll(block.number + 1000);
68+
assertEq(LibBlockHash.blockHash(block.number - 1000), bytes32(0));
69+
}
70+
}

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)