From 8b96c259c82fa2d6d3b2f184e6af6ce4aa79f1bd Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 11:57:46 +0100 Subject: [PATCH 1/9] Add BlockHeader library for parsing and verifying RLP-encoded block headers --- contracts/mocks/Stateless.sol | 1 + contracts/utils/BlockHeader.sol | 196 ++++++++++++++++++++++++++++++++ contracts/utils/README.adoc | 3 + test/utils/BlockHeader.test.js | 71 ++++++++++++ 4 files changed, 271 insertions(+) create mode 100644 contracts/utils/BlockHeader.sol create mode 100644 test/utils/BlockHeader.test.js diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 0669c675248..73eae3f5b7c 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -12,6 +12,7 @@ import {Base58} from "../utils/Base58.sol"; import {Base64} from "../utils/Base64.sol"; import {BitMaps} from "../utils/structs/BitMaps.sol"; import {Blockhash} from "../utils/Blockhash.sol"; +import {BlockHeader} from "../utils/BlockHeader.sol"; import {Bytes} from "../utils/Bytes.sol"; import {CAIP2} from "../utils/CAIP2.sol"; import {CAIP10} from "../utils/CAIP10.sol"; diff --git a/contracts/utils/BlockHeader.sol b/contracts/utils/BlockHeader.sol new file mode 100644 index 00000000000..518c96ffc1d --- /dev/null +++ b/contracts/utils/BlockHeader.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.26; + +import {SafeCast} from "./math/SafeCast.sol"; +import {Blockhash} from "./Blockhash.sol"; +import {Memory} from "./Memory.sol"; +import {RLP} from "./RLP.sol"; + +/// @dev Library for parsing and verifying RLP-encoded block headers. +library BlockHeader { + using SafeCast for *; + using RLP for *; + + /// @dev List of evm versions. + enum Hardforks { + Homestead, + DAO, + TangerineWhistle, + SpuriousDragon, + Byzantium, + Constantinople, + Petersburg, + Istanbul, + MuirGlacier, + Berlin, + London, + ArrowGlacier, + GreyGlacier, + Paris, + Shanghai, + Cancun, + Prague, + Osaka, + Amsterdam + } + + /// @dev List of block header fields, in the order they are encoded in the block header RLP. + enum HeaderField { + ParentHash, // Since Homestead + OmmersHash, // Since Homestead + Coinbase, // Since Homestead + StateRoot, // Since Homestead + TransactionsRoot, // Since Homestead + ReceiptsRoot, // Since Homestead + LogsBloom, // Since Homestead + Difficulty, // Since Homestead + Number, // Since Homestead + GasLimit, // Since Homestead + GasUsed, // Since Homestead + Timestamp, // Since Homestead + ExtraData, // Since Homestead + PrevRandao, // Since Homestead (called MixHash before Paris) + Nonce, // Since Homestead + BaseFeePerGas, // Since London + WithdrawalsRoot, // Since Shanghai + BlobGasUsed, // Since Cancun + ExcessBlobGas, // Since Cancun + ParentBeaconBlockRoot, // Since Cancun + RequestsHash, // Since Prague + BlockAccessListHash // Since Amsterdam + } + + /// @dev Thrown when the provided block header RLP does not have the expected number of fields for the given hardfork version. + error InvalidBlockHeader(Hardforks expectedVersion); + + /// @dev Verifies that the given block header RLP corresponds to a valid block header for the current chain. + function verifyBlockHeader(bytes memory headerRLP) internal view returns (bool) { + return Blockhash.blockHash(getNumber(headerRLP)) == keccak256(headerRLP); + } + + /// @dev Extract the parent hash from the block header RLP. + function getParentHash(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.ParentHash)].readBytes32(); + } + + /// @dev Extract the ommers hash from the block header RLP. This is constant to keccak256(rlp([])) since EIP-3675 (Paris) + function getOmmersHash(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.OmmersHash)].readBytes32(); + } + + /// @dev Extract the coinbase (a.k.a. beneficiary or miner) address from the block header RLP. + function getCoinbase(bytes memory headerRLP) internal pure returns (address) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Coinbase)].readAddress(); + } + + /// @dev Extract the state root from the block header RLP. + function getStateRoot(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.StateRoot)].readBytes32(); + } + + /// @dev Extract the transactions root from the block header RLP. + function getTransactionsRoot(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.TransactionsRoot)].readBytes32(); + } + + /// @dev Extract the receipts root from the block header RLP. + function getReceiptsRoot(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.ReceiptsRoot)].readBytes32(); + } + + /// @dev Extract the logs bloom from the block header RLP. + function getLogsBloom(bytes memory headerRLP) internal pure returns (bytes memory) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.LogsBloom)].readBytes(); + } + + /// @dev Extract the difficulty from the block header RLP. This is constant to 0 since EIP-3675 (Paris) + function getDifficulty(bytes memory headerRLP) internal pure returns (uint256) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Difficulty)].readUint256(); + } + + /// @dev Extract the block number from the block header RLP. + function getNumber(bytes memory headerRLP) internal pure returns (uint256) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Number)].readUint256(); + } + + /// @dev Extract the gas used from the block header RLP. + function getGasUsed(bytes memory headerRLP) internal pure returns (uint256) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.GasUsed)].readUint256(); + } + + /// @dev Extract the gas limit from the block header RLP. + function getGasLimit(bytes memory headerRLP) internal pure returns (uint256) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.GasLimit)].readUint256(); + } + + /// @dev Extract the timestamp from the block header RLP. + function getTimestamp(bytes memory headerRLP) internal pure returns (uint256) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Timestamp)].readUint256(); + } + + /// @dev Extract the extra data from the block header RLP. + function getExtraData(bytes memory headerRLP) internal pure returns (bytes memory) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.ExtraData)].readBytes(); + } + + /// @dev Extract the prevRandao (a.k.a. mixHash before Paris) from the block header RLP. + function getPrevRandao(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.PrevRandao)].readBytes32(); + } + + /// @dev Extract the nonce from the block header RLP. This is constant to 0 since EIP-3675 (Paris) + function getNonce(bytes memory headerRLP) internal pure returns (bytes8) { + return bytes8(_parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Nonce)].readBytes32()); + } + + /// @dev Extract the base fee per gas from the block header RLP. This was introduced in London. + function getBaseFeePerGas(bytes memory headerRLP) internal pure returns (uint256) { + return _parseHeader(headerRLP, Hardforks.London)[uint8(HeaderField.BaseFeePerGas)].readUint256(); + } + + /// @dev Extract the withdrawals root from the block header RLP. This was introduced in Shanghai. + function getWithdrawalsRoot(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Shanghai)[uint8(HeaderField.WithdrawalsRoot)].readBytes32(); + } + + /// @dev Extract the blob gas used from the block header RLP. This was introduced in Cancun. + function getBlobGasUsed(bytes memory headerRLP) internal pure returns (uint64) { + return _parseHeader(headerRLP, Hardforks.Cancun)[uint8(HeaderField.BlobGasUsed)].readUint256().toUint64(); + } + + /// @dev Extract the excess blob gas from the block header RLP. This was introduced in Cancun. + function getExcessBlobGas(bytes memory headerRLP) internal pure returns (uint64) { + return _parseHeader(headerRLP, Hardforks.Cancun)[uint8(HeaderField.ExcessBlobGas)].readUint256().toUint64(); + } + + /// @dev Extract the parent beacon block root from the block header RLP. This was introduced in Cancun. + function getParentBeaconBlockRoot(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Cancun)[uint8(HeaderField.ParentBeaconBlockRoot)].readBytes32(); + } + + /// @dev Extract the requests hash from the block header RLP. This was introduced in Prague. + function getRequestsHash(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Prague)[uint8(HeaderField.RequestsHash)].readBytes32(); + } + + /// @dev Extract the block access list hash from the block header RLP. This will be introduced in Amsterdam. + function getBlockAccessListHash(bytes memory headerRLP) internal pure returns (bytes32) { + return _parseHeader(headerRLP, Hardforks.Amsterdam)[uint8(HeaderField.BlockAccessListHash)].readBytes32(); + } + + /// @dev Internal function to parse the block header RLP and return the list of fields as memory slices. + /// It also checks that the number of fields is at least the expected number for the given hardfork version. + function _parseHeader(bytes memory headerRLP, Hardforks version) private pure returns (Memory.Slice[] memory) { + Memory.Slice[] memory fields = RLP.decodeList(headerRLP); + require(fields.length >= _expectedHeadersLength(version), InvalidBlockHeader(version)); + return fields; + } + + /// @dev Internal function to return the expected number of fields in the block header RLP for a given hardfork version. + function _expectedHeadersLength(Hardforks version) private pure returns (uint8 count) { + assembly ("memory-safe") { + count := byte(version, 0x0F0F0F0F0F0F0F0F0F0F10101010111415151600000000000000000000000000) + } + } +} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index d6b0f720f3e..be9be722f42 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -27,6 +27,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Base58}: On-chain base58 encoding and decoding. * {Base64}: On-chain base64 and base64URL encoding according to https://datatracker.ietf.org/doc/html/rfc4648[RFC-4648]. * {Blockhash}: A library for accessing historical block hashes beyond the standard 256 block limit utilizing EIP-2935's historical blockhash functionality. + * {BlockHeader}: A library for parsing and verifying RLP-encoded block headers. * {Bytes}: Common operations on bytes objects. * {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. * {Calldata}: Helpers for manipulating calldata. @@ -118,6 +119,8 @@ Ethereum contracts have no native concept of an interface, so applications must {{Blockhash}} +{{BlockHeader}} + {{Bytes}} {{CAIP10}} diff --git a/test/utils/BlockHeader.test.js b/test/utils/BlockHeader.test.js new file mode 100644 index 00000000000..d3b9369e359 --- /dev/null +++ b/test/utils/BlockHeader.test.js @@ -0,0 +1,71 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +async function fixture() { + return { + mock: await ethers.deployContract('$BlockHeader'), + }; +} + +describe('BlockHeader', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + for (const block of [ + { + _version: 'osaka', + _rlpEncoded: + '0xf90281a031d473c19b8e0d89e0546d057506d9dea042cd1492ef90682134e6027cf8669ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794dadb0d80178819f2319190d340ce9a924f783711a05ac60aff31a075d3d53eea5b040cc8f753bde9e107558eec5fe17df8bc110b12a08d49818fb50fbdf89c471f303685b1bdc506d824b5edc291f74a234f74a73758a0fdb89e5c12a2dca6f1d213dd82d69ec07afa07a3e1aeb76f9aac2c3a0a98f01cb90100fffffff7ffffff7ffeffffffffffffbbff7fff7edfb7fffffffffdfffffbefffb7dffdfffffffffffbf7ffffdffff7ffffffffffffbffffffffffffffffffffffeffbfffffffeffffffefffffffffdfffffffffffffffffffffffffffffffefffffffffffffffffffffffffffef7fffff7feffffffffffffbfffffff7fffffbffffffffffffffffffffffffffffff76ffefffffff7fff7bffffffffdfff7fffffffffffffbfffffefef7fffdbffffdffffefceffffffffffffffffbffdfffffffffffefffffffbffbfffffffff7fbf7ffffffffffeffffdfffffdffffffeffffffffffff7fff77ffffffffffffffffffffffffeb7fffffffffffff7ff7ffffff80840177396d84039386c784037dbb0e8469a95c3b934275696c6465724e6574202842656176657229a0f10bbd059504a06792efabaaca4af22f285ce028d54a60f9a65ea25db70eed2d8800000000000000008408342539a06bb47e5be135671fde5284a24b5f5dd933f90bb7fdb22aa764f120bdc55d164f83200000840af13e4da066573e435a75fed7dfab9b037bb87bfa2ca01e7a4914c7178145e8a9d1eba444a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + hash: '0x0f23d6dee77755efe485b0870d820b1aae8cfe689d767ce7d10b6afa2b1ef14d', + parentHash: '0x31d473c19b8e0d89e0546d057506d9dea042cd1492ef90682134e6027cf8669c', + sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + miner: '0xdadb0d80178819f2319190d340ce9a924f783711', + stateRoot: '0x5ac60aff31a075d3d53eea5b040cc8f753bde9e107558eec5fe17df8bc110b12', + transactionsRoot: '0x8d49818fb50fbdf89c471f303685b1bdc506d824b5edc291f74a234f74a73758', + receiptsRoot: '0xfdb89e5c12a2dca6f1d213dd82d69ec07afa07a3e1aeb76f9aac2c3a0a98f01c', + logsBloom: + '0xfffffff7ffffff7ffeffffffffffffbbff7fff7edfb7fffffffffdfffffbefffb7dffdfffffffffffbf7ffffdffff7ffffffffffffbffffffffffffffffffffffeffbfffffffeffffffefffffffffdfffffffffffffffffffffffffffffffefffffffffffffffffffffffffffef7fffff7feffffffffffffbfffffff7fffffbffffffffffffffffffffffffffffff76ffefffffff7fff7bffffffffdfff7fffffffffffffbfffffefef7fffdbffffdffffefceffffffffffffffffbffdfffffffffffefffffffbffbfffffffff7fbf7ffffffffffeffffdfffffdffffffeffffffffffff7fff77ffffffffffffffffffffffffeb7fffffffffffff7ff7ffffff', + difficulty: '0x0', + number: '0x177396d', + gasLimit: '0x39386c7', + gasUsed: '0x37dbb0e', + timestamp: '0x69a95c3b', + extraData: '0x4275696c6465724e6574202842656176657229', + mixHash: '0xf10bbd059504a06792efabaaca4af22f285ce028d54a60f9a65ea25db70eed2d', + nonce: '0x0000000000000000', + baseFeePerGas: '0x8342539', + withdrawalsRoot: '0x6bb47e5be135671fde5284a24b5f5dd933f90bb7fdb22aa764f120bdc55d164f', + blobGasUsed: '0x200000', + excessBlobGas: '0xaf13e4d', + parentBeaconBlockRoot: '0x66573e435a75fed7dfab9b037bb87bfa2ca01e7a4914c7178145e8a9d1eba444', + requestsHash: '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + ]) { + it(`should parse block #${Number(block.number)} correctly`, async function () { + const headerRLP = block._rlpEncoded; + await expect(this.mock.$getParentHash(headerRLP)).to.eventually.equal(block.parentHash); + await expect(this.mock.$getOmmersHash(headerRLP)).to.eventually.equal(block.sha3Uncles); + await expect(this.mock.$getCoinbase(headerRLP)).to.eventually.equal(ethers.getAddress(block.miner)); + await expect(this.mock.$getStateRoot(headerRLP)).to.eventually.equal(block.stateRoot); + await expect(this.mock.$getTransactionsRoot(headerRLP)).to.eventually.equal(block.transactionsRoot); + await expect(this.mock.$getReceiptsRoot(headerRLP)).to.eventually.equal(block.receiptsRoot); + await expect(this.mock.$getLogsBloom(headerRLP)).to.eventually.equal(block.logsBloom); + await expect(this.mock.$getDifficulty(headerRLP)).to.eventually.equal(block.difficulty); + await expect(this.mock.$getNumber(headerRLP)).to.eventually.equal(block.number); + await expect(this.mock.$getGasLimit(headerRLP)).to.eventually.equal(block.gasLimit); + await expect(this.mock.$getGasUsed(headerRLP)).to.eventually.equal(block.gasUsed); + await expect(this.mock.$getTimestamp(headerRLP)).to.eventually.equal(block.timestamp); + await expect(this.mock.$getExtraData(headerRLP)).to.eventually.equal(block.extraData); + await expect(this.mock.$getPrevRandao(headerRLP)).to.eventually.equal(block.mixHash); + await expect(this.mock.$getNonce(headerRLP)).to.eventually.equal(block.nonce); + await expect(this.mock.$getBaseFeePerGas(headerRLP)).to.eventually.equal(block.baseFeePerGas); + await expect(this.mock.$getWithdrawalsRoot(headerRLP)).to.eventually.equal(block.withdrawalsRoot); + await expect(this.mock.$getBlobGasUsed(headerRLP)).to.eventually.equal(block.blobGasUsed); + await expect(this.mock.$getExcessBlobGas(headerRLP)).to.eventually.equal(block.excessBlobGas); + await expect(this.mock.$getParentBeaconBlockRoot(headerRLP)).to.eventually.equal(block.parentBeaconBlockRoot); + await expect(this.mock.$getRequestsHash(headerRLP)).to.eventually.equal(block.requestsHash); + }); + } +}); From 1df4cb0116bf0afddbf2240c020e4b4adedb00c6 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 12:24:21 +0100 Subject: [PATCH 2/9] update tests --- test/utils/BlockHeader.test.js | 179 ++++++++++++++++++++++----------- 1 file changed, 123 insertions(+), 56 deletions(-) diff --git a/test/utils/BlockHeader.test.js b/test/utils/BlockHeader.test.js index d3b9369e359..48d637362a1 100644 --- a/test/utils/BlockHeader.test.js +++ b/test/utils/BlockHeader.test.js @@ -1,6 +1,33 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); + +const clean = hex => (hex === '0x0' ? '0x' : ethers.toBeHex(hex)); + +const rlpEncodeBlock = block => + ethers.encodeRlp([ + block.parentHash, + block.sha3Uncles, + block.miner, + block.stateRoot, + block.transactionsRoot, + block.receiptsRoot, + block.logsBloom, + clean(block.difficulty), + clean(block.number), + clean(block.gasLimit), + clean(block.gasUsed), + clean(block.timestamp), + block.extraData, + block.mixHash, + block.nonce, + clean(block.baseFeePerGas), + block.withdrawalsRoot, + clean(block.blobGasUsed), + clean(block.excessBlobGas), + block.parentBeaconBlockRoot, + block.requestsHash, + ]); async function fixture() { return { @@ -13,59 +40,99 @@ describe('BlockHeader', function () { Object.assign(this, await loadFixture(fixture)); }); - for (const block of [ - { - _version: 'osaka', - _rlpEncoded: - '0xf90281a031d473c19b8e0d89e0546d057506d9dea042cd1492ef90682134e6027cf8669ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794dadb0d80178819f2319190d340ce9a924f783711a05ac60aff31a075d3d53eea5b040cc8f753bde9e107558eec5fe17df8bc110b12a08d49818fb50fbdf89c471f303685b1bdc506d824b5edc291f74a234f74a73758a0fdb89e5c12a2dca6f1d213dd82d69ec07afa07a3e1aeb76f9aac2c3a0a98f01cb90100fffffff7ffffff7ffeffffffffffffbbff7fff7edfb7fffffffffdfffffbefffb7dffdfffffffffffbf7ffffdffff7ffffffffffffbffffffffffffffffffffffeffbfffffffeffffffefffffffffdfffffffffffffffffffffffffffffffefffffffffffffffffffffffffffef7fffff7feffffffffffffbfffffff7fffffbffffffffffffffffffffffffffffff76ffefffffff7fff7bffffffffdfff7fffffffffffffbfffffefef7fffdbffffdffffefceffffffffffffffffbffdfffffffffffefffffffbffbfffffffff7fbf7ffffffffffeffffdfffffdffffffeffffffffffff7fff77ffffffffffffffffffffffffeb7fffffffffffff7ff7ffffff80840177396d84039386c784037dbb0e8469a95c3b934275696c6465724e6574202842656176657229a0f10bbd059504a06792efabaaca4af22f285ce028d54a60f9a65ea25db70eed2d8800000000000000008408342539a06bb47e5be135671fde5284a24b5f5dd933f90bb7fdb22aa764f120bdc55d164f83200000840af13e4da066573e435a75fed7dfab9b037bb87bfa2ca01e7a4914c7178145e8a9d1eba444a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - hash: '0x0f23d6dee77755efe485b0870d820b1aae8cfe689d767ce7d10b6afa2b1ef14d', - parentHash: '0x31d473c19b8e0d89e0546d057506d9dea042cd1492ef90682134e6027cf8669c', - sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', - miner: '0xdadb0d80178819f2319190d340ce9a924f783711', - stateRoot: '0x5ac60aff31a075d3d53eea5b040cc8f753bde9e107558eec5fe17df8bc110b12', - transactionsRoot: '0x8d49818fb50fbdf89c471f303685b1bdc506d824b5edc291f74a234f74a73758', - receiptsRoot: '0xfdb89e5c12a2dca6f1d213dd82d69ec07afa07a3e1aeb76f9aac2c3a0a98f01c', - logsBloom: - '0xfffffff7ffffff7ffeffffffffffffbbff7fff7edfb7fffffffffdfffffbefffb7dffdfffffffffffbf7ffffdffff7ffffffffffffbffffffffffffffffffffffeffbfffffffeffffffefffffffffdfffffffffffffffffffffffffffffffefffffffffffffffffffffffffffef7fffff7feffffffffffffbfffffff7fffffbffffffffffffffffffffffffffffff76ffefffffff7fff7bffffffffdfff7fffffffffffffbfffffefef7fffdbffffdffffefceffffffffffffffffbffdfffffffffffefffffffbffbfffffffff7fbf7ffffffffffeffffdfffffdffffffeffffffffffff7fff77ffffffffffffffffffffffffeb7fffffffffffff7ff7ffffff', - difficulty: '0x0', - number: '0x177396d', - gasLimit: '0x39386c7', - gasUsed: '0x37dbb0e', - timestamp: '0x69a95c3b', - extraData: '0x4275696c6465724e6574202842656176657229', - mixHash: '0xf10bbd059504a06792efabaaca4af22f285ce028d54a60f9a65ea25db70eed2d', - nonce: '0x0000000000000000', - baseFeePerGas: '0x8342539', - withdrawalsRoot: '0x6bb47e5be135671fde5284a24b5f5dd933f90bb7fdb22aa764f120bdc55d164f', - blobGasUsed: '0x200000', - excessBlobGas: '0xaf13e4d', - parentBeaconBlockRoot: '0x66573e435a75fed7dfab9b037bb87bfa2ca01e7a4914c7178145e8a9d1eba444', - requestsHash: '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - }, - ]) { - it(`should parse block #${Number(block.number)} correctly`, async function () { - const headerRLP = block._rlpEncoded; - await expect(this.mock.$getParentHash(headerRLP)).to.eventually.equal(block.parentHash); - await expect(this.mock.$getOmmersHash(headerRLP)).to.eventually.equal(block.sha3Uncles); - await expect(this.mock.$getCoinbase(headerRLP)).to.eventually.equal(ethers.getAddress(block.miner)); - await expect(this.mock.$getStateRoot(headerRLP)).to.eventually.equal(block.stateRoot); - await expect(this.mock.$getTransactionsRoot(headerRLP)).to.eventually.equal(block.transactionsRoot); - await expect(this.mock.$getReceiptsRoot(headerRLP)).to.eventually.equal(block.receiptsRoot); - await expect(this.mock.$getLogsBloom(headerRLP)).to.eventually.equal(block.logsBloom); - await expect(this.mock.$getDifficulty(headerRLP)).to.eventually.equal(block.difficulty); - await expect(this.mock.$getNumber(headerRLP)).to.eventually.equal(block.number); - await expect(this.mock.$getGasLimit(headerRLP)).to.eventually.equal(block.gasLimit); - await expect(this.mock.$getGasUsed(headerRLP)).to.eventually.equal(block.gasUsed); - await expect(this.mock.$getTimestamp(headerRLP)).to.eventually.equal(block.timestamp); - await expect(this.mock.$getExtraData(headerRLP)).to.eventually.equal(block.extraData); - await expect(this.mock.$getPrevRandao(headerRLP)).to.eventually.equal(block.mixHash); - await expect(this.mock.$getNonce(headerRLP)).to.eventually.equal(block.nonce); - await expect(this.mock.$getBaseFeePerGas(headerRLP)).to.eventually.equal(block.baseFeePerGas); - await expect(this.mock.$getWithdrawalsRoot(headerRLP)).to.eventually.equal(block.withdrawalsRoot); - await expect(this.mock.$getBlobGasUsed(headerRLP)).to.eventually.equal(block.blobGasUsed); - await expect(this.mock.$getExcessBlobGas(headerRLP)).to.eventually.equal(block.excessBlobGas); - await expect(this.mock.$getParentBeaconBlockRoot(headerRLP)).to.eventually.equal(block.parentBeaconBlockRoot); - await expect(this.mock.$getRequestsHash(headerRLP)).to.eventually.equal(block.requestsHash); - }); - } + it('current block', async function () { + const block = await ethers.provider.send('eth_getBlockByNumber', ['latest', false]); + const headerRLP = rlpEncodeBlock(block); + + // encoding sanity check + expect(ethers.keccak256(headerRLP)).to.equal(block.hash); + + // validate inclusion + await mine(1); // ensure blockhash is available + await expect(this.mock.$verifyBlockHeader(headerRLP)).to.eventually.be.true; + + // parsing check + await expect(this.mock.$getParentHash(headerRLP)).to.eventually.equal(block.parentHash); + await expect(this.mock.$getOmmersHash(headerRLP)).to.eventually.equal(block.sha3Uncles); + await expect(this.mock.$getCoinbase(headerRLP)).to.eventually.equal(ethers.getAddress(block.miner)); + await expect(this.mock.$getStateRoot(headerRLP)).to.eventually.equal(block.stateRoot); + await expect(this.mock.$getTransactionsRoot(headerRLP)).to.eventually.equal(block.transactionsRoot); + await expect(this.mock.$getReceiptsRoot(headerRLP)).to.eventually.equal(block.receiptsRoot); + await expect(this.mock.$getLogsBloom(headerRLP)).to.eventually.equal(block.logsBloom); + await expect(this.mock.$getDifficulty(headerRLP)).to.eventually.equal(block.difficulty); + await expect(this.mock.$getNumber(headerRLP)).to.eventually.equal(block.number); + await expect(this.mock.$getGasLimit(headerRLP)).to.eventually.equal(block.gasLimit); + await expect(this.mock.$getGasUsed(headerRLP)).to.eventually.equal(block.gasUsed); + await expect(this.mock.$getTimestamp(headerRLP)).to.eventually.equal(block.timestamp); + await expect(this.mock.$getExtraData(headerRLP)).to.eventually.equal(block.extraData); + await expect(this.mock.$getPrevRandao(headerRLP)).to.eventually.equal(block.mixHash); + await expect(this.mock.$getNonce(headerRLP)).to.eventually.equal(block.nonce); + await expect(this.mock.$getBaseFeePerGas(headerRLP)).to.eventually.equal(block.baseFeePerGas); + await expect(this.mock.$getWithdrawalsRoot(headerRLP)).to.eventually.equal(block.withdrawalsRoot); + await expect(this.mock.$getBlobGasUsed(headerRLP)).to.eventually.equal(block.blobGasUsed); + await expect(this.mock.$getExcessBlobGas(headerRLP)).to.eventually.equal(block.excessBlobGas); + await expect(this.mock.$getParentBeaconBlockRoot(headerRLP)).to.eventually.equal(block.parentBeaconBlockRoot); + await expect(this.mock.$getRequestsHash(headerRLP)).to.eventually.equal(block.requestsHash); + }); + + describe('historical blocks', function () { + for (const block of [ + { + _version: 'osaka', + hash: '0x0f23d6dee77755efe485b0870d820b1aae8cfe689d767ce7d10b6afa2b1ef14d', + parentHash: '0x31d473c19b8e0d89e0546d057506d9dea042cd1492ef90682134e6027cf8669c', + sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + miner: '0xdadb0d80178819f2319190d340ce9a924f783711', + stateRoot: '0x5ac60aff31a075d3d53eea5b040cc8f753bde9e107558eec5fe17df8bc110b12', + transactionsRoot: '0x8d49818fb50fbdf89c471f303685b1bdc506d824b5edc291f74a234f74a73758', + receiptsRoot: '0xfdb89e5c12a2dca6f1d213dd82d69ec07afa07a3e1aeb76f9aac2c3a0a98f01c', + logsBloom: + '0xfffffff7ffffff7ffeffffffffffffbbff7fff7edfb7fffffffffdfffffbefffb7dffdfffffffffffbf7ffffdffff7ffffffffffffbffffffffffffffffffffffeffbfffffffeffffffefffffffffdfffffffffffffffffffffffffffffffefffffffffffffffffffffffffffef7fffff7feffffffffffffbfffffff7fffffbffffffffffffffffffffffffffffff76ffefffffff7fff7bffffffffdfff7fffffffffffffbfffffefef7fffdbffffdffffefceffffffffffffffffbffdfffffffffffefffffffbffbfffffffff7fbf7ffffffffffeffffdfffffdffffffeffffffffffff7fff77ffffffffffffffffffffffffeb7fffffffffffff7ff7ffffff', + difficulty: '0x0', + number: '0x177396d', + gasLimit: '0x39386c7', + gasUsed: '0x37dbb0e', + timestamp: '0x69a95c3b', + extraData: '0x4275696c6465724e6574202842656176657229', + mixHash: '0xf10bbd059504a06792efabaaca4af22f285ce028d54a60f9a65ea25db70eed2d', + nonce: '0x0000000000000000', + baseFeePerGas: '0x8342539', + withdrawalsRoot: '0x6bb47e5be135671fde5284a24b5f5dd933f90bb7fdb22aa764f120bdc55d164f', + blobGasUsed: '0x200000', + excessBlobGas: '0xaf13e4d', + parentBeaconBlockRoot: '0x66573e435a75fed7dfab9b037bb87bfa2ca01e7a4914c7178145e8a9d1eba444', + requestsHash: '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + ]) { + it(`should parse block #${Number(block.number)} correctly`, async function () { + const headerRLP = rlpEncodeBlock(block); + + // encoding sanity check + expect(ethers.keccak256(headerRLP)).to.equal(block.hash); + + // parsing check + await expect(this.mock.$getParentHash(headerRLP)).to.eventually.equal(block.parentHash); + await expect(this.mock.$getOmmersHash(headerRLP)).to.eventually.equal(block.sha3Uncles); + await expect(this.mock.$getCoinbase(headerRLP)).to.eventually.equal(ethers.getAddress(block.miner)); + await expect(this.mock.$getStateRoot(headerRLP)).to.eventually.equal(block.stateRoot); + await expect(this.mock.$getTransactionsRoot(headerRLP)).to.eventually.equal(block.transactionsRoot); + await expect(this.mock.$getReceiptsRoot(headerRLP)).to.eventually.equal(block.receiptsRoot); + await expect(this.mock.$getLogsBloom(headerRLP)).to.eventually.equal(block.logsBloom); + await expect(this.mock.$getDifficulty(headerRLP)).to.eventually.equal(block.difficulty); + await expect(this.mock.$getNumber(headerRLP)).to.eventually.equal(block.number); + await expect(this.mock.$getGasLimit(headerRLP)).to.eventually.equal(block.gasLimit); + await expect(this.mock.$getGasUsed(headerRLP)).to.eventually.equal(block.gasUsed); + await expect(this.mock.$getTimestamp(headerRLP)).to.eventually.equal(block.timestamp); + await expect(this.mock.$getExtraData(headerRLP)).to.eventually.equal(block.extraData); + await expect(this.mock.$getPrevRandao(headerRLP)).to.eventually.equal(block.mixHash); + await expect(this.mock.$getNonce(headerRLP)).to.eventually.equal(block.nonce); + await expect(this.mock.$getBaseFeePerGas(headerRLP)).to.eventually.equal(block.baseFeePerGas); + await expect(this.mock.$getWithdrawalsRoot(headerRLP)).to.eventually.equal(block.withdrawalsRoot); + await expect(this.mock.$getBlobGasUsed(headerRLP)).to.eventually.equal(block.blobGasUsed); + await expect(this.mock.$getExcessBlobGas(headerRLP)).to.eventually.equal(block.excessBlobGas); + await expect(this.mock.$getParentBeaconBlockRoot(headerRLP)).to.eventually.equal(block.parentBeaconBlockRoot); + await expect(this.mock.$getRequestsHash(headerRLP)).to.eventually.equal(block.requestsHash); + }); + } + }); }); From 0a37a291fca4d97231b58755135acbdc1bc02614 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 14:00:27 +0100 Subject: [PATCH 3/9] testing against block headers of different format --- contracts/utils/BlockHeader.sol | 2 +- test/utils/BlockHeader.test.js | 211 +++++++++++++++++++++++++------- 2 files changed, 166 insertions(+), 47 deletions(-) diff --git a/contracts/utils/BlockHeader.sol b/contracts/utils/BlockHeader.sol index 518c96ffc1d..062a8ab4ae1 100644 --- a/contracts/utils/BlockHeader.sol +++ b/contracts/utils/BlockHeader.sol @@ -141,7 +141,7 @@ library BlockHeader { /// @dev Extract the nonce from the block header RLP. This is constant to 0 since EIP-3675 (Paris) function getNonce(bytes memory headerRLP) internal pure returns (bytes8) { - return bytes8(_parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Nonce)].readBytes32()); + return bytes8(_parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Nonce)].readUint256().toUint64()); } /// @dev Extract the base fee per gas from the block header RLP. This was introduced in London. diff --git a/test/utils/BlockHeader.test.js b/test/utils/BlockHeader.test.js index 48d637362a1..1a82a2847ab 100644 --- a/test/utils/BlockHeader.test.js +++ b/test/utils/BlockHeader.test.js @@ -1,33 +1,57 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); +const { Enum } = require('../helpers/enums'); -const clean = hex => (hex === '0x0' ? '0x' : ethers.toBeHex(hex)); +const Hardforks = Enum( + 'Homestead', + 'DAO', + 'TangerineWhistle', + 'SpuriousDragon', + 'Byzantium', + 'Constantinople', + 'Petersburg', + 'Istanbul', + 'MuirGlacier', + 'Berlin', + 'London', + 'ArrowGlacier', + 'GreyGlacier', + 'Paris', + 'Shanghai', + 'Cancun', + 'Prague', + 'Osaka', + 'Amsterdam', +); +const sanitize = hex => (hex === undefined ? undefined : hex === '0x0' ? '0x' : ethers.toBeHex(hex)); const rlpEncodeBlock = block => - ethers.encodeRlp([ - block.parentHash, - block.sha3Uncles, - block.miner, - block.stateRoot, - block.transactionsRoot, - block.receiptsRoot, - block.logsBloom, - clean(block.difficulty), - clean(block.number), - clean(block.gasLimit), - clean(block.gasUsed), - clean(block.timestamp), - block.extraData, - block.mixHash, - block.nonce, - clean(block.baseFeePerGas), - block.withdrawalsRoot, - clean(block.blobGasUsed), - clean(block.excessBlobGas), - block.parentBeaconBlockRoot, - block.requestsHash, - ]); + ethers.encodeRlp( + [ + block.parentHash, + block.sha3Uncles, + block.miner, + block.stateRoot, + block.transactionsRoot, + block.receiptsRoot, + block.logsBloom, + sanitize(block.difficulty), + sanitize(block.number), + sanitize(block.gasLimit), + sanitize(block.gasUsed), + sanitize(block.timestamp), + block.extraData, + block.mixHash, + block.nonce, + sanitize(block.baseFeePerGas), + block.withdrawalsRoot, + sanitize(block.blobGasUsed), + sanitize(block.excessBlobGas), + block.parentBeaconBlockRoot, + block.requestsHash, + ].filter(x => x !== undefined), + ); // filter out fields not present in older blocks async function fixture() { return { @@ -78,7 +102,95 @@ describe('BlockHeader', function () { describe('historical blocks', function () { for (const block of [ { - _version: 'osaka', + _version: Hardforks.Berlin, + hash: '0x244dd4312575b2c8846837086cf4f1669d026f1db376d1ad700ed77782c11b75', + parentHash: '0x9b2a40b705d5d806ede8764de1b628e43d98df93dfe779e5f8d9fdb51c6c3e5e', + sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + miner: '0xd224ca0c819e8e97ba0136b3b95ceff503b79f53', + stateRoot: '0x78cc9771fd6eb41a7bb869e5bc186fd42323cf352caa1d5a6088977c90e8f1af', + transactionsRoot: '0xa8615e34921ad24e27a437eafe0336c3d390d35ed8e04f5932ef32ddc19bcc6b', + receiptsRoot: '0xb4d7b1832dd9501bb975e74c16e7b22088c620f6afcd0509fc8016aaecf13e19', + logsBloom: + '0xd7b7fba609e4a0b8909bae66c339fe308802f55c009adeb842091c7e2ee296bc289738e45cb0b5d0cc4bdb175261a90bde166987edbab1fa63d0dbaec37cf98200c1e008b9009e12ce1196ab3ba86af49411e29dd7ec8110fd5281f0c3f9d5509bb689466fa79f8f4109dc06c900cef93721b67c15589e4468effd9ea010d8c8e802e1ba0ff12614c7421b4d276317b7b95f3fc1f7fa81fe69563d6a02993f9637fb08cd12aebdfabdd905c2d408d4291fe660b928035a8de171aeb95d5e00e1727d21a233f81063a492d322e5cc1d75ad68bf873f8f143c78ca90e709ba6ce58d18213b84057b51af510e202096e59435f4e86447feb8dd9cfc37068d836b64', + difficulty: '0x19824c721603ce', + number: '0xbc6140', + gasLimit: '0xe34a80', + gasUsed: '0xe300f7', + timestamp: '0x608cc067', + extraData: '0x7575706f6f6c2e636e2d32', + mixHash: '0x6b6af59fde87c4b4750bedc93f76a7c330087c11784a344fb3c7948308f05112', + nonce: '0xb3b2502a8c3de6f2', + }, + { + _version: Hardforks.London, + hash: '0xbd44f309a77f0eeca1f0fb8d0fee0663ca024cb46c2e6fa8f5eee77f0259b7d5', + parentHash: '0xa7d38804924d37bcc5dff91167ac797aa4721a2d4235f433f12598219b5eee93', + sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + miner: '0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c', + stateRoot: '0x938dffb3b445ae76f354166681a514cc274087734b2bd1e1b4d84c9eb3507ead', + transactionsRoot: '0x00ee8734e436cee128d0ade5fdab24b31ab9403c0a93951195dbee98822a169d', + receiptsRoot: '0xb227d8104c677943a50b33f142e9582bbc408d7e758cc4c4e8947f33f8665d65', + logsBloom: + '0x777673cfe7da9fd71084a5b3c37bbb77d22cf9f9f975f26238bfe5c29c67d57d499e95a7c6dafb7ed192d99ed59f9f4f1644c6dbffc7bf9bb6fe2ea9657e7e0fbecb78bf0f251d2ffcdb0abf117b6ff1bfacb27c97def1cdfbc4f66fd17137b2db1b7bbdbfd9476fd7f6dbbe0d24eada329cd779784c9efa7b2ff7b7f7fcce2978fde77bb35567738c53354b3b0fe69dc457fe3fbbf67a3d98f135ffc9172faaf31adfdba9ebed926e97e19fff31e2f79ade9b2dbae386dfc4f596ee5d4d5dd7319d876b6e0a9ded78c8c507f63964d6bb979bef9f2f34db4e73b2c31a0767f409bf763bdba1d0019ab55729d7ec90c4597c8f57e4903d5c67e6fa0756ee717f', + difficulty: '0x1fb6d0dbf2e1e5', + number: '0xca1d48', + gasLimit: '0x1ca35b6', + gasUsed: '0x1ca1799', + timestamp: '0x61450e8c', + extraData: '0xd883010a08846765746888676f312e31362e38856c696e7578', + mixHash: '0x2a71012cea7e7ef11d4e7bf39e5b395dbb372498922b942ab157e070f4723814', + nonce: '0x6fbf46080346a66a', + baseFeePerGas: '0xa9558cdb7', + }, + { + _version: Hardforks.Shanghai, + hash: '0x82b4ead321f2f3891e11990f109b9e17cc39a4ffd7aeb64b8e2c9ecad6a8dcdc', + parentHash: '0xa920c05e272e83f10ee97d8bd809986a9fa036d9e245682fce503ecb435b0bc1', + sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + miner: '0x1f9090aae28b8a3dceadf281b0f12828e676c326', + stateRoot: '0xeaf528d4e0b9f4355cac7c6ed22502a23672e3ace4cc9901c8aa0fca658e67c0', + transactionsRoot: '0x3765c52b95c470d869706b1c422938c5c86387846dbae3df8f5335a1c4f53d72', + receiptsRoot: '0xcafb926aaa7a4ebc4c7c234ec03e94c1e3eef8dba377c93d768f88351542dcec', + logsBloom: + '0x6c399420c38bf13d400012e08070d1b615890004c08555c49853408b12231c2acb0f2893a26930ad964cc6e4704f8d5ca2c32303af0637281634f50e01feb444642016aa41eab8bb78ab512f5ec89b2c8db1b01d0c6c380c009273e6983028506687286f4a82d1411059c55316a8ae6d667dda39a0218440c44000b174a933acf6c011d92a140149ac751c0507c0d0f6c5141b670942041b2db534404ab8854c8ab85d4218fbed4c8f9143c954b0c49cbcde74089160e57f0045052e548f27701ec24ef738a20fc94e24b9310926d86df14e414a07482d7507c50ca202fbec0002f9b4ed07248f7828c40a8289c5739984f09411108e8259814cb89bf02b2688', + difficulty: '0x0', + number: '0x11a1d48', + gasLimit: '0x1c9c380', + gasUsed: '0xdb8485', + timestamp: '0x65445eff', + extraData: '0x7273796e632d6275696c6465722e78797a', + mixHash: '0x51e2514f5ab8d09bca53a15d73996ac7b5389872e4f2b1b7277afeb3f1a9d559', + nonce: '0x0000000000000000', + baseFeePerGas: '0x3847759e4', + withdrawalsRoot: '0x114350f058bd9496a55fc98134dba4fa9aea36083246ae9ad7adcc9906e3eb58', + }, + { + _version: Hardforks.Cancun, + hash: '0xf98958f9c83103ec7f0d5f13cf2c36e9793d99413aa06943fad5a8743c055b29', + parentHash: '0xb37620f0875d897305dad1b7cbd043243880ad96acdb04d73f38b039796c821a', + sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + miner: '0x1f9090aae28b8a3dceadf281b0f12828e676c326', + stateRoot: '0x356e3c73d12ea1020b9bc022ec73d08e752e054e355df84cc20023bb5438c2f4', + transactionsRoot: '0x7db01aa19fcf9438293c5744cf7f81cb47f6fd8934bb38651b9328931988b72a', + receiptsRoot: '0x61aa2951a6bc6f94ec59c722b8fcf4a9cb4b71d3c5c06aec9848cdb6e8626691', + logsBloom: + '0x4dff21b3771d80875898d9f8c10a71bfb05162b5628cb6a09c81a9e7df8ba4c1e0361f25984035894a687b2450b38130de2588ab9921387df52924eb4d7e11c2ba7f75886a10a6acf999794fde671aa4d19856e175641870f9fc2860dfbeac5eef971134e75b41ee16d5d1c594a7ecbff31e494a4fdf8cde5ee11e17297dd621e819ff7c39def9108aee77c0a32a0a7d475d26032ddf2bfe0e94315c76b73e3dfbfac9605dd5a204973e39cd99a70aaf26b6a5f77924b25b8d674ddf64886b21d912b96b81031fc26e53308332df088f6380902aecb794390703630e3456b810513afdcf80516d9884a730e5b220cf3ca47c1de0d8bf34d15bba6508e21505df', + difficulty: '0x0', + number: '0x14a2d48', + gasLimit: '0x1ca35ef', + gasUsed: '0x1005a4c', + timestamp: '0x67893e93', + extraData: '0x407273796e636275696c646572', + mixHash: '0x680a819d98146baadc2ce3286f05074ef16c8c79ef2789bef8e5ab8a755c2b11', + nonce: '0x0000000000000000', + baseFeePerGas: '0x2015b8fcc', + withdrawalsRoot: '0xda6b506b0b73ff427edac248fd9a50d13574849b0aba8aae4c91c6a4657d17da', + blobGasUsed: '0xa0000', + excessBlobGas: '0x4d00000', + parentBeaconBlockRoot: '0x868fd48788b5a92d01ebb31bab47b997ce3f35e30ea4dd4055751d6309a71ff0', + }, + { + _version: Hardforks.Prague, hash: '0x0f23d6dee77755efe485b0870d820b1aae8cfe689d767ce7d10b6afa2b1ef14d', parentHash: '0x31d473c19b8e0d89e0546d057506d9dea042cd1492ef90682134e6027cf8669c', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', @@ -105,33 +217,40 @@ describe('BlockHeader', function () { }, ]) { it(`should parse block #${Number(block.number)} correctly`, async function () { + // Check helper (for old evm versions) + const check = (promise, value, version) => + value + ? expect(promise).to.eventually.equal(value) + : expect(promise).to.be.revertedWithCustomError(this.mock, 'InvalidBlockHeader').withArgs(version); + const headerRLP = rlpEncodeBlock(block); // encoding sanity check expect(ethers.keccak256(headerRLP)).to.equal(block.hash); // parsing check - await expect(this.mock.$getParentHash(headerRLP)).to.eventually.equal(block.parentHash); - await expect(this.mock.$getOmmersHash(headerRLP)).to.eventually.equal(block.sha3Uncles); - await expect(this.mock.$getCoinbase(headerRLP)).to.eventually.equal(ethers.getAddress(block.miner)); - await expect(this.mock.$getStateRoot(headerRLP)).to.eventually.equal(block.stateRoot); - await expect(this.mock.$getTransactionsRoot(headerRLP)).to.eventually.equal(block.transactionsRoot); - await expect(this.mock.$getReceiptsRoot(headerRLP)).to.eventually.equal(block.receiptsRoot); - await expect(this.mock.$getLogsBloom(headerRLP)).to.eventually.equal(block.logsBloom); - await expect(this.mock.$getDifficulty(headerRLP)).to.eventually.equal(block.difficulty); - await expect(this.mock.$getNumber(headerRLP)).to.eventually.equal(block.number); - await expect(this.mock.$getGasLimit(headerRLP)).to.eventually.equal(block.gasLimit); - await expect(this.mock.$getGasUsed(headerRLP)).to.eventually.equal(block.gasUsed); - await expect(this.mock.$getTimestamp(headerRLP)).to.eventually.equal(block.timestamp); - await expect(this.mock.$getExtraData(headerRLP)).to.eventually.equal(block.extraData); - await expect(this.mock.$getPrevRandao(headerRLP)).to.eventually.equal(block.mixHash); - await expect(this.mock.$getNonce(headerRLP)).to.eventually.equal(block.nonce); - await expect(this.mock.$getBaseFeePerGas(headerRLP)).to.eventually.equal(block.baseFeePerGas); - await expect(this.mock.$getWithdrawalsRoot(headerRLP)).to.eventually.equal(block.withdrawalsRoot); - await expect(this.mock.$getBlobGasUsed(headerRLP)).to.eventually.equal(block.blobGasUsed); - await expect(this.mock.$getExcessBlobGas(headerRLP)).to.eventually.equal(block.excessBlobGas); - await expect(this.mock.$getParentBeaconBlockRoot(headerRLP)).to.eventually.equal(block.parentBeaconBlockRoot); - await expect(this.mock.$getRequestsHash(headerRLP)).to.eventually.equal(block.requestsHash); + await check(this.mock.$getParentHash(headerRLP), block.parentHash, Hardforks.Homestead); + await check(this.mock.$getOmmersHash(headerRLP), block.sha3Uncles, Hardforks.Homestead); + await check(this.mock.$getCoinbase(headerRLP), ethers.getAddress(block.miner), Hardforks.Homestead); + await check(this.mock.$getStateRoot(headerRLP), block.stateRoot, Hardforks.Homestead); + await check(this.mock.$getTransactionsRoot(headerRLP), block.transactionsRoot, Hardforks.Homestead); + await check(this.mock.$getReceiptsRoot(headerRLP), block.receiptsRoot, Hardforks.Homestead); + await check(this.mock.$getLogsBloom(headerRLP), block.logsBloom, Hardforks.Homestead); + await check(this.mock.$getDifficulty(headerRLP), block.difficulty, Hardforks.Homestead); + await check(this.mock.$getNumber(headerRLP), block.number, Hardforks.Homestead); + await check(this.mock.$getGasLimit(headerRLP), block.gasLimit, Hardforks.Homestead); + await check(this.mock.$getGasUsed(headerRLP), block.gasUsed, Hardforks.Homestead); + await check(this.mock.$getTimestamp(headerRLP), block.timestamp, Hardforks.Homestead); + await check(this.mock.$getExtraData(headerRLP), block.extraData, Hardforks.Homestead); + await check(this.mock.$getPrevRandao(headerRLP), block.mixHash, Hardforks.Homestead); + await check(this.mock.$getNonce(headerRLP), block.nonce, Hardforks.Homestead); + await check(this.mock.$getBaseFeePerGas(headerRLP), block.baseFeePerGas, Hardforks.London); + await check(this.mock.$getWithdrawalsRoot(headerRLP), block.withdrawalsRoot, Hardforks.Shanghai); + await check(this.mock.$getBlobGasUsed(headerRLP), block.blobGasUsed, Hardforks.Cancun); + await check(this.mock.$getExcessBlobGas(headerRLP), block.excessBlobGas, Hardforks.Cancun); + await check(this.mock.$getParentBeaconBlockRoot(headerRLP), block.parentBeaconBlockRoot, Hardforks.Cancun); + await check(this.mock.$getRequestsHash(headerRLP), block.requestsHash, Hardforks.Prague); + await check(this.mock.$getBlockAccessListHash(headerRLP), block.blockAccessListHash, Hardforks.Amsterdam); }); } }); From cd4cd6c2c1aead82c7ae8ddacfd8d73a565a5dcd Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 14:24:35 +0100 Subject: [PATCH 4/9] add missing frontier version --- contracts/utils/BlockHeader.sol | 63 +++++++++++++++++---------------- test/utils/BlockHeader.test.js | 36 +++++++++---------- 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/contracts/utils/BlockHeader.sol b/contracts/utils/BlockHeader.sol index 062a8ab4ae1..00245c543ea 100644 --- a/contracts/utils/BlockHeader.sol +++ b/contracts/utils/BlockHeader.sol @@ -14,6 +14,7 @@ library BlockHeader { /// @dev List of evm versions. enum Hardforks { + Frontier, Homestead, DAO, TangerineWhistle, @@ -37,21 +38,21 @@ library BlockHeader { /// @dev List of block header fields, in the order they are encoded in the block header RLP. enum HeaderField { - ParentHash, // Since Homestead - OmmersHash, // Since Homestead - Coinbase, // Since Homestead - StateRoot, // Since Homestead - TransactionsRoot, // Since Homestead - ReceiptsRoot, // Since Homestead - LogsBloom, // Since Homestead - Difficulty, // Since Homestead - Number, // Since Homestead - GasLimit, // Since Homestead - GasUsed, // Since Homestead - Timestamp, // Since Homestead - ExtraData, // Since Homestead - PrevRandao, // Since Homestead (called MixHash before Paris) - Nonce, // Since Homestead + ParentHash, // Since Frontier + OmmersHash, // Since Frontier + Coinbase, // Since Frontier + StateRoot, // Since Frontier + TransactionsRoot, // Since Frontier + ReceiptsRoot, // Since Frontier + LogsBloom, // Since Frontier + Difficulty, // Since Frontier + Number, // Since Frontier + GasLimit, // Since Frontier + GasUsed, // Since Frontier + Timestamp, // Since Frontier + ExtraData, // Since Frontier + PrevRandao, // Since Frontier (called MixHash before Paris) + Nonce, // Since Frontier BaseFeePerGas, // Since London WithdrawalsRoot, // Since Shanghai BlobGasUsed, // Since Cancun @@ -71,77 +72,77 @@ library BlockHeader { /// @dev Extract the parent hash from the block header RLP. function getParentHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.ParentHash)].readBytes32(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.ParentHash)].readBytes32(); } /// @dev Extract the ommers hash from the block header RLP. This is constant to keccak256(rlp([])) since EIP-3675 (Paris) function getOmmersHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.OmmersHash)].readBytes32(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.OmmersHash)].readBytes32(); } /// @dev Extract the coinbase (a.k.a. beneficiary or miner) address from the block header RLP. function getCoinbase(bytes memory headerRLP) internal pure returns (address) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Coinbase)].readAddress(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Coinbase)].readAddress(); } /// @dev Extract the state root from the block header RLP. function getStateRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.StateRoot)].readBytes32(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.StateRoot)].readBytes32(); } /// @dev Extract the transactions root from the block header RLP. function getTransactionsRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.TransactionsRoot)].readBytes32(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.TransactionsRoot)].readBytes32(); } /// @dev Extract the receipts root from the block header RLP. function getReceiptsRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.ReceiptsRoot)].readBytes32(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.ReceiptsRoot)].readBytes32(); } /// @dev Extract the logs bloom from the block header RLP. function getLogsBloom(bytes memory headerRLP) internal pure returns (bytes memory) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.LogsBloom)].readBytes(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.LogsBloom)].readBytes(); } /// @dev Extract the difficulty from the block header RLP. This is constant to 0 since EIP-3675 (Paris) function getDifficulty(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Difficulty)].readUint256(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Difficulty)].readUint256(); } /// @dev Extract the block number from the block header RLP. function getNumber(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Number)].readUint256(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Number)].readUint256(); } /// @dev Extract the gas used from the block header RLP. function getGasUsed(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.GasUsed)].readUint256(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.GasUsed)].readUint256(); } /// @dev Extract the gas limit from the block header RLP. function getGasLimit(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.GasLimit)].readUint256(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.GasLimit)].readUint256(); } /// @dev Extract the timestamp from the block header RLP. function getTimestamp(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Timestamp)].readUint256(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Timestamp)].readUint256(); } /// @dev Extract the extra data from the block header RLP. function getExtraData(bytes memory headerRLP) internal pure returns (bytes memory) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.ExtraData)].readBytes(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.ExtraData)].readBytes(); } /// @dev Extract the prevRandao (a.k.a. mixHash before Paris) from the block header RLP. function getPrevRandao(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.PrevRandao)].readBytes32(); + return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.PrevRandao)].readBytes32(); } /// @dev Extract the nonce from the block header RLP. This is constant to 0 since EIP-3675 (Paris) function getNonce(bytes memory headerRLP) internal pure returns (bytes8) { - return bytes8(_parseHeader(headerRLP, Hardforks.Homestead)[uint8(HeaderField.Nonce)].readUint256().toUint64()); + return bytes8(_parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Nonce)].readUint256().toUint64()); } /// @dev Extract the base fee per gas from the block header RLP. This was introduced in London. @@ -190,7 +191,7 @@ library BlockHeader { /// @dev Internal function to return the expected number of fields in the block header RLP for a given hardfork version. function _expectedHeadersLength(Hardforks version) private pure returns (uint8 count) { assembly ("memory-safe") { - count := byte(version, 0x0F0F0F0F0F0F0F0F0F0F10101010111415151600000000000000000000000000) + count := byte(version, 0x0F0F0F0F0F0F0F0F0F0F0F101010101114151516000000000000000000000000) } } } diff --git a/test/utils/BlockHeader.test.js b/test/utils/BlockHeader.test.js index 1a82a2847ab..0b53e2fa867 100644 --- a/test/utils/BlockHeader.test.js +++ b/test/utils/BlockHeader.test.js @@ -4,6 +4,7 @@ const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers' const { Enum } = require('../helpers/enums'); const Hardforks = Enum( + 'Frontier', 'Homestead', 'DAO', 'TangerineWhistle', @@ -102,7 +103,6 @@ describe('BlockHeader', function () { describe('historical blocks', function () { for (const block of [ { - _version: Hardforks.Berlin, hash: '0x244dd4312575b2c8846837086cf4f1669d026f1db376d1ad700ed77782c11b75', parentHash: '0x9b2a40b705d5d806ede8764de1b628e43d98df93dfe779e5f8d9fdb51c6c3e5e', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', @@ -122,7 +122,6 @@ describe('BlockHeader', function () { nonce: '0xb3b2502a8c3de6f2', }, { - _version: Hardforks.London, hash: '0xbd44f309a77f0eeca1f0fb8d0fee0663ca024cb46c2e6fa8f5eee77f0259b7d5', parentHash: '0xa7d38804924d37bcc5dff91167ac797aa4721a2d4235f433f12598219b5eee93', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', @@ -143,7 +142,6 @@ describe('BlockHeader', function () { baseFeePerGas: '0xa9558cdb7', }, { - _version: Hardforks.Shanghai, hash: '0x82b4ead321f2f3891e11990f109b9e17cc39a4ffd7aeb64b8e2c9ecad6a8dcdc', parentHash: '0xa920c05e272e83f10ee97d8bd809986a9fa036d9e245682fce503ecb435b0bc1', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', @@ -165,7 +163,6 @@ describe('BlockHeader', function () { withdrawalsRoot: '0x114350f058bd9496a55fc98134dba4fa9aea36083246ae9ad7adcc9906e3eb58', }, { - _version: Hardforks.Cancun, hash: '0xf98958f9c83103ec7f0d5f13cf2c36e9793d99413aa06943fad5a8743c055b29', parentHash: '0xb37620f0875d897305dad1b7cbd043243880ad96acdb04d73f38b039796c821a', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', @@ -190,7 +187,6 @@ describe('BlockHeader', function () { parentBeaconBlockRoot: '0x868fd48788b5a92d01ebb31bab47b997ce3f35e30ea4dd4055751d6309a71ff0', }, { - _version: Hardforks.Prague, hash: '0x0f23d6dee77755efe485b0870d820b1aae8cfe689d767ce7d10b6afa2b1ef14d', parentHash: '0x31d473c19b8e0d89e0546d057506d9dea042cd1492ef90682134e6027cf8669c', sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', @@ -229,21 +225,21 @@ describe('BlockHeader', function () { expect(ethers.keccak256(headerRLP)).to.equal(block.hash); // parsing check - await check(this.mock.$getParentHash(headerRLP), block.parentHash, Hardforks.Homestead); - await check(this.mock.$getOmmersHash(headerRLP), block.sha3Uncles, Hardforks.Homestead); - await check(this.mock.$getCoinbase(headerRLP), ethers.getAddress(block.miner), Hardforks.Homestead); - await check(this.mock.$getStateRoot(headerRLP), block.stateRoot, Hardforks.Homestead); - await check(this.mock.$getTransactionsRoot(headerRLP), block.transactionsRoot, Hardforks.Homestead); - await check(this.mock.$getReceiptsRoot(headerRLP), block.receiptsRoot, Hardforks.Homestead); - await check(this.mock.$getLogsBloom(headerRLP), block.logsBloom, Hardforks.Homestead); - await check(this.mock.$getDifficulty(headerRLP), block.difficulty, Hardforks.Homestead); - await check(this.mock.$getNumber(headerRLP), block.number, Hardforks.Homestead); - await check(this.mock.$getGasLimit(headerRLP), block.gasLimit, Hardforks.Homestead); - await check(this.mock.$getGasUsed(headerRLP), block.gasUsed, Hardforks.Homestead); - await check(this.mock.$getTimestamp(headerRLP), block.timestamp, Hardforks.Homestead); - await check(this.mock.$getExtraData(headerRLP), block.extraData, Hardforks.Homestead); - await check(this.mock.$getPrevRandao(headerRLP), block.mixHash, Hardforks.Homestead); - await check(this.mock.$getNonce(headerRLP), block.nonce, Hardforks.Homestead); + await check(this.mock.$getParentHash(headerRLP), block.parentHash, Hardforks.Frontier); + await check(this.mock.$getOmmersHash(headerRLP), block.sha3Uncles, Hardforks.Frontier); + await check(this.mock.$getCoinbase(headerRLP), ethers.getAddress(block.miner), Hardforks.Frontier); + await check(this.mock.$getStateRoot(headerRLP), block.stateRoot, Hardforks.Frontier); + await check(this.mock.$getTransactionsRoot(headerRLP), block.transactionsRoot, Hardforks.Frontier); + await check(this.mock.$getReceiptsRoot(headerRLP), block.receiptsRoot, Hardforks.Frontier); + await check(this.mock.$getLogsBloom(headerRLP), block.logsBloom, Hardforks.Frontier); + await check(this.mock.$getDifficulty(headerRLP), block.difficulty, Hardforks.Frontier); + await check(this.mock.$getNumber(headerRLP), block.number, Hardforks.Frontier); + await check(this.mock.$getGasLimit(headerRLP), block.gasLimit, Hardforks.Frontier); + await check(this.mock.$getGasUsed(headerRLP), block.gasUsed, Hardforks.Frontier); + await check(this.mock.$getTimestamp(headerRLP), block.timestamp, Hardforks.Frontier); + await check(this.mock.$getExtraData(headerRLP), block.extraData, Hardforks.Frontier); + await check(this.mock.$getPrevRandao(headerRLP), block.mixHash, Hardforks.Frontier); + await check(this.mock.$getNonce(headerRLP), block.nonce, Hardforks.Frontier); await check(this.mock.$getBaseFeePerGas(headerRLP), block.baseFeePerGas, Hardforks.London); await check(this.mock.$getWithdrawalsRoot(headerRLP), block.withdrawalsRoot, Hardforks.Shanghai); await check(this.mock.$getBlobGasUsed(headerRLP), block.blobGasUsed, Hardforks.Cancun); From 3e6fbd98440d2e6a1c74b9d4e3140ff4aa1f4bed Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 14:31:46 +0100 Subject: [PATCH 5/9] remove HardFork that include no change to the EVM (difficulty bomb only) --- contracts/utils/BlockHeader.sol | 5 +---- test/utils/BlockHeader.test.js | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/contracts/utils/BlockHeader.sol b/contracts/utils/BlockHeader.sol index 00245c543ea..d8a744b06bc 100644 --- a/contracts/utils/BlockHeader.sol +++ b/contracts/utils/BlockHeader.sol @@ -23,11 +23,8 @@ library BlockHeader { Constantinople, Petersburg, Istanbul, - MuirGlacier, Berlin, London, - ArrowGlacier, - GreyGlacier, Paris, Shanghai, Cancun, @@ -191,7 +188,7 @@ library BlockHeader { /// @dev Internal function to return the expected number of fields in the block header RLP for a given hardfork version. function _expectedHeadersLength(Hardforks version) private pure returns (uint8 count) { assembly ("memory-safe") { - count := byte(version, 0x0F0F0F0F0F0F0F0F0F0F0F101010101114151516000000000000000000000000) + count := byte(version, 0x0F0F0F0F0F0F0F0F0F0F10101114151516000000000000000000000000000000) } } } diff --git a/test/utils/BlockHeader.test.js b/test/utils/BlockHeader.test.js index 0b53e2fa867..1594a46a01b 100644 --- a/test/utils/BlockHeader.test.js +++ b/test/utils/BlockHeader.test.js @@ -13,11 +13,8 @@ const Hardforks = Enum( 'Constantinople', 'Petersburg', 'Istanbul', - 'MuirGlacier', 'Berlin', 'London', - 'ArrowGlacier', - 'GreyGlacier', 'Paris', 'Shanghai', 'Cancun', From c4e86a8a3c0d36352ea5ddd95240eb84f15ddb91 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 20:14:22 +0100 Subject: [PATCH 6/9] update --- contracts/utils/BlockHeader.sol | 61 ++++++++++++++++----------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/contracts/utils/BlockHeader.sol b/contracts/utils/BlockHeader.sol index d8a744b06bc..176e39467e0 100644 --- a/contracts/utils/BlockHeader.sol +++ b/contracts/utils/BlockHeader.sol @@ -69,126 +69,123 @@ library BlockHeader { /// @dev Extract the parent hash from the block header RLP. function getParentHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.ParentHash)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.ParentHash, Hardforks.Frontier).readBytes32(); } /// @dev Extract the ommers hash from the block header RLP. This is constant to keccak256(rlp([])) since EIP-3675 (Paris) function getOmmersHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.OmmersHash)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.OmmersHash, Hardforks.Frontier).readBytes32(); } /// @dev Extract the coinbase (a.k.a. beneficiary or miner) address from the block header RLP. function getCoinbase(bytes memory headerRLP) internal pure returns (address) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Coinbase)].readAddress(); + return _parseHeader(headerRLP, HeaderField.Coinbase, Hardforks.Frontier).readAddress(); } /// @dev Extract the state root from the block header RLP. function getStateRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.StateRoot)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.StateRoot, Hardforks.Frontier).readBytes32(); } /// @dev Extract the transactions root from the block header RLP. function getTransactionsRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.TransactionsRoot)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.TransactionsRoot, Hardforks.Frontier).readBytes32(); } /// @dev Extract the receipts root from the block header RLP. function getReceiptsRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.ReceiptsRoot)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.ReceiptsRoot, Hardforks.Frontier).readBytes32(); } /// @dev Extract the logs bloom from the block header RLP. function getLogsBloom(bytes memory headerRLP) internal pure returns (bytes memory) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.LogsBloom)].readBytes(); + return _parseHeader(headerRLP, HeaderField.LogsBloom, Hardforks.Frontier).readBytes(); } /// @dev Extract the difficulty from the block header RLP. This is constant to 0 since EIP-3675 (Paris) function getDifficulty(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Difficulty)].readUint256(); + return _parseHeader(headerRLP, HeaderField.Difficulty, Hardforks.Frontier).readUint256(); } /// @dev Extract the block number from the block header RLP. function getNumber(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Number)].readUint256(); + return _parseHeader(headerRLP, HeaderField.Number, Hardforks.Frontier).readUint256(); } /// @dev Extract the gas used from the block header RLP. function getGasUsed(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.GasUsed)].readUint256(); + return _parseHeader(headerRLP, HeaderField.GasUsed, Hardforks.Frontier).readUint256(); } /// @dev Extract the gas limit from the block header RLP. function getGasLimit(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.GasLimit)].readUint256(); + return _parseHeader(headerRLP, HeaderField.GasLimit, Hardforks.Frontier).readUint256(); } /// @dev Extract the timestamp from the block header RLP. function getTimestamp(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Timestamp)].readUint256(); + return _parseHeader(headerRLP, HeaderField.Timestamp, Hardforks.Frontier).readUint256(); } /// @dev Extract the extra data from the block header RLP. function getExtraData(bytes memory headerRLP) internal pure returns (bytes memory) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.ExtraData)].readBytes(); + return _parseHeader(headerRLP, HeaderField.ExtraData, Hardforks.Frontier).readBytes(); } /// @dev Extract the prevRandao (a.k.a. mixHash before Paris) from the block header RLP. function getPrevRandao(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.PrevRandao)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.PrevRandao, Hardforks.Frontier).readBytes32(); } /// @dev Extract the nonce from the block header RLP. This is constant to 0 since EIP-3675 (Paris) function getNonce(bytes memory headerRLP) internal pure returns (bytes8) { - return bytes8(_parseHeader(headerRLP, Hardforks.Frontier)[uint8(HeaderField.Nonce)].readUint256().toUint64()); + return bytes8(_parseHeader(headerRLP, HeaderField.Nonce, Hardforks.Frontier).readUint256().toUint64()); } /// @dev Extract the base fee per gas from the block header RLP. This was introduced in London. function getBaseFeePerGas(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, Hardforks.London)[uint8(HeaderField.BaseFeePerGas)].readUint256(); + return _parseHeader(headerRLP, HeaderField.BaseFeePerGas, Hardforks.London).readUint256(); } /// @dev Extract the withdrawals root from the block header RLP. This was introduced in Shanghai. function getWithdrawalsRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Shanghai)[uint8(HeaderField.WithdrawalsRoot)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.WithdrawalsRoot, Hardforks.Shanghai).readBytes32(); } /// @dev Extract the blob gas used from the block header RLP. This was introduced in Cancun. function getBlobGasUsed(bytes memory headerRLP) internal pure returns (uint64) { - return _parseHeader(headerRLP, Hardforks.Cancun)[uint8(HeaderField.BlobGasUsed)].readUint256().toUint64(); + return _parseHeader(headerRLP, HeaderField.BlobGasUsed, Hardforks.Cancun).readUint256().toUint64(); } /// @dev Extract the excess blob gas from the block header RLP. This was introduced in Cancun. function getExcessBlobGas(bytes memory headerRLP) internal pure returns (uint64) { - return _parseHeader(headerRLP, Hardforks.Cancun)[uint8(HeaderField.ExcessBlobGas)].readUint256().toUint64(); + return _parseHeader(headerRLP, HeaderField.ExcessBlobGas, Hardforks.Cancun).readUint256().toUint64(); } /// @dev Extract the parent beacon block root from the block header RLP. This was introduced in Cancun. function getParentBeaconBlockRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Cancun)[uint8(HeaderField.ParentBeaconBlockRoot)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.ParentBeaconBlockRoot, Hardforks.Cancun).readBytes32(); } /// @dev Extract the requests hash from the block header RLP. This was introduced in Prague. function getRequestsHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Prague)[uint8(HeaderField.RequestsHash)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.RequestsHash, Hardforks.Prague).readBytes32(); } /// @dev Extract the block access list hash from the block header RLP. This will be introduced in Amsterdam. function getBlockAccessListHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, Hardforks.Amsterdam)[uint8(HeaderField.BlockAccessListHash)].readBytes32(); + return _parseHeader(headerRLP, HeaderField.BlockAccessListHash, Hardforks.Amsterdam).readBytes32(); } /// @dev Internal function to parse the block header RLP and return the list of fields as memory slices. /// It also checks that the number of fields is at least the expected number for the given hardfork version. - function _parseHeader(bytes memory headerRLP, Hardforks version) private pure returns (Memory.Slice[] memory) { + function _parseHeader( + bytes memory headerRLP, + HeaderField field, + Hardforks version + ) private pure returns (Memory.Slice) { Memory.Slice[] memory fields = RLP.decodeList(headerRLP); - require(fields.length >= _expectedHeadersLength(version), InvalidBlockHeader(version)); - return fields; - } - - /// @dev Internal function to return the expected number of fields in the block header RLP for a given hardfork version. - function _expectedHeadersLength(Hardforks version) private pure returns (uint8 count) { - assembly ("memory-safe") { - count := byte(version, 0x0F0F0F0F0F0F0F0F0F0F10101114151516000000000000000000000000000000) - } + require(uint8(field) < fields.length, InvalidBlockHeader(version)); + return fields[uint8(field)]; } } From ab57fb3ba2f1e2dc05e8370e0f7b01c35edc372d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 20:19:38 +0100 Subject: [PATCH 7/9] update --- contracts/utils/BlockHeader.sol | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/contracts/utils/BlockHeader.sol b/contracts/utils/BlockHeader.sol index 176e39467e0..8d9d5de6ef7 100644 --- a/contracts/utils/BlockHeader.sol +++ b/contracts/utils/BlockHeader.sol @@ -184,8 +184,22 @@ library BlockHeader { HeaderField field, Hardforks version ) private pure returns (Memory.Slice) { + // Cache the FMP. + Memory.Pointer fmp = Memory.getFreeMemoryPointer(); + + // allocates an array of slices. The slices are pointing to sections of the original headerRLP. Memory.Slice[] memory fields = RLP.decodeList(headerRLP); + + // Check field index is present in the header. require(uint8(field) < fields.length, InvalidBlockHeader(version)); - return fields[uint8(field)]; + + // Slice pointing to a section of the original headerRLP, which was allocated before this function call. This + // slice remains valid even if we deallocate the fields array that was generated by RLP.decodeList. + Memory.Slice result = fields[uint8(field)]; + + // Reset the FMP. + Memory.unsafeSetFreeMemoryPointer(fmp); + + return result; } } From 922af989df5c3b2eb4e2b3425395d4354640849e Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 20:27:04 +0100 Subject: [PATCH 8/9] update --- contracts/utils/BlockHeader.sol | 80 ++++++++++----------------- test/utils/BlockHeader.test.js | 97 +++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 94 deletions(-) diff --git a/contracts/utils/BlockHeader.sol b/contracts/utils/BlockHeader.sol index 8d9d5de6ef7..50546d0f24e 100644 --- a/contracts/utils/BlockHeader.sol +++ b/contracts/utils/BlockHeader.sol @@ -9,29 +9,8 @@ import {RLP} from "./RLP.sol"; /// @dev Library for parsing and verifying RLP-encoded block headers. library BlockHeader { - using SafeCast for *; using RLP for *; - - /// @dev List of evm versions. - enum Hardforks { - Frontier, - Homestead, - DAO, - TangerineWhistle, - SpuriousDragon, - Byzantium, - Constantinople, - Petersburg, - Istanbul, - Berlin, - London, - Paris, - Shanghai, - Cancun, - Prague, - Osaka, - Amsterdam - } + using SafeCast for *; /// @dev List of block header fields, in the order they are encoded in the block header RLP. enum HeaderField { @@ -59,8 +38,9 @@ library BlockHeader { BlockAccessListHash // Since Amsterdam } - /// @dev Thrown when the provided block header RLP does not have the expected number of fields for the given hardfork version. - error InvalidBlockHeader(Hardforks expectedVersion); + /// @dev Thrown when the provided block header RLP does not have the expected number of fields. + /// Happens if it correspond to an older version of the EVM that doesn't include the field.. + error FieldNotPresentInBlockHeader(HeaderField); /// @dev Verifies that the given block header RLP corresponds to a valid block header for the current chain. function verifyBlockHeader(bytes memory headerRLP) internal view returns (bool) { @@ -69,121 +49,117 @@ library BlockHeader { /// @dev Extract the parent hash from the block header RLP. function getParentHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.ParentHash, Hardforks.Frontier).readBytes32(); + return _parseHeader(headerRLP, HeaderField.ParentHash).readBytes32(); } /// @dev Extract the ommers hash from the block header RLP. This is constant to keccak256(rlp([])) since EIP-3675 (Paris) function getOmmersHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.OmmersHash, Hardforks.Frontier).readBytes32(); + return _parseHeader(headerRLP, HeaderField.OmmersHash).readBytes32(); } /// @dev Extract the coinbase (a.k.a. beneficiary or miner) address from the block header RLP. function getCoinbase(bytes memory headerRLP) internal pure returns (address) { - return _parseHeader(headerRLP, HeaderField.Coinbase, Hardforks.Frontier).readAddress(); + return _parseHeader(headerRLP, HeaderField.Coinbase).readAddress(); } /// @dev Extract the state root from the block header RLP. function getStateRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.StateRoot, Hardforks.Frontier).readBytes32(); + return _parseHeader(headerRLP, HeaderField.StateRoot).readBytes32(); } /// @dev Extract the transactions root from the block header RLP. function getTransactionsRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.TransactionsRoot, Hardforks.Frontier).readBytes32(); + return _parseHeader(headerRLP, HeaderField.TransactionsRoot).readBytes32(); } /// @dev Extract the receipts root from the block header RLP. function getReceiptsRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.ReceiptsRoot, Hardforks.Frontier).readBytes32(); + return _parseHeader(headerRLP, HeaderField.ReceiptsRoot).readBytes32(); } /// @dev Extract the logs bloom from the block header RLP. function getLogsBloom(bytes memory headerRLP) internal pure returns (bytes memory) { - return _parseHeader(headerRLP, HeaderField.LogsBloom, Hardforks.Frontier).readBytes(); + return _parseHeader(headerRLP, HeaderField.LogsBloom).readBytes(); } /// @dev Extract the difficulty from the block header RLP. This is constant to 0 since EIP-3675 (Paris) function getDifficulty(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, HeaderField.Difficulty, Hardforks.Frontier).readUint256(); + return _parseHeader(headerRLP, HeaderField.Difficulty).readUint256(); } /// @dev Extract the block number from the block header RLP. function getNumber(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, HeaderField.Number, Hardforks.Frontier).readUint256(); + return _parseHeader(headerRLP, HeaderField.Number).readUint256(); } /// @dev Extract the gas used from the block header RLP. function getGasUsed(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, HeaderField.GasUsed, Hardforks.Frontier).readUint256(); + return _parseHeader(headerRLP, HeaderField.GasUsed).readUint256(); } /// @dev Extract the gas limit from the block header RLP. function getGasLimit(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, HeaderField.GasLimit, Hardforks.Frontier).readUint256(); + return _parseHeader(headerRLP, HeaderField.GasLimit).readUint256(); } /// @dev Extract the timestamp from the block header RLP. function getTimestamp(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, HeaderField.Timestamp, Hardforks.Frontier).readUint256(); + return _parseHeader(headerRLP, HeaderField.Timestamp).readUint256(); } /// @dev Extract the extra data from the block header RLP. function getExtraData(bytes memory headerRLP) internal pure returns (bytes memory) { - return _parseHeader(headerRLP, HeaderField.ExtraData, Hardforks.Frontier).readBytes(); + return _parseHeader(headerRLP, HeaderField.ExtraData).readBytes(); } /// @dev Extract the prevRandao (a.k.a. mixHash before Paris) from the block header RLP. function getPrevRandao(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.PrevRandao, Hardforks.Frontier).readBytes32(); + return _parseHeader(headerRLP, HeaderField.PrevRandao).readBytes32(); } /// @dev Extract the nonce from the block header RLP. This is constant to 0 since EIP-3675 (Paris) function getNonce(bytes memory headerRLP) internal pure returns (bytes8) { - return bytes8(_parseHeader(headerRLP, HeaderField.Nonce, Hardforks.Frontier).readUint256().toUint64()); + return bytes8(_parseHeader(headerRLP, HeaderField.Nonce).readUint256().toUint64()); } /// @dev Extract the base fee per gas from the block header RLP. This was introduced in London. function getBaseFeePerGas(bytes memory headerRLP) internal pure returns (uint256) { - return _parseHeader(headerRLP, HeaderField.BaseFeePerGas, Hardforks.London).readUint256(); + return _parseHeader(headerRLP, HeaderField.BaseFeePerGas).readUint256(); } /// @dev Extract the withdrawals root from the block header RLP. This was introduced in Shanghai. function getWithdrawalsRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.WithdrawalsRoot, Hardforks.Shanghai).readBytes32(); + return _parseHeader(headerRLP, HeaderField.WithdrawalsRoot).readBytes32(); } /// @dev Extract the blob gas used from the block header RLP. This was introduced in Cancun. function getBlobGasUsed(bytes memory headerRLP) internal pure returns (uint64) { - return _parseHeader(headerRLP, HeaderField.BlobGasUsed, Hardforks.Cancun).readUint256().toUint64(); + return _parseHeader(headerRLP, HeaderField.BlobGasUsed).readUint256().toUint64(); } /// @dev Extract the excess blob gas from the block header RLP. This was introduced in Cancun. function getExcessBlobGas(bytes memory headerRLP) internal pure returns (uint64) { - return _parseHeader(headerRLP, HeaderField.ExcessBlobGas, Hardforks.Cancun).readUint256().toUint64(); + return _parseHeader(headerRLP, HeaderField.ExcessBlobGas).readUint256().toUint64(); } /// @dev Extract the parent beacon block root from the block header RLP. This was introduced in Cancun. function getParentBeaconBlockRoot(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.ParentBeaconBlockRoot, Hardforks.Cancun).readBytes32(); + return _parseHeader(headerRLP, HeaderField.ParentBeaconBlockRoot).readBytes32(); } /// @dev Extract the requests hash from the block header RLP. This was introduced in Prague. function getRequestsHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.RequestsHash, Hardforks.Prague).readBytes32(); + return _parseHeader(headerRLP, HeaderField.RequestsHash).readBytes32(); } /// @dev Extract the block access list hash from the block header RLP. This will be introduced in Amsterdam. function getBlockAccessListHash(bytes memory headerRLP) internal pure returns (bytes32) { - return _parseHeader(headerRLP, HeaderField.BlockAccessListHash, Hardforks.Amsterdam).readBytes32(); + return _parseHeader(headerRLP, HeaderField.BlockAccessListHash).readBytes32(); } /// @dev Internal function to parse the block header RLP and return the list of fields as memory slices. /// It also checks that the number of fields is at least the expected number for the given hardfork version. - function _parseHeader( - bytes memory headerRLP, - HeaderField field, - Hardforks version - ) private pure returns (Memory.Slice) { + function _parseHeader(bytes memory headerRLP, HeaderField field) private pure returns (Memory.Slice) { // Cache the FMP. Memory.Pointer fmp = Memory.getFreeMemoryPointer(); @@ -191,7 +167,7 @@ library BlockHeader { Memory.Slice[] memory fields = RLP.decodeList(headerRLP); // Check field index is present in the header. - require(uint8(field) < fields.length, InvalidBlockHeader(version)); + require(uint8(field) < fields.length, FieldNotPresentInBlockHeader(field)); // Slice pointing to a section of the original headerRLP, which was allocated before this function call. This // slice remains valid even if we deallocate the fields array that was generated by RLP.decodeList. diff --git a/test/utils/BlockHeader.test.js b/test/utils/BlockHeader.test.js index 1594a46a01b..3dc1da6c26e 100644 --- a/test/utils/BlockHeader.test.js +++ b/test/utils/BlockHeader.test.js @@ -3,24 +3,29 @@ const { expect } = require('chai'); const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); const { Enum } = require('../helpers/enums'); -const Hardforks = Enum( - 'Frontier', - 'Homestead', - 'DAO', - 'TangerineWhistle', - 'SpuriousDragon', - 'Byzantium', - 'Constantinople', - 'Petersburg', - 'Istanbul', - 'Berlin', - 'London', - 'Paris', - 'Shanghai', - 'Cancun', - 'Prague', - 'Osaka', - 'Amsterdam', +const HeaderField = Enum( + 'ParentHash', // Since Frontier + 'OmmersHash', // Since Frontier + 'Coinbase', // Since Frontier + 'StateRoot', // Since Frontier + 'TransactionsRoot', // Since Frontier + 'ReceiptsRoot', // Since Frontier + 'LogsBloom', // Since Frontier + 'Difficulty', // Since Frontier + 'Number', // Since Frontier + 'GasLimit', // Since Frontier + 'GasUsed', // Since Frontier + 'Timestamp', // Since Frontier + 'ExtraData', // Since Frontier + 'PrevRandao', // Since Frontier (called MixHash before Paris) + 'Nonce', // Since Frontier + 'BaseFeePerGas', // Since London + 'WithdrawalsRoot', // Since Shanghai + 'BlobGasUsed', // Since Cancun + 'ExcessBlobGas', // Since Cancun + 'ParentBeaconBlockRoot', // Since Cancun + 'RequestsHash', // Since Prague + 'BlockAccessListHash', // Since Amsterdam ); const sanitize = hex => (hex === undefined ? undefined : hex === '0x0' ? '0x' : ethers.toBeHex(hex)); @@ -211,10 +216,10 @@ describe('BlockHeader', function () { ]) { it(`should parse block #${Number(block.number)} correctly`, async function () { // Check helper (for old evm versions) - const check = (promise, value, version) => + const check = (promise, value, field) => value ? expect(promise).to.eventually.equal(value) - : expect(promise).to.be.revertedWithCustomError(this.mock, 'InvalidBlockHeader').withArgs(version); + : expect(promise).to.be.revertedWithCustomError(this.mock, 'FieldNotPresentInBlockHeader').withArgs(field); const headerRLP = rlpEncodeBlock(block); @@ -222,28 +227,36 @@ describe('BlockHeader', function () { expect(ethers.keccak256(headerRLP)).to.equal(block.hash); // parsing check - await check(this.mock.$getParentHash(headerRLP), block.parentHash, Hardforks.Frontier); - await check(this.mock.$getOmmersHash(headerRLP), block.sha3Uncles, Hardforks.Frontier); - await check(this.mock.$getCoinbase(headerRLP), ethers.getAddress(block.miner), Hardforks.Frontier); - await check(this.mock.$getStateRoot(headerRLP), block.stateRoot, Hardforks.Frontier); - await check(this.mock.$getTransactionsRoot(headerRLP), block.transactionsRoot, Hardforks.Frontier); - await check(this.mock.$getReceiptsRoot(headerRLP), block.receiptsRoot, Hardforks.Frontier); - await check(this.mock.$getLogsBloom(headerRLP), block.logsBloom, Hardforks.Frontier); - await check(this.mock.$getDifficulty(headerRLP), block.difficulty, Hardforks.Frontier); - await check(this.mock.$getNumber(headerRLP), block.number, Hardforks.Frontier); - await check(this.mock.$getGasLimit(headerRLP), block.gasLimit, Hardforks.Frontier); - await check(this.mock.$getGasUsed(headerRLP), block.gasUsed, Hardforks.Frontier); - await check(this.mock.$getTimestamp(headerRLP), block.timestamp, Hardforks.Frontier); - await check(this.mock.$getExtraData(headerRLP), block.extraData, Hardforks.Frontier); - await check(this.mock.$getPrevRandao(headerRLP), block.mixHash, Hardforks.Frontier); - await check(this.mock.$getNonce(headerRLP), block.nonce, Hardforks.Frontier); - await check(this.mock.$getBaseFeePerGas(headerRLP), block.baseFeePerGas, Hardforks.London); - await check(this.mock.$getWithdrawalsRoot(headerRLP), block.withdrawalsRoot, Hardforks.Shanghai); - await check(this.mock.$getBlobGasUsed(headerRLP), block.blobGasUsed, Hardforks.Cancun); - await check(this.mock.$getExcessBlobGas(headerRLP), block.excessBlobGas, Hardforks.Cancun); - await check(this.mock.$getParentBeaconBlockRoot(headerRLP), block.parentBeaconBlockRoot, Hardforks.Cancun); - await check(this.mock.$getRequestsHash(headerRLP), block.requestsHash, Hardforks.Prague); - await check(this.mock.$getBlockAccessListHash(headerRLP), block.blockAccessListHash, Hardforks.Amsterdam); + await check(this.mock.$getParentHash(headerRLP), block.parentHash, HeaderField.ParentHash); + await check(this.mock.$getOmmersHash(headerRLP), block.sha3Uncles, HeaderField.OmmersHash); + await check(this.mock.$getCoinbase(headerRLP), ethers.getAddress(block.miner), HeaderField.Coinbase); + await check(this.mock.$getStateRoot(headerRLP), block.stateRoot, HeaderField.StateRoot); + await check(this.mock.$getTransactionsRoot(headerRLP), block.transactionsRoot, HeaderField.TransactionsRoot); + await check(this.mock.$getReceiptsRoot(headerRLP), block.receiptsRoot, HeaderField.ReceiptsRoot); + await check(this.mock.$getLogsBloom(headerRLP), block.logsBloom, HeaderField.LogsBloom); + await check(this.mock.$getDifficulty(headerRLP), block.difficulty, HeaderField.Difficulty); + await check(this.mock.$getNumber(headerRLP), block.number, HeaderField.Number); + await check(this.mock.$getGasLimit(headerRLP), block.gasLimit, HeaderField.GasLimit); + await check(this.mock.$getGasUsed(headerRLP), block.gasUsed, HeaderField.GasUsed); + await check(this.mock.$getTimestamp(headerRLP), block.timestamp, HeaderField.Timestamp); + await check(this.mock.$getExtraData(headerRLP), block.extraData, HeaderField.ExtraData); + await check(this.mock.$getPrevRandao(headerRLP), block.mixHash, HeaderField.PrevRandao); + await check(this.mock.$getNonce(headerRLP), block.nonce, HeaderField.Nonce); + await check(this.mock.$getBaseFeePerGas(headerRLP), block.baseFeePerGas, HeaderField.BaseFeePerGas); + await check(this.mock.$getWithdrawalsRoot(headerRLP), block.withdrawalsRoot, HeaderField.WithdrawalsRoot); + await check(this.mock.$getBlobGasUsed(headerRLP), block.blobGasUsed, HeaderField.BlobGasUsed); + await check(this.mock.$getExcessBlobGas(headerRLP), block.excessBlobGas, HeaderField.ExcessBlobGas); + await check( + this.mock.$getParentBeaconBlockRoot(headerRLP), + block.parentBeaconBlockRoot, + HeaderField.ParentBeaconBlockRoot, + ); + await check(this.mock.$getRequestsHash(headerRLP), block.requestsHash, HeaderField.RequestsHash); + await check( + this.mock.$getBlockAccessListHash(headerRLP), + block.blockAccessListHash, + HeaderField.BlockAccessListHash, + ); }); } }); From 52fea0330e3687232b77595be97e855d003cf21e Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 20:36:36 +0100 Subject: [PATCH 9/9] add changeset --- .changeset/yellow-clouds-pick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/yellow-clouds-pick.md diff --git a/.changeset/yellow-clouds-pick.md b/.changeset/yellow-clouds-pick.md new file mode 100644 index 00000000000..4c7aa2c2098 --- /dev/null +++ b/.changeset/yellow-clouds-pick.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`BlockHeader`: Add a new library for verifying and parsing block headers.