-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Add Blockhash library following EIP-2935 #5642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
f5aec41
Add Blockhash library following EIP-2935
ernestognw e5062ff
Merge branch 'master' into feature/EIP-2935
ernestognw aba1a63
Add failing tests
ernestognw 568f692
nits
ernestognw 3184a68
fix tests
arr00 92992e8
fix coverage
arr00 1f2d0f6
add test to ensure unsupported chains fail gracefully
arr00 1e95107
fix return value on unsupported chains
arr00 eb7b88f
add hardhat testing
arr00 fc6444d
add blockhash mock
arr00 2ad2485
fix lint
arr00 e8b16c5
fix pragma
arr00 58940d8
Simplify and fix lint
ernestognw 1c2fab1
Fix and evaluation order
ernestognw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'openzeppelin-solidity': minor | ||
| --- | ||
|
|
||
| `Blockhash`: Add a library that provides access to historical block hashes using EIP-2935's history storage, extending the standard 256-block limit to 8191 blocks. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.24; | ||
|
|
||
| /** | ||
| * @dev Library for accessing historical block hashes beyond the standard 256 block limit. | ||
| * Uses EIP-2935's history storage contract which maintains a ring buffer of the last | ||
| * 8191 block hashes in state. | ||
| * | ||
| * For blocks within the last 256 blocks, it uses the native `BLOCKHASH` opcode. | ||
| * For blocks between 257 and 8191 blocks ago, it queries the EIP-2935 history storage. | ||
| * For blocks older than 8191 or future blocks, it returns zero, matching the `BLOCKHASH` behavior. | ||
| * | ||
| * NOTE: After EIP-2935 activation, it takes 8191 blocks to completely fill the history. | ||
| * Before that, only block hashes since the fork block will be available. | ||
| */ | ||
| library Blockhash { | ||
| address internal constant HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53a10cb7A02335B175320002935; | ||
|
|
||
| /** | ||
| * @dev Retrieves the block hash for any historical block within the supported range. | ||
| * | ||
| * NOTE: The function gracefully handles future blocks and blocks beyond the history window | ||
| * by returning zero, consistent with the EVM's native `BLOCKHASH` behavior. | ||
| */ | ||
| function blockHash(uint256 blockNumber) internal view returns (bytes32) { | ||
| uint256 current = block.number; | ||
| uint256 distance; | ||
|
|
||
| unchecked { | ||
| // Can only wrap around to `current + 1` given `block.number - (2**256 - 1) = block.number + 1` | ||
| distance = current - blockNumber; | ||
| } | ||
|
|
||
| return distance > 256 && distance <= 8191 ? _historyStorageCall(blockNumber) : blockhash(blockNumber); | ||
| } | ||
|
|
||
| /// @dev Internal function to query the EIP-2935 history storage contract. | ||
| function _historyStorageCall(uint256 blockNumber) private view returns (bytes32 hash) { | ||
| assembly ("memory-safe") { | ||
| // Use scratch space to allocate blockNumber | ||
| mstore(0, blockNumber) // Store the blockNumber in scratch space | ||
|
|
||
| // In case the history storage address is not deployed, the call will succeed | ||
| // without returndata, so the hash will be 0 just as querying `blockhash` directly. | ||
| let success := staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0, 0x20, 0, 0x20) | ||
|
|
||
| // In case of failure, the returndata might include the revert reason or custom error | ||
| if success { | ||
| hash := mload(0) | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.24; | ||
|
|
||
| import {Test} from "forge-std/Test.sol"; | ||
| import {Blockhash} from "../../contracts/utils/Blockhash.sol"; | ||
|
|
||
| contract BlockhashTest is Test { | ||
| uint256 internal startingBlock; | ||
|
|
||
| address internal constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; | ||
|
|
||
| // See https://eips.ethereum.org/EIPS/eip-2935#bytecode | ||
| // Generated using https://www.evm.codes/playground | ||
| bytes private HISTORY_STORAGE_BYTECODE = | ||
| hex"3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500"; | ||
|
|
||
| function setUp() public { | ||
| startingBlock = block.number; | ||
| vm.etch(Blockhash.HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_BYTECODE); | ||
| } | ||
|
|
||
| function testFuzzRecentBlocks(uint256 offset, uint256 currentBlock, bytes32 expectedHash) public { | ||
| // Recent blocks (1-256 blocks old) | ||
| offset = bound(offset, 1, 256); | ||
| vm.assume(currentBlock > offset); | ||
| vm.roll(currentBlock); | ||
|
|
||
| uint256 targetBlock = currentBlock - offset; | ||
| vm.setBlockhash(targetBlock, expectedHash); | ||
|
|
||
| bytes32 result = Blockhash.blockHash(targetBlock); | ||
| assertEq(result, blockhash(targetBlock)); | ||
| assertEq(result, expectedHash); | ||
| } | ||
|
|
||
| function testFuzzHistoryBlocks(uint256 offset, uint256 currentBlock, bytes32 expectedHash) public { | ||
| // History blocks (257-8191 blocks old) | ||
| offset = bound(offset, 257, 8191); | ||
| vm.assume(currentBlock > offset); | ||
| vm.roll(currentBlock); | ||
| _setHistoryBlockhash(expectedHash); | ||
|
|
||
| uint256 targetBlock = currentBlock - offset; | ||
| bytes32 result = Blockhash.blockHash(targetBlock); | ||
| (bool success, bytes memory returndata) = Blockhash.HISTORY_STORAGE_ADDRESS.staticcall( | ||
| abi.encodePacked(bytes32(targetBlock)) | ||
| ); | ||
| assertTrue(success); | ||
| assertEq(result, abi.decode(returndata, (bytes32))); | ||
| assertEq(result, expectedHash); | ||
| } | ||
|
|
||
| function testFuzzVeryOldBlocks(uint256 offset, uint256 currentBlock) public { | ||
| // Very old blocks (>8191 blocks old) | ||
| offset = bound(offset, 8192, type(uint256).max); | ||
| vm.assume(currentBlock > offset); | ||
| vm.roll(currentBlock); | ||
|
|
||
| uint256 targetBlock = currentBlock - offset; | ||
| bytes32 result = Blockhash.blockHash(targetBlock); | ||
| assertEq(result, bytes32(0)); | ||
| } | ||
|
|
||
| function testFuzzFutureBlocks(uint256 offset, uint256 currentBlock) public { | ||
| // Future blocks | ||
| offset = bound(offset, 1, type(uint256).max); | ||
| vm.roll(currentBlock); | ||
|
|
||
| unchecked { | ||
| uint256 targetBlock = currentBlock + offset; | ||
| bytes32 result = Blockhash.blockHash(targetBlock); | ||
| assertEq(result, blockhash(targetBlock)); | ||
| } | ||
| } | ||
|
|
||
| function _setHistoryBlockhash(bytes32 blockHash) internal { | ||
| vm.assume(block.number < type(uint256).max); | ||
| vm.roll(block.number + 1); // roll to the next block so the storage contract sets the parent's blockhash | ||
| vm.prank(SYSTEM_ADDRESS); | ||
| (bool success, ) = Blockhash.HISTORY_STORAGE_ADDRESS.call(abi.encode(blockHash)); // set parent's blockhash | ||
| assertTrue(success); | ||
| vm.roll(block.number - 1); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.