Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
58 changes: 58 additions & 0 deletions src/utils/BlockHashLib.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @dev Ethereum block header fields relevant to historical MPT proofs.
struct ShortHeader {
bytes32 parentHash;
bytes32 stateRoot;
bytes32 transactionsRoot;
bytes32 receiptsRoot;
bytes32[8] logsBloom;
}

/// @notice Library for accessing block hashes way beyond the 256-block limit.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/BlockHashLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Blockhash.sol)
library BlockHashLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Invalid block hash.
error InvalidBlockHeader();

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand Down Expand Up @@ -35,4 +51,46 @@ library BlockHashLib {
result := mload(0x00)
}
}

/// @dev Returns whether the hash of a provided RLP-encoded block `header` equals the block hash at `blockNumber`.
function verifyBlockHash(bytes calldata header, uint256 blockNumber)
internal
view
returns (bytes32 result)
{
result = blockHash(blockNumber);
/// @solidity memory-safe-assembly
assembly {
calldatacopy(mload(0x40), header.offset, header.length)
if iszero(eq(result, keccak256(mload(0x40), header.length))) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is xor more efficient here than iszero + eq?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not for here, i think.

mstore(0x00, 0x464db2f8) // InvalidBlockHeader()
revert(0x1c, 0x04)
}
}
}

/// @dev Retrieves the most relevant fields for MPT proofs from an RLP-encoded block `header`.
/// Leading fields are always present and have fixed offsets and lengths.
/// This function allows efficient extraction of these fields from calldata without full RLP decoding.
/// For the specification of field order and lengths, please refer to prefix. 6 of the Ethereum Yellow Paper:
/// (https://ethereum.github.io/yellowpaper/paper.pdf)
/// and the Ethereum Wiki (https://epf.wiki/#/wiki/EL/RLP).
function toShortHeader(bytes calldata header)
internal
pure
returns (ShortHeader memory result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let o := add(header.offset, sub(byte(0, calldataload(header.offset)), 0xF6))
mstore(result, calldataload(add(1, o))) // parentHash
mstore(add(0x20, result), calldataload(add(88, o))) // stateRoot
mstore(add(0x40, result), calldataload(add(121, o))) // transactionsRoot
mstore(add(0x60, result), calldataload(add(154, o))) // receiptsRoot
mstore(add(0x80, result), m) // logsBloom
calldatacopy(m, add(189, o), 0x100)
mstore(0x40, add(0x100, m)) // Allocate the memory.
}
}
}
62 changes: 61 additions & 1 deletion test/BlockHashLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.4;

import "./utils/SoladyTest.sol";
import {BlockHashLib} from "../src/utils/BlockHashLib.sol";
import {BlockHashLib, ShortHeader} from "../src/utils/BlockHashLib.sol";

contract BlockHashLibTest is SoladyTest {
uint256 internal startingBlock;
Expand All @@ -12,6 +12,15 @@ contract BlockHashLibTest is SoladyTest {
bytes private constant _HISTORY_STORAGE_BYTECODE =
hex"3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500";

// cast block 23270177 --raw
// vm.getRawBlockHeader(23270177)
bytes private constant _ETH_BLOCK_23270177 =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fork tests are preferred, not sure how you handle them though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to bump forge-std to support vm.getRawBlockHeader.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we’ll just fake it for now.

when we are really free, we can add a CI that does fork testing.

hex"f9027da01581f4448b16694d5a728161cd65f8c80b88f5352a6f5bd2d2315b970582958da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794dadb0d80178819f2319190d340ce9a924f783711a010d2afa5dabcf2dbfe3aa82b758427938e07880bd6fef3c82c404d0dd7c3f0f3a0f81230c715a462c827898bf2e337982907a7af90e5be20f911785bda05dab93ca0740f11bc75cf25e40d78d892d2e03083eaa573e5b4c26913fcc1b833db854c94b9010085f734fb06ea8fe377abbcb2e27f9ac99751ba817dc327327db101fd76f964ed0b7ca161f148fc165b9e5b575dc7473f17f4b8ebbf4a7b02b3e1e642197f27b2af54680834449abaf833619ac7d18afb50b19d5f6944dca0dc952edfdd9837573783c339ee6a36353ce6e536eaaf29fcd569c426091d4e24568dc353347f98c74fb6f8c91d68d358467c437563f66566377fe6c3f9e8301dbeb5fc7e7adee7a85ef5f8fa905cedbaf26601e21ba91646cac4034601e51d889d49739ee6990943a6a41927660f68e1f50b9f9209ee29551a7dae478d88e0547eefc83334ea770bb6fbac620fc47479c2c59389622bf32f55e36a75e56a5fc47c38bf8ef211fc0e8084016313218402af50e883fc53b78468b5ea9b974275696c6465724e657420284e65746865726d696e6429a0580ca94e91c0e7aef26ffb0c86f6ae48ef40df6dd1629f203a1930e0ce0be9d188000000000000000084479c1e2aa00345740e1b79edb2fbb3a20220e1a497ea9bb82aaba7dc7a881f7f3cae8a8ea38080a06675ad2a40134499a753924a04b75898ae09efc6fba6b3d7a506203042cb7611a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";

// keccak256(_ETH_BLOCK_23270177)
bytes32 private constant _ETH_BLOCK_HASH_23270177 =
0x5def79dc43d588fafa396f3fbf0bcfb9bf83eaf8003f4508a626b6d3e806b29f;

function testBlockHash(
uint256 simulationBlockNumber,
uint256 queryBlockNumber,
Expand Down Expand Up @@ -57,4 +66,55 @@ contract BlockHashLibTest is SoladyTest {
result := mload(0x00)
}
}

function beforeTestSetup(bytes4 selector) public pure returns (bytes[] memory cd) {
if (selector == this.testToShortHeader.selector) {
cd = new bytes[](1);
cd[0] = abi.encodeWithSelector(this.checkToShortHeader.selector, _ETH_BLOCK_23270177);
}
if (selector == this.testVerifyBlockHash.selector) {
cd = new bytes[](1);
cd[0] = abi.encodeWithSelector(this.checkVerifyBlockHash.selector, _ETH_BLOCK_23270177);
}
}

function checkToShortHeader(bytes calldata h) public {
ShortHeader memory expected = ShortHeader({
parentHash: 0x1581f4448b16694d5a728161cd65f8c80b88f5352a6f5bd2d2315b970582958d,
stateRoot: 0x10d2afa5dabcf2dbfe3aa82b758427938e07880bd6fef3c82c404d0dd7c3f0f3,
transactionsRoot: 0xf81230c715a462c827898bf2e337982907a7af90e5be20f911785bda05dab93c,
receiptsRoot: 0x740f11bc75cf25e40d78d892d2e03083eaa573e5b4c26913fcc1b833db854c94,
logsBloom: [
bytes32(0x85f734fb06ea8fe377abbcb2e27f9ac99751ba817dc327327db101fd76f964ed), // lol
0x0b7ca161f148fc165b9e5b575dc7473f17f4b8ebbf4a7b02b3e1e642197f27b2,
0xaf54680834449abaf833619ac7d18afb50b19d5f6944dca0dc952edfdd983757,
0x3783c339ee6a36353ce6e536eaaf29fcd569c426091d4e24568dc353347f98c7,
0x4fb6f8c91d68d358467c437563f66566377fe6c3f9e8301dbeb5fc7e7adee7a8,
0x5ef5f8fa905cedbaf26601e21ba91646cac4034601e51d889d49739ee6990943,
0xa6a41927660f68e1f50b9f9209ee29551a7dae478d88e0547eefc83334ea770b,
0xb6fbac620fc47479c2c59389622bf32f55e36a75e56a5fc47c38bf8ef211fc0e
]
});

ShortHeader memory actual = BlockHashLib.toShortHeader(h);
assertEq(actual.parentHash, expected.parentHash, "parentHash");
assertEq(actual.stateRoot, expected.stateRoot, "stateRoot");
assertEq(actual.transactionsRoot, expected.transactionsRoot, "transactionsRoot");
assertEq(actual.receiptsRoot, expected.receiptsRoot, "receiptsRoot");
assertEq(
keccak256(abi.encodePacked(actual.logsBloom)),
keccak256(abi.encodePacked(expected.logsBloom)),
"logsBloom"
);
}

function checkVerifyBlockHash(bytes calldata h) public {
vm.roll(23270177 + 1);
vm.setBlockhash(23270177, _ETH_BLOCK_HASH_23270177);
assertEq(BlockHashLib.verifyBlockHash(h, 23270177), _ETH_BLOCK_HASH_23270177);
}

function testToShortHeader() public view {}

function testVerifyBlockHash() public view {}
}