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
5 changes: 5 additions & 0 deletions .changeset/salty-cooks-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-tron-solidity': patch
---

Trim contracts/utils/Blockhash.sol down to the native BLOCKHASH opcode only
50 changes: 10 additions & 40 deletions contracts/utils/Blockhash.sol
Original file line number Diff line number Diff line change
@@ -1,54 +1,24 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/Blockhash.sol)

pragma solidity ^0.8.20;

/**
* @dev Library for accessing historical block hashes beyond the standard 256 block limit.
* Uses EIP-2935's history storage contract which maintains a ring buffer of the last
* 8191 block hashes in state.
* @dev Library for accessing historical block hashes.
*
* For blocks within the last 256 blocks, it uses the native `BLOCKHASH` opcode.
* For blocks between 257 and 8191 blocks ago, it queries the EIP-2935 history storage.
* For blocks older than 8191 or future blocks, it returns zero, matching the `BLOCKHASH` behavior.
*
* NOTE: After EIP-2935 activation, it takes 8191 blocks to completely fill the history.
* Before that, only block hashes since the fork block will be available.
* Uses the native `BLOCKHASH` opcode, which exposes the hashes of the most
* recent 256 blocks. Queries for blocks outside that window — older than 256
* blocks, the current block, or any future block — return zero, matching the
* opcode's behavior.
*/
library Blockhash {
/// @dev Address of the EIP-2935 history storage contract.
address internal constant HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53a10cb7A02335B175320002935;

/**
* @dev Retrieves the block hash for any historical block within the supported range.
* @dev Retrieves the block hash for a historical block.
*
* NOTE: The function gracefully handles future blocks and blocks beyond the history window
* by returning zero, consistent with the EVM's native `BLOCKHASH` behavior.
* NOTE: The function gracefully handles future blocks and blocks beyond the
* 256-block window by returning zero, consistent with the EVM's native
* `BLOCKHASH` behavior.
*/
function blockHash(uint256 blockNumber) internal view returns (bytes32) {
uint256 current = block.number;
uint256 distance;

unchecked {
// Can only wrap around to `current + 1` given `block.number - (2**256 - 1) = block.number + 1`
distance = current - blockNumber;
}

return distance < 257 ? blockhash(blockNumber) : _historyStorageCall(blockNumber);
}

/// @dev Internal function to query the EIP-2935 history storage contract.
function _historyStorageCall(uint256 blockNumber) private view returns (bytes32 hash) {
assembly ("memory-safe") {
// 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)
}
return blockhash(blockNumber);
}
}
101 changes: 0 additions & 101 deletions test/utils/Blockhash.t.sol

This file was deleted.

55 changes: 15 additions & 40 deletions test/utils/Blockhash.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
const { ethers, predeploy } = require('hardhat');
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture, mineUpTo, setCode } = require('@nomicfoundation/hardhat-network-helpers');
const { impersonate } = require('../helpers/account');
const { loadFixture, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers');

const SYSTEM_ADDRESS = '0xfffffffffffffffffffffffffffffffffffffffe';
const HISTORY_SERVE_WINDOW = 8191;
const BLOCKHASH_SERVE_WINDOW = 256;

async function fixture() {
return {
mock: await ethers.deployContract('$Blockhash'),
systemSigner: await impersonate(SYSTEM_ADDRESS),
latestBlock: await ethers.provider.getBlock('latest'),
};
}
Expand All @@ -20,40 +16,19 @@ describe('Blockhash', function () {
Object.assign(this, await loadFixture(fixture));
});

for (const supported of [true, false]) {
describe(`${supported ? 'supported' : 'unsupported'} chain`, function () {
beforeEach(async function () {
if (supported) {
await this.systemSigner.sendTransaction({ to: predeploy.eip2935, data: this.latestBlock.hash });
} else {
await setCode(predeploy.eip2935.target, '0x');
}
});

it('recent block', async function () {
// fast forward (less than blockhash serve window)
await mineUpTo(this.latestBlock.number + BLOCKHASH_SERVE_WINDOW);
await expect(this.mock.$blockHash(this.latestBlock.number)).to.eventually.equal(this.latestBlock.hash);
});

it('old block', async function () {
// fast forward (more than blockhash serve window)
await mineUpTo(this.latestBlock.number + BLOCKHASH_SERVE_WINDOW + 1);
await expect(this.mock.$blockHash(this.latestBlock.number)).to.eventually.equal(
supported ? this.latestBlock.hash : ethers.ZeroHash,
);
});
it('recent block', async function () {
// fast forward (less than blockhash serve window)
await mineUpTo(this.latestBlock.number + BLOCKHASH_SERVE_WINDOW);
await expect(this.mock.$blockHash(this.latestBlock.number)).to.eventually.equal(this.latestBlock.hash);
});

it('very old block', async function () {
// fast forward (more than history serve window)
await mineUpTo(this.latestBlock.number + HISTORY_SERVE_WINDOW + 10);
await expect(this.mock.$blockHash(this.latestBlock.number)).to.eventually.equal(ethers.ZeroHash);
});
it('old block', async function () {
// fast forward (more than blockhash serve window)
await mineUpTo(this.latestBlock.number + BLOCKHASH_SERVE_WINDOW + 1);
await expect(this.mock.$blockHash(this.latestBlock.number)).to.eventually.equal(ethers.ZeroHash);
});

it('future block', async function () {
// check history access in the future
await expect(this.mock.$blockHash(this.latestBlock.number + 10)).to.eventually.equal(ethers.ZeroHash);
});
});
}
it('future block', async function () {
await expect(this.mock.$blockHash(this.latestBlock.number + 10)).to.eventually.equal(ethers.ZeroHash);
});
});
Loading