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
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ auto_detect_solc = false
optimizer = true
optimizer_runs = 1_000
gas_limit = 100_000_000 # ETH is 30M, but we use a higher value.
skip = ["*/*7702*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"]
skip = ["*/*7702*", "*/*BlockHashLib*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"]
fs_permissions = [{ access = "read", path = "./test/data"}]
remappings = [
"forge-std=test/utils/forge-std/"
Expand Down
38 changes: 38 additions & 0 deletions src/utils/BlockHashLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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/BlockHashLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Blockhash.sol)
library BlockHashLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @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 result) {
unchecked {
// If `blockNumber + 256` overflows:
// - Typical chain height (`block.number > 255`) -> `staticcall` -> 0.
// - Very early chain (`block.number <= 255`) -> `blockhash` -> 0.
if (block.number <= blockNumber + 256) return blockhash(blockNumber);
}
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, blockNumber)
mstore(0x00, 0)
pop(staticcall(gas(), HISTORY_STORAGE_ADDRESS, 0x20, 0x20, 0x00, 0x20))
result := mload(0x00)
}
}
}
58 changes: 58 additions & 0 deletions test/BlockHashLib.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

contract BlockHashLibTest is SoladyTest {
uint256 internal startingBlock;

address internal constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE;

bytes private constant _HISTORY_STORAGE_BYTECODE =
hex"3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500";

function testBlockHash(
uint256 simulationBlockNumber,
uint256 queryBlockNumber,
uint256 savedBlockedNumber,
bytes32 h
) public {
if (_randomChance(2)) {
vm.etch(BlockHashLib.HISTORY_STORAGE_ADDRESS, _HISTORY_STORAGE_BYTECODE);
}

savedBlockedNumber = _bound(savedBlockedNumber, 0, 2 ** 64 - 1);

vm.roll(savedBlockedNumber + 1);
vm.prank(SYSTEM_ADDRESS);
(bool success,) = BlockHashLib.HISTORY_STORAGE_ADDRESS.call(abi.encode(h));
require(success);

vm.setBlockhash(savedBlockedNumber, h);

vm.roll(simulationBlockNumber);

assertEq(BlockHashLib.blockHash(queryBlockNumber), _blockHash(queryBlockNumber));
}

function _blockHash(uint256 blockNumber) internal view returns (bytes32) {
(bool success, bytes memory result) =
address(this).staticcall(abi.encodeWithSignature("blockHash(uint256)", blockNumber));
if (!success) return 0;
return abi.decode(result, (bytes32));
}

function blockHash(uint256 blockNumber) public view returns (bytes32 result) {
if (block.number <= blockNumber + 256) return blockhash(blockNumber);
address a = BlockHashLib.HISTORY_STORAGE_ADDRESS;
if (a.code.length == 0) return 0;
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, blockNumber)
mstore(0x00, 0)
pop(staticcall(gas(), a, 0x20, 0x20, 0x00, 0x20))
result := mload(0x00)
}
}
}
3 changes: 3 additions & 0 deletions test/utils/forge-std/Vm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading