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
41 changes: 41 additions & 0 deletions src/utils/LibBlockHash.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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)
}
}
}
2 changes: 2 additions & 0 deletions src/utils/LibTransient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ library LibTransient {
_compat(ptr)._spacer = 0;
}

/// @dev

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ADDRESS OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand Down
70 changes: 70 additions & 0 deletions test/LibBlockHash.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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));
}
}
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