Repository: OpenZeppelin/openzeppelin-contracts
Pull request #5642: Add Blockhash library following EIP-2935
Author: login: ernestognw
Created: 2025-04-21T17:10:02Z | Merged: 2025-04-29T22:05:27Z

<!-- Thank you for your interest in contributing to OpenZeppelin! -->

<!-- Consider opening an issue for discussion prior to submitting a PR. -->
<!-- New features will be merged faster if they were first discussed and designed with the team. -->

Fixes #5639

<!-- Describe the changes introduced in this pull request. -->
<!-- Include any context necessary for understanding the PR's purpose. -->


#### PR Checklist

<!-- Before merging the pull request all of the following must be complete. -->
<!-- Feel free to submit a PR or Draft PR even if some items are pending. -->
<!-- Some of the items may not apply. -->

- [ ] Tests
- [ ] Documentation
- [ ] Changeset entry (run `npx changeset add`)


Linked issue #5639: Create a library to serve historical block hashes from state (EIP-2935)
**🧐 Motivation**
Before Pectra you can only query the last 256 block hashes. With [EIP-2539](https://eips.ethereum.org/EIPS/eip-2935) the EL will hold in a system contract the last 8192 block hashes(~27 hs) of blocks, this is very useful for rollups and other applications that need to query or proof something against older ethereum state.

**📝 Details**
Calling the new contract will be more expensive, so you will probably want to still use the precompile for newer blocks - a simple library that abstracts this away seems pretty useful.
Here's a very simple way to implement this. If we use this in our minimal-rollup I'm happy to contribute upstream
```solidity
/// @dev Reads a block hash up to 8 191 blocks ago after the Pectra fork.
///      Falls back to the BLOCKHASH opcode for <256 blocks.
library BlockhashHistory {
    address constant _HISTORY_ADDR = 0x0000F90827F1C53a10Cb7A02335B175320002935;
    uint256 constant _WINDOW = 8191;

    /// @notice Returns the hash of block `n` or reverts if too old / future.
    function hashOf(uint256 n) internal view returns (bytes32 h) {
        uint256 current = block.number;
        if (n >= current) revert("future block");
        unchecked {
            if (current - n <= 256) {
                return blockhash(n); // cheap path
            }
            if (current - n > _WINDOW) revert("too old");
        }
        bytes memory input = abi.encodePacked(bytes32(n));
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let success := staticcall(gas(), _HISTORY_ADDR, add(input, 32), 32, 0, 32)
            if iszero(success) { revert(0, 0) }
            h := mload(0)
        }
    }
}
```

<!-- Make sure that you have reviewed the OpenZeppelin Contracts Contributor Guidelines. -->
<!-- https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CONTRIBUTING.md -->


Diff:
diff --git a/.changeset/wet-dodos-reply.md b/.changeset/wet-dodos-reply.md
new file mode 100644
index 00000000000..00ebaddb6ca
--- /dev/null
+++ b/.changeset/wet-dodos-reply.md
@@ -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.
diff --git a/contracts/mocks/BlockhashMock.sol b/contracts/mocks/BlockhashMock.sol
new file mode 100644
index 00000000000..e0a37289969
--- /dev/null
+++ b/contracts/mocks/BlockhashMock.sol
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {Blockhash} from "../utils/Blockhash.sol";
+
+/// @dev This mock is required for upgradeable tests to pass
+contract BlockhashMock {
+
+}
diff --git a/contracts/utils/Blockhash.sol b/contracts/utils/Blockhash.sol
new file mode 100644
index 00000000000..db8eaac2a21
--- /dev/null
+++ b/contracts/utils/Blockhash.sol
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+/**
+ * @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") {
+            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.
+            if and(gt(returndatasize(), 0), staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0, 0x20, 0, 0x20)) {
+                hash := mload(0)
+            }
+        }
+    }
+}
diff --git a/foundry.toml b/foundry.toml
index 78dd0781224..7a2e8a60942 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -1,6 +1,6 @@
 [profile.default]
 solc_version = '0.8.24'
-evm_version = 'cancun'
+evm_version = 'prague'
 optimizer = true
 optimizer-runs = 200
 src = 'contracts'
diff --git a/hardhat/common-contracts.js b/hardhat/common-contracts.js
index fd4ef1dbad5..67e44be23cd 100644
--- a/hardhat/common-contracts.js
+++ b/hardhat/common-contracts.js
@@ -47,6 +47,12 @@ const INSTANCES = {
       bytecode: '0x60003681823780368234f58015156014578182fd5b80825250506014600cf3',
     },
   },
+  eip2935: {
+    address: '0x0000F90827F1C53a10cb7A02335B175320002935',
+    abi: [],
+    bytecode:
+      '0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500',
+  },
 };
 
 const setup = (input, ethers) =>
diff --git a/test/utils/Blockhash.t.sol b/test/utils/Blockhash.t.sol
new file mode 100644
index 00000000000..eda4da626d9
--- /dev/null
+++ b/test/utils/Blockhash.t.sol
@@ -0,0 +1,100 @@
+// 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 constant HISTORY_STORAGE_BYTECODE =
+        hex"3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500";
+
+    function setUp() public {
+        vm.roll(block.number + 100);
+
+        startingBlock = block.number;
+        vm.etch(Blockhash.HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_BYTECODE);
+    }
+
+    function testFuzzRecentBlocks(uint8 offset, uint64 currentBlock, bytes32 expectedHash) public {
+        // Recent blocks (1-256 blocks old)
+        uint256 boundedOffset = uint256(offset) + 1;
+        vm.assume(currentBlock > boundedOffset);
+        vm.roll(currentBlock);
+
+        uint256 targetBlock = currentBlock - boundedOffset;
+        vm.setBlockhash(targetBlock, expectedHash);
+
+        bytes32 result = Blockhash.blockHash(targetBlock);
+        assertEq(result, blockhash(targetBlock));
+        assertEq(result, expectedHash);
+    }
+
+    function testFuzzHistoryBlocks(uint16 offset, uint256 currentBlock, bytes32 expectedHash) public {
+        // History blocks (257-8191 blocks old)
+        offset = uint16(bound(offset, 257, 8191));
+        vm.assume(currentBlock > offset);
+        vm.roll(currentBlock);
+
+        uint256 targetBlock = currentBlock - offset;
+        _setHistoryBlockhash(targetBlock, expectedHash);
+
+        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 testUnsupportedChainsReturnZeroWhenOutOfRange() public {
+        vm.etch(Blockhash.HISTORY_STORAGE_ADDRESS, hex"");
+
+        vm.roll(block.number + 1000);
+        assertEq(Blockhash.blockHash(block.number - 1000), bytes32(0));
+    }
+
+    function _setHistoryBlockhash(bytes32 blockHash) internal {
+        _setHistoryBlockhash(block.number, blockHash);
+    }
+
+    function _setHistoryBlockhash(uint256 blockNumber, bytes32 blockHash) internal {
+        // Subtracting 1 due to bug encountered during coverage
+        uint256 currentBlock = block.number - 1;
+        vm.assume(blockNumber < type(uint256).max);
+        vm.roll(blockNumber + 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(currentBlock + 1);
+    }
+}
diff --git a/test/utils/Blockhash.test.js b/test/utils/Blockhash.test.js
new file mode 100644
index 00000000000..a3de2655e83
--- /dev/null
+++ b/test/utils/Blockhash.test.js
@@ -0,0 +1,76 @@
+const { ethers } = require('hardhat');
+const { expect } = require('chai');
+const { loadFixture, mine, mineUpTo, setCode } = require('@nomicfoundation/hardhat-network-helpers');
+const { impersonate } = require('../helpers/account');
+
+async function fixture() {
+  const mock = await ethers.deployContract('$Blockhash');
+  return { mock };
+}
+
+const HISTORY_STORAGE_ADDRESS = '0x0000F90827F1C53a10cb7A02335B175320002935';
+const SYSTEM_ADDRESS = '0xfffffffffffffffffffffffffffffffffffffffe';
+const HISTORY_SERVE_WINDOW = 8191;
+const BLOCKHASH_SERVE_WINDOW = 256;
+
+describe('Blockhash', function () {
+  before(async function () {
+    Object.assign(this, await loadFixture(fixture));
+
+    impersonate(SYSTEM_ADDRESS);
+    this.systemSigner = await ethers.getSigner(SYSTEM_ADDRESS);
+  });
+
+  it('recent block', async function () {
+    await mine();
+
+    const mostRecentBlock = (await ethers.provider.getBlock('latest')).number;
+    const blockToCheck = mostRecentBlock - 1;
+    const fetchedHash = (await ethers.provider.getBlock(blockToCheck)).hash;
+    await expect(this.mock.$blockHash(blockToCheck)).to.eventually.equal(fetchedHash);
+  });
+
+  it('old block', async function () {
+    await mine();
+
+    const mostRecentBlock = await ethers.provider.getBlock('latest');
+
+    // Call the history address with the most recent block hash
+    await this.systemSigner.sendTransaction({
+      to: HISTORY_STORAGE_ADDRESS,
+      data: mostRecentBlock.hash,
+    });
+
+    await mineUpTo(mostRecentBlock.number + BLOCKHASH_SERVE_WINDOW + 10);
+
+    // Verify blockhash after setting history
+    await expect(this.mock.$blockHash(mostRecentBlock.number)).to.eventually.equal(mostRecentBlock.hash);
+  });
+
+  it('very old block', async function () {
+    await mine();
+
+    const mostRecentBlock = await ethers.provider.getBlock('latest');
+    await mineUpTo(mostRecentBlock.number + HISTORY_SERVE_WINDOW + 10);
+
+    await expect(this.mock.$blockHash(mostRecentBlock.number)).to.eventually.equal(ethers.ZeroHash);
+  });
+
+  it('future block', async function () {
+    await mine();
+
+    const mostRecentBlock = await ethers.provider.getBlock('latest');
+    const blockToCheck = mostRecentBlock.number + 10;
+    await expect(this.mock.$blockHash(blockToCheck)).to.eventually.equal(ethers.ZeroHash);
+  });
+
+  it('unsupported chain', async function () {
+    await setCode(HISTORY_STORAGE_ADDRESS, '0x00');
+
+    const mostRecentBlock = await ethers.provider.getBlock('latest');
+    await mineUpTo(mostRecentBlock.number + BLOCKHASH_SERVE_WINDOW + 10);
+
+    await expect(this.mock.$blockHash(mostRecentBlock.number)).to.eventually.equal(ethers.ZeroHash);
+    await expect(this.mock.$blockHash(mostRecentBlock.number + 20)).to.eventually.not.equal(ethers.ZeroHash);
+  });
+});


Reviews:
user: login: arr00
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T19:13:33Z
user: login: arr00
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T19:14:28Z
user: login: ernestognw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T00:59:36Z
user: login: arr00
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T23:31:54Z
user: login: ernestognw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T23:38:35Z
user: login: arr00
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T14:54:08Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a Blockhash library as per EIP-2935, extending access to historical block hashes beyond the standard 256-block limit.
- Updates the EVM version in foundry.toml from 'cancun' to 'prague' to support new EIP features.
- Adds a changeset entry detailing the Blockhash library feature.

### Reviewed Changes

Copilot reviewed 2 out of 4 changed files in this pull request and generated no comments.

| File                             | Description                                                    |
| -------------------------------- | -------------------------------------------------------------- |
| foundry.toml                     | Updated evm_version to 'prague' to reflect the new EIP-2935 support. |
| .changeset/wet-dodos-reply.md      | Added changeset entry summarizing the Blockhash library feature.  |

<details>
<summary>Files not reviewed (2)</summary>

* **contracts/utils/Blockhash.sol**: Language not supported
* **test/utils/Blockhash.t.sol**: Language not supported
</details>



state: COMMENTED
author_association: NONE
submitted_at: 2025-04-24T15:19:33Z
user: login: arr00
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-04-29T21:33:08Z
user: login: arr00
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-29T21:57:22Z
user: login: boss1512boss-commits
state: COMMENTED
author_association: NONE
submitted_at: 2025-10-25T19:07:32Z

Review comments:
diff_hunk: @@ -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, 0x20) // Store length and clear potentially dirty scratch space
+            mstore(0x20, 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, 0x20, 0, 0, 0x20)
path: contracts/utils/Blockhash.sol
user: login: arr00
body: ```suggestion
            let success := staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0x20, 0x20, 0, 0x20)
```
Shouldn't the input length be `0x20`?
created_at: 2025-04-21T19:13:33Z
author_association: MEMBER
diff_hunk: @@ -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, 0x20) // Store length and clear potentially dirty scratch space
path: contracts/utils/Blockhash.sol
user: login: arr00
body: If the contract isn't deployed and there is no return data, this won't be overwritten.
created_at: 2025-04-21T19:14:28Z
author_association: MEMBER
diff_hunk: @@ -1,6 +1,6 @@
 [profile.default]
 solc_version = '0.8.24'
-evm_version = 'cancun'
+evm_version = 'prague'
path: foundry.toml
user: login: ernestognw
body: Perhaps this is unnecessary since we're `vm.etch`'ing the bytecode anyway. I couldn't find anythign about EIP-2935 in the Foundry docs
created_at: 2025-04-22T00:59:36Z
author_association: MEMBER
diff_hunk: @@ -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)
path: contracts/utils/Blockhash.sol
user: login: arr00
body: If used on an unsupported chain this will return the block number? Why not just use a second zero-ed slot?
created_at: 2025-04-23T23:31:50Z
author_association: MEMBER
diff_hunk: @@ -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)
path: contracts/utils/Blockhash.sol
user: login: ernestognw
body: Do you mean using `mload(0x20)`? I think we'd need to clear it out too with `mstore(0x20,0)`

I think this would be a better implementation:

```solidity
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.
    if and(staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0, 0x20, 0, 0x20), returndatasize()) {
        hash := mload(0)
    }
}
```

wdyt?
created_at: 2025-04-23T23:38:34Z
author_association: MEMBER
diff_hunk: @@ -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)
path: contracts/utils/Blockhash.sol
user: login: arr00
body: Yes I this would work
created_at: 2025-04-24T14:54:08Z
author_association: MEMBER
diff_hunk: @@ -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, 0x20) // Store length and clear potentially dirty scratch space
+            mstore(0x20, 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, 0x20, 0, 0, 0x20)
path: contracts/utils/Blockhash.sol
user: login: boss1512boss-commits
body: 1c2fab1a005044df00fbe6b9907f050bc6b5327e
created_at: 2025-10-25T19:07:32Z
author_association: NONE

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-04-21T17:10:05Z
body: ###  🦋  Changeset detected

Latest commit: 1c2fab1a005044df00fbe6b9907f050bc6b5327e

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                  | Type  |
| --------------------- | ----- |
| openzeppelin-solidity | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/ernestognw/openzeppelin-contracts/new/feature/EIP-2935?filename=.changeset/light-falcons-run.md&value=---%0A%22openzeppelin-solidity%22%3A%20patch%0A---%0A%0AAdd%20Blockhash%20library%20following%20EIP-2935%0A)


author_association: NONE
user: login: ernestognw
created_at: 2025-04-21T17:10:46Z
body: Tests are missing, perhaps there's a way to enable 2935 in either Foundry or Hardhat but requires some research before continue
author_association: MEMBER
user: login: arr00
created_at: 2025-04-21T21:41:06Z
body: Btw https://github.com/NomicFoundation/hardhat/issues/6226
author_association: MEMBER
user: login: ernestognw
created_at: 2025-04-22T00:47:17Z
body: Yeah I'm also trying to test in Foundry and it doesn't really work 😕 
author_association: MEMBER
user: login: ernestognw
created_at: 2025-04-29T22:05:22Z
body: Thanks for leading this @arr00 !
author_association: MEMBER