Skip to content

Instantly share code, notes, and snippets.

@pcaversaccio
Last active May 7, 2025 10:54
Show Gist options
  • Save pcaversaccio/64c7829e0c7ed4eeb1e5a07e2177836c to your computer and use it in GitHub Desktop.
Save pcaversaccio/64c7829e0c7ed4eeb1e5a07e2177836c to your computer and use it in GitHub Desktop.
Historical block hashes oracle Vyper contract.
# pragma version ~=0.4.1
"""
@title Historical Block Hashes Oracle
@custom:contract-name block_hash_oracle
@license GNU Affero General Public License v3.0 only
@author pcaversaccio
@notice The contract function `block_hash` can be used to access the
historical block hashes beyond the default 256-block limit.
We use the EIP-2935 (https://eips.ethereum.org/EIPS/eip-2935)
history contract, which maintains a ring buffer of the last
8,191 block hashes stored in state:
- For blocks within the last 256 blocks, the function `block_hash`
uses the native `BLOCKHASH` opcode.
- For blocks between 257 and 8,191 blocks ago, the function
`block_hash` queries the EIP-2935 history contract using the
specified `get` method: https://eips.ethereum.org/EIPS/eip-2935#get.
- For blocks older than 8,191 blocks or future blocks (including the
current one), it returns zero, matching the `BLOCKHASH` behavior.
Please note that after EIP-2935 is activated, it takes 8,191
blocks to fully populate the history. Before that, only block
hashes from the fork block onward are available.
"""
from snekmate.utils import block_hash as bh
@external
@view
def block_hash(block_number: uint256) -> bytes32:
"""
@dev Returns the block hash for block number `block_number`.
@notice For blocks older than 8,191 or future blocks (including
the current one), returns zero, matching the `BLOCKHASH`
behaviour. Furthermore, this function does verify if the
history contract is deployed. If the history contract is
undeployed, the function will fallback to the `BLOCKHASH`
behaviour.
@param block_number The 32-byte block number.
@return bytes32 The 32-byte block hash for block number `block_number`.
"""
return bh._block_hash(block_number)
@pcaversaccio
Copy link
Author

pcaversaccio commented Apr 29, 2025

Compilation Versions

Use Vyper version 0.4.1 and 🐍 snekmate commit 96b0845:

~$ python -m venv venv
~$ source venv/bin/activate  # Or `venv\Scripts\activate` on Windows.
~$ pip install vyper==0.4.1 git+https://github.com/pcaversaccio/snekmate.git@96b084595a4bc5d326d62dbbada03826e87621e4

After, save the gist code to a file called block_hash_oracle.vy and run:

vyper block_hash_oracle.vy

Contract Creation Code

0x61015961001161000039610159610000f35f3560e01c63b509f4ef81186100315760243610341761015557602060043560e05261002c61014061009a565b610140f35b5f5ffd5b5a6040516080526020606052606050602060c0606051608071f90827f1c53a10cb7a02335b17532000293585fa9050610070573d5f5f3e3d5ffd5b3d602081183d602010021860a05260a0805160200360031b6020820151811c811b90509050815250565b4360e051106100ac575f815250610153565b60e0514303610100526101006101005111156101385761200061010051101561010b577f6e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d71f90827f1c53a10cb7a02335b1753200029353f141561010e565b60015b61012f5760e051604052610123610120610035565b61012051815250610153565b5f815250610153565b60e05161010043038112610155574381101561015557408152505b565b5f80fd8558205faae5cedd8d4098fc2e7250264ec7db93b093963b3583c968a0353969b14b2d1901598000a1657679706572830004010035

Standard JSON Input

The standard JSON input file can be retrieved via:

vyper -f solc_json block_hash_oracle.vy > block_hash_oracle.json
{
  "language": "Vyper",
  "sources": {
    "venv/Lib/site-packages/snekmate/utils/block_hash.vy": {
      "content": "# pragma version ~=0.4.1\n\"\"\"\n@title Utility Functions to Access Historical Block Hashes\n@custom:contract-name block_hash\n@license GNU Affero General Public License v3.0 only\n@author pcaversaccio\n@notice These functions can be used to access the historical block\n        hashes beyond the default 256-block limit. We use the EIP-2935\n        (https://eips.ethereum.org/EIPS/eip-2935) history contract,\n        which maintains a ring buffer of the last 8,191 block hashes\n        stored in state. For the blocks within the last 256 blocks,\n        we use the native `BLOCKHASH` opcode. For blocks between 257\n        and 8,191 blocks ago, the function `_block_hash` queries via\n        the specified `get` (https://eips.ethereum.org/EIPS/eip-2935#get)\n        method the EIP-2935 history contract. For blocks older than\n        8,191 or future blocks (including the current one), we return\n        zero, matching the `BLOCKHASH` behaviour.\n\n        Please note that after EIP-2935 is activated, it takes 8,191\n        blocks to fully populate the history. Before that, only block\n        hashes from the fork block onward are available.\n\"\"\"\n\n\n# @dev The `HISTORY_STORAGE_ADDRESS` contract address.\n# @notice See the EIP-2935 specifications here: https://eips.ethereum.org/EIPS/eip-2935#specification.\n_HISTORY_STORAGE_ADDRESS: constant(address) = 0x0000F90827F1C53a10cb7A02335B175320002935\n\n\n# @dev The `keccak256` hash of the runtime bytecode of the\n# history contract deployed at `HISTORY_STORAGE_ADDRESS`.\n_HISTORY_STORAGE_RUNTIME_BYTECODE_HASH: constant(bytes32) = (\n    0x6e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d\n)\n\n\n@deploy\n@payable\ndef __init__():\n    \"\"\"\n    @dev To omit the opcodes for checking the `msg.value`\n         in the creation-time EVM bytecode, the constructor\n         is declared as `payable`.\n    \"\"\"\n    pass\n\n\n@internal\n@view\ndef _block_hash(block_number: uint256) -> bytes32:\n    \"\"\"\n    @dev Returns the block hash for block number `block_number`.\n    @notice For blocks older than 8,191 or future blocks (including\n            the current one), returns zero, matching the `BLOCKHASH`\n            behaviour. Furthermore, this function does verify if the\n            history contract is deployed. If the history contract is\n            undeployed, the function will fallback to the `BLOCKHASH`\n            behaviour.\n    @param block_number The 32-byte block number.\n    @return bytes32 The 32-byte block hash for block number `block_number`.\n    \"\"\"\n    # For future blocks (including the current one), we already return\n    # an empty `bytes32` value here in order not to iterate through the\n    # remaining code.\n    if block_number >= block.number:\n        return empty(bytes32)\n\n    delta: uint256 = unsafe_sub(block.number, block_number)\n\n    if delta <= 256:\n        return blockhash(block_number)\n    elif delta > 8191 or _HISTORY_STORAGE_ADDRESS.codehash != _HISTORY_STORAGE_RUNTIME_BYTECODE_HASH:\n        # The Vyper built-in function `blockhash` reverts if the block number\n        # is more than `256` blocks behind the current block. We explicitly\n        # handle this case (i.e. `delta > 8191`) to ensure the function returns\n        # an empty `bytes32` value rather than reverting (i.e. exactly matching\n        # the `BLOCKHASH` opcode behaviour).\n        return empty(bytes32)\n    else:\n        return self._get_history_storage(block_number)\n\n\n@internal\n@view\ndef _get_history_storage(block_number: uint256) -> bytes32:\n    \"\"\"\n    @dev Returns the block hash for block number `block_number` by\n         calling the `HISTORY_STORAGE_ADDRESS` contract address.\n    @notice Please note that for any request outside the range of\n            `[block.number - 8191, block.number - 1]`, this function\n            reverts (see https://eips.ethereum.org/EIPS/eip-2935#get).\n            Furthermore, this function does not verify if the history\n            contract is deployed. If the history contract is undeployed,\n            the function will return an empty `bytes32` value.\n    @param block_number The 32-byte block number.\n    @return bytes32 The 32-byte block hash for block number `block_number`.\n    \"\"\"\n    return convert(\n        raw_call(\n            _HISTORY_STORAGE_ADDRESS,\n            abi_encode(block_number),\n            max_outsize=32,\n            is_static_call=True,\n        ),\n        bytes32,\n    )\n",
      "sha256sum": "5c2da0de4147e4cc317c92ca0beb0ccf13f4485f35688e29832f34179decf02c"
    },
    "block_hash_oracle.vy": {
      "content": "# pragma version ~=0.4.1\n\"\"\"\n@title Historical Block Hashes Oracle\n@custom:contract-name block_hash_oracle\n@license GNU Affero General Public License v3.0 only\n@author pcaversaccio\n@notice The contract function `block_hash` can be used to access the\n        historical block hashes beyond the default 256-block limit.\n        We use the EIP-2935 (https://eips.ethereum.org/EIPS/eip-2935)\n        history contract, which maintains a ring buffer of the last\n        8,191 block hashes stored in state:\n        - For blocks within the last 256 blocks, the function `block_hash`\n          uses the native `BLOCKHASH` opcode.\n        - For blocks between 257 and 8,191 blocks ago, the function\n          `block_hash` queries the EIP-2935 history contract using the\n          specified `get` method: https://eips.ethereum.org/EIPS/eip-2935#get.\n        - For blocks older than 8,191 blocks or future blocks (including the\n          current one), it returns zero, matching the `BLOCKHASH` behavior.\n\n        Please note that after EIP-2935 is activated, it takes 8,191\n        blocks to fully populate the history. Before that, only block\n        hashes from the fork block onward are available.\n\"\"\"\n\nfrom snekmate.utils import block_hash as bh\n\n\n@external\n@view\ndef block_hash(block_number: uint256) -> bytes32:\n    \"\"\"\n    @dev Returns the block hash for block number `block_number`.\n    @notice For blocks older than 8,191 or future blocks (including\n            the current one), returns zero, matching the `BLOCKHASH`\n            behaviour. Furthermore, this function does verify if the\n            history contract is deployed. If the history contract is\n            undeployed, the function will fallback to the `BLOCKHASH`\n            behaviour.\n    @param block_number The 32-byte block number.\n    @return bytes32 The 32-byte block hash for block number `block_number`.\n    \"\"\"\n    return bh._block_hash(block_number)\n",
      "sha256sum": "c2d0fd1295f106bb1c081bba1992d27e4a7606eb98c75fcda22d96e69e779628"
    }
  },
  "settings": {
    "outputSelection": { "block_hash_oracle.vy": ["*"] },
    "search_paths": ["venv/Lib/site-packages", "venv", "."]
  },
  "compiler_version": "v0.4.1+commit.8a93dd27",
  "integrity": "5faae5cedd8d4098fc2e7250264ec7db93b093963b3583c968a0353969b14b2d"
}

Deployment via CreateX

Call deployCreate2(bytes32,bytes) with the arguments:

{
  "salt": "0xecc539188a01ae980fcdf7e561679dfda0b504b6e017309f2c72d8e6f182e3bf",
  "initCode": "0x61015961001161000039610159610000f35f3560e01c63b509f4ef81186100315760243610341761015557602060043560e05261002c61014061009a565b610140f35b5f5ffd5b5a6040516080526020606052606050602060c0606051608071f90827f1c53a10cb7a02335b17532000293585fa9050610070573d5f5f3e3d5ffd5b3d602081183d602010021860a05260a0805160200360031b6020820151811c811b90509050815250565b4360e051106100ac575f815250610153565b60e0514303610100526101006101005111156101385761200061010051101561010b577f6e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d71f90827f1c53a10cb7a02335b1753200029353f141561010e565b60015b61012f5760e051604052610123610120610035565b61012051815250610153565b5f815250610153565b60e05161010043038112610155574381101561015557408152505b565b5f80fd8558205faae5cedd8d4098fc2e7250264ec7db93b093963b3583c968a0353969b14b2d1901598000a1657679706572830004010035"
}

keccak256("Long Live Vyper!") ➜ 0xecc539188a01ae980fcdf7e561679dfda0b504b6e017309f2c72d8e6f182e3bf πŸ˜„.

Deployments

Testing

You can test it on e.g. Sepolia by running:

~$ export ETH_RPC_URL=https://ethereum-sepolia.rpc.subquery.network/public
~$ cast call 0x0e04bDF212088405D1EF8abE507F644a87c17a84 "block_hash(uint256)(bytes32)" $(($(cast block-number) - 1337))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment