Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contracts/mocks/Stateless.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
196 changes: 196 additions & 0 deletions contracts/utils/BlockHeader.sol
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
3 changes: 3 additions & 0 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -118,6 +119,8 @@ Ethereum contracts have no native concept of an interface, so applications must

{{Blockhash}}

{{BlockHeader}}

{{Bytes}}

{{CAIP10}}
Expand Down
71 changes: 71 additions & 0 deletions test/utils/BlockHeader.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
}
});
Loading