From af3fa4b406a0ca0e0d5b15405a9e728257830205 Mon Sep 17 00:00:00 2001 From: developeruche Date: Sat, 16 Aug 2025 00:00:05 +0100 Subject: [PATCH 1/2] impl LibBlockHash with test --- src/utils/LibBlockHash.sol | 42 ++++++++++++++++++++++ test/LibBlockHash.t.sol | 69 +++++++++++++++++++++++++++++++++++++ test/utils/forge-std/Vm.sol | 3 ++ 3 files changed, 114 insertions(+) create mode 100644 src/utils/LibBlockHash.sol create mode 100644 test/LibBlockHash.t.sol diff --git a/src/utils/LibBlockHash.sol b/src/utils/LibBlockHash.sol new file mode 100644 index 0000000000..a3bdba14f0 --- /dev/null +++ b/src/utils/LibBlockHash.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + + +/// @notice Library for accessing block hashes way beyond the 256-block limit. ref: EIP-2935 +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBlockHash.sol) +library LibBlockHash { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Address of the EIP-2935 history storage contract. + /// See: https://eips.ethereum.org/EIPS/eip-2935 + address internal constant HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53a10cb7A02335B175320002935; + + /// @dev Retrieves the block hash for any historical block within the supported range. + /// The function gracefully handles future blocks and blocks beyond the history window by returning zero, + /// consistent with the EVM's native `BLOCKHASH` behavior. + function blockHash(uint256 blockNumber) internal view returns (bytes32 hash) { + assembly { + let current := number() + let distance := sub(current, blockNumber) + + // Check if distance < 257 + if lt(distance, 257) { + // Return blockhash(blockNumber) + mstore(0x00, blockhash(blockNumber)) + return(0x00, 0x20) + } + + // Store the blockNumber in scratch space + mstore(0x00, blockNumber) + mstore(0x20, 0) + + // call history storage address + pop(staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0x00, 0x20, 0x20, 0x20)) + + // load result + hash := mload(0x20) + } + } +} \ No newline at end of file diff --git a/test/LibBlockHash.t.sol b/test/LibBlockHash.t.sol new file mode 100644 index 0000000000..ee00975dd3 --- /dev/null +++ b/test/LibBlockHash.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "./utils/SoladyTest.sol"; +import {LibBlockHash} from "../src/utils/LibBlockHash.sol"; + +contract LibBlockHashTest is SoladyTest { + uint256 internal startingBlock; + + address internal constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; + + bytes private constant _HISTORY_STORAGE_BYTECODE = + hex"3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500"; + + function setUp() public { + vm.roll(block.number + 100); + startingBlock = block.number; + vm.etch(LibBlockHash.HISTORY_STORAGE_ADDRESS, _HISTORY_STORAGE_BYTECODE); + } + + function __blockHash(uint256 blockNumber, bytes32 expectedHash, bytes32 sysExpectedHash) internal view returns (bool) { + if (expectedHash != sysExpectedHash) return false; + return sysExpectedHash == LibBlockHash.blockHash(blockNumber); + } + + function testFuzzRecentBlocks(uint8 offset, uint64 currentBlock, bytes32 expectedHash) public { + // Recent blocks (1-256 blocks old) + uint256 boundedOffset = uint256(offset) + 1; + vm.assume(currentBlock > boundedOffset); + vm.roll(currentBlock); + + uint256 targetBlock = currentBlock - boundedOffset; + vm.setBlockhash(targetBlock, expectedHash); + + assertTrue(__blockHash(targetBlock, expectedHash, blockhash(targetBlock))); + } + + function testFuzzVeryOldBlocks(uint256 offset, uint256 currentBlock) public { + // Very old blocks (>8191 blocks old) + offset = _bound(offset, 8192, type(uint256).max); + vm.assume(currentBlock > offset); + vm.roll(currentBlock); + + uint256 targetBlock = currentBlock - offset; + assertTrue(__blockHash(targetBlock, bytes32(0), bytes32(0))); + } + + function testFuzzFutureBlocks(uint256 offset, uint256 currentBlock) public { + // Future blocks + offset = _bound(offset, 1, type(uint256).max); + currentBlock = _bound(currentBlock, 0, type(uint256).max - offset); + vm.roll(currentBlock); + + unchecked { + uint256 targetBlock = currentBlock + offset; + assertTrue(__blockHash(targetBlock, blockhash(targetBlock), blockhash(targetBlock))); + } + } + + function testUnsupportedChainsReturnZeroWhenOutOfRange() public { + vm.etch(LibBlockHash.HISTORY_STORAGE_ADDRESS, hex""); + + vm.roll(block.number + 1000); + assertEq(LibBlockHash.blockHash(block.number - 1000), bytes32(0)); + } +} + + + diff --git a/test/utils/forge-std/Vm.sol b/test/utils/forge-std/Vm.sol index 2614f4ecba..dc644b9c26 100644 --- a/test/utils/forge-std/Vm.sol +++ b/test/utils/forge-std/Vm.sol @@ -1988,4 +1988,7 @@ interface Vm is VmSafe { /// Stops all safe memory expectation in the current subcontext. function stopExpectSafeMemory() external; + + /// Sets the blockhash for a given block number. + function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; } From f19c6ac8188e190337c321cb2c4cc9476ae12ae0 Mon Sep 17 00:00:00 2001 From: developeruche Date: Sat, 16 Aug 2025 21:47:58 +0100 Subject: [PATCH 2/2] fmt --- src/utils/LibBlockHash.sol | 9 ++++----- src/utils/LibTransient.sol | 2 ++ test/LibBlockHash.t.sol | 11 ++++++----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/utils/LibBlockHash.sol b/src/utils/LibBlockHash.sol index a3bdba14f0..62012815f7 100644 --- a/src/utils/LibBlockHash.sol +++ b/src/utils/LibBlockHash.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; - /// @notice Library for accessing block hashes way beyond the 256-block limit. ref: EIP-2935 /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBlockHash.sol) library LibBlockHash { @@ -14,20 +13,20 @@ library LibBlockHash { address internal constant HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53a10cb7A02335B175320002935; /// @dev Retrieves the block hash for any historical block within the supported range. - /// The function gracefully handles future blocks and blocks beyond the history window by returning zero, + /// The function gracefully handles future blocks and blocks beyond the history window by returning zero, /// consistent with the EVM's native `BLOCKHASH` behavior. function blockHash(uint256 blockNumber) internal view returns (bytes32 hash) { assembly { let current := number() let distance := sub(current, blockNumber) - + // Check if distance < 257 if lt(distance, 257) { // Return blockhash(blockNumber) mstore(0x00, blockhash(blockNumber)) return(0x00, 0x20) } - + // Store the blockNumber in scratch space mstore(0x00, blockNumber) mstore(0x20, 0) @@ -39,4 +38,4 @@ library LibBlockHash { hash := mload(0x20) } } -} \ No newline at end of file +} diff --git a/src/utils/LibTransient.sol b/src/utils/LibTransient.sol index b7151a4198..614ba336d7 100644 --- a/src/utils/LibTransient.sol +++ b/src/utils/LibTransient.sol @@ -406,6 +406,8 @@ library LibTransient { _compat(ptr)._spacer = 0; } + /// @dev + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ADDRESS OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ diff --git a/test/LibBlockHash.t.sol b/test/LibBlockHash.t.sol index ee00975dd3..6cfbc7e52c 100644 --- a/test/LibBlockHash.t.sol +++ b/test/LibBlockHash.t.sol @@ -18,8 +18,12 @@ contract LibBlockHashTest is SoladyTest { vm.etch(LibBlockHash.HISTORY_STORAGE_ADDRESS, _HISTORY_STORAGE_BYTECODE); } - function __blockHash(uint256 blockNumber, bytes32 expectedHash, bytes32 sysExpectedHash) internal view returns (bool) { - if (expectedHash != sysExpectedHash) return false; + function __blockHash(uint256 blockNumber, bytes32 expectedHash, bytes32 sysExpectedHash) + internal + view + returns (bool) + { + if (expectedHash != sysExpectedHash) return false; return sysExpectedHash == LibBlockHash.blockHash(blockNumber); } @@ -64,6 +68,3 @@ contract LibBlockHashTest is SoladyTest { assertEq(LibBlockHash.blockHash(block.number - 1000), bytes32(0)); } } - - -