// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(account),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2024
/// All rights reserved
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./BeaconBlockHistoryBase.sol";
import "./lib/Propogate.sol";
import "./interfaces/IBlockHistory.sol";
import "./interfaces/IBeaconBlockHistory.sol";
/**
* @title BeaconBlockHistory
* @author Theori, Inc.
* @notice BeaconBlockHistory allows trustless and cheap verification of any
* post-Dencun historical beacon block root. Since the beacon blocks
* contain the execution block headers, this also enables verifying
* those execution block hashes.
*
* @notice By propogating some queries to Relic's original BlockHistory contract,
* this contract enables cheap verification of *all* execution block hashes
* back to genesis.
*
* @dev This works by leveraging the native beacon block hash oracle contract introduced
* in EIP-4788: https://eips.ethereum.org/EIPS/eip-4788#block-structure-and-validity.
*
* Recent blocks (< 8191 slots old) can be accessed by directly querying the oracle.
* Then, using an SSZ merkle proof of the `BeaconState.historical_summaries` elements,
* we can verifiably access beacon block root since the Capella hardfork.
*
* To reduce redundancy, this contract supports caching each value of the
* `historical_sumaries` list. Given this cached root, block proofs can be generated
* using only `BeaconBlock` roots (and data), which are easily accessible.
*
* Execution block information can then be verified with merkle proofs of
* the `BeaconBlock.body.execution_payload.block_{number,hash}` fields.
*/
contract BeaconBlockHistory is AccessControl, BeaconBlockHistoryBase, IBlockHistory, IBeaconBlockHistory {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant QUERY_ROLE = keccak256("QUERY_ROLE");
/// @dev address of the reliquary, immutable
address public immutable reliquary;
/// @dev mapping of precomitted execution block hashes
mapping(uint256 => bytes32) private precomittedBlockHashes;
/// @dev the blockHistory which stores the data before the Dencnun fork
address public immutable preDencunBlockHistory;
/// @dev the address of the beacon oracle contract on this network
address public immutable beaconOracleContract;
/// @dev the first block number handled by this contract
uint256 public immutable UPGRADE_BLOCK;
event PrecomittedBlock(uint256 indexed blockNum, bytes32 blockHash);
/// @dev types of block proofs supported by this and prior contracts
enum ProofType {
Merkle, // legacy, not supported in this contract
SNARK, // legacy, not supported in this contract
Precomitted,
Beacon
}
constructor(
address _reliquary,
address _preDencunBlockHistory,
address _beaconOracleContract,
uint256 _CAPELLA_SLOT,
uint256 _DENEB_SLOT,
uint256 _UPGRADE_BLOCK
) BeaconBlockHistoryBase(_CAPELLA_SLOT, _DENEB_SLOT) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(ADMIN_ROLE, msg.sender);
_setupRole(QUERY_ROLE, msg.sender);
reliquary = _reliquary;
preDencunBlockHistory = _preDencunBlockHistory;
beaconOracleContract = _beaconOracleContract;
UPGRADE_BLOCK = _UPGRADE_BLOCK;
}
/**
* @notice Checks if the block is a valid precomitted block.
*
* @param hash the alleged block hash
* @param num the block number
*/
function _validPrecomittedBlock(bytes32 hash, uint256 num) internal view returns (bool) {
bytes32 stored = precomittedBlockHashes[num];
return stored != bytes32(0) && stored == hash;
}
/**
* @notice Determines if the block is accessible via the BLOCKHASH opcode
*
* @param num the block number
*/
function _isBlockhashEVMAccessible(uint256 num) internal view returns (bool) {
return num < block.number && block.number - num <= 256;
}
/**
* @notice Checks if the block is a current block (defined as being
* accessible in the EVM, i.e. <= 256 blocks old) and that the hash
* is correct.
*
* @param hash the alleged block hash
* @param num the block number
* @return the validity
*/
function _validCurrentBlock(bytes32 hash, uint256 num) internal view returns (bool) {
// the block hash must be accessible in the EVM and match
return _isBlockhashEVMAccessible(num) && (blockhash(num) == hash);
}
function _storeCommittedBlock(uint256 blockNum, bytes32 blockHash) internal {
require(blockHash != bytes32(0), "invalid blockhash");
precomittedBlockHashes[blockNum] = blockHash;
emit PrecomittedBlock(blockNum, blockHash);
}
/**
* @notice commits to a recent execution block header
* @notice reverts if the blockhash is not natively accessible
*
* @param blockNum the block number to commit
*/
function commitRecent(uint256 blockNum) external {
require(_isBlockhashEVMAccessible(blockNum), "target block not in EVM");
_storeCommittedBlock(blockNum, blockhash(blockNum));
}
/**
* @dev queries the oracle for a beacon block root
* @dev the returned root will be the parent of the block at the given timestamp
*/
function _queryBeaconRootOracle(
uint256 nextBlockTimestamp
) internal view returns (bytes32 blockRoot) {
address oracle = beaconOracleContract;
assembly {
mstore(0, nextBlockTimestamp)
let success := staticcall(gas(), oracle, 0, 0x20, 0, 0x20)
switch success
case 0 {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
default {
blockRoot := mload(0)
}
}
}
/**
* @dev uses the oracle to query for a beacon block root
*/
function _verifyOracleBlockRoot(
bytes calldata rawProof
) internal view returns (bytes32 blockRoot) {
OracleBlockRootProof calldata proof = _castOracleBlockRootProof(rawProof);
// if no summary proof is provided, use the oracle to access a recent block root
blockRoot = _queryBeaconRootOracle(proof.timestamp);
}
/**
* @dev implements beacon block root verification for L1
* @dev supports all proof types, including oracle queries
*/
function _verifyBeaconBlockRoot(
bytes calldata proof
) internal override view returns (bytes32 blockRoot) {
BeaconProofType typ;
(typ, proof) = parseBeaconProofType(proof);
if (typ == BeaconProofType.Summary) {
return _verifySummaryBlockRoot(proof);
} else if (typ == BeaconProofType.Oracle) {
return _verifyOracleBlockRoot(proof);
} else if (typ == BeaconProofType.Relative) {
return _verifyRelativeBlockRoot(proof);
} else if (typ == BeaconProofType.Header) {
return _verifyHeaderBlockRoot(proof);
} else {
revert("unsupported proof type");
}
}
/**
* @notice verifies a beacon block root
* @param proof the proof of the beacon blcok
* @return blockRoot the `BeaconBlock` root
*/
function verifyBeaconBlockRoot(
bytes calldata proof
) external view onlyRole(QUERY_ROLE) returns (bytes32 blockRoot) {
blockRoot = _verifyBeaconBlockRoot(proof);
}
/**
* @notice Parses a proof type and proof from the encoded proof
*
* @param encodedProof the encoded proof
* @return typ the proof type
* @return proof the remaining encoded proof
*/
function parseProofType(bytes calldata encodedProof)
internal
pure
returns (ProofType typ, bytes calldata proof)
{
require(encodedProof.length > 0, "cannot parse proof type");
typ = ProofType(uint8(encodedProof[0]));
proof = encodedProof[1:];
}
/**
* @notice Checks if an execution block hash is valid. A proof is required unless
* the block is current (accesible in the EVM) or precomitted.
* @notice if the target block is before the Dencun fork, the query will be propogated
* to the pre-Dencun BlockHistory contract.
*
* @param hash the hash to check
* @param num the block number for the alleged hash
* @param proof the proof (if needed)
* @return the validity
*/
function _validBlockHash(
bytes32 hash,
uint256 num,
bytes calldata proof
) internal override view returns (bool) {
// if attempting to verify an unhandled block,
// propogate the call to the legacy BlockHistory
if (num < UPGRADE_BLOCK) {
Propogate.staticcall(preDencunBlockHistory); // does not return
}
require(num < block.number, "given block is current or future block");
if (_validCurrentBlock(hash, num)) {
return true;
}
ProofType typ;
(typ, proof) = parseProofType(proof);
if (typ == ProofType.Precomitted) {
return _validPrecomittedBlock(hash, num);
} else if (typ == ProofType.Beacon) {
return _validBlockHashWithBeacon(hash, num, proof);
} else {
revert("unsupported proof type");
}
}
/**
* @notice Checks if a block hash is correct. A proof is required unless the
* block is current (accesible in the EVM) or precomitted.
* Reverts if proof is invalid.
*
* @param hash the hash to check
* @param num the block number for the alleged hash
* @param proof the merkle witness or SNARK proof (if needed)
*/
function validBlockHash(
bytes32 hash,
uint256 num,
bytes calldata proof
) external view returns (bool) {
// optimization: check if sender is reliquary first,
// so we don't touch storage in the typical path
require(msg.sender == reliquary || hasRole(QUERY_ROLE, msg.sender));
return _validBlockHash(hash, num, proof);
}
function getBlockSummary(uint256 slot) external view returns (bytes32 result) {
require(hasRole(QUERY_ROLE, msg.sender) || msg.sender == address(0));
result = _getBlockSummary(slot);
}
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2024
/// All rights reserved
pragma solidity >=0.8.0;
import "./lib/SSZ.sol";
import "./lib/CoreTypes.sol";
/**
* @title BeaconBlockHistoryBase
* @author Theori, Inc.
* @notice BeaconBlockHistoryBase implements common logic for Beacon block verification.
*
* @notice Logic which is specific to L1 or L2 is implemented in subcontracts.
*/
abstract contract BeaconBlockHistoryBase {
uint256 private constant SLOTS_PER_HISTORICAL_ROOT = 1 << 13;
/// @dev A mapping from slot number to the
// `historical_summary.block_summary_root`
mapping(uint256 => bytes32) private blockSummaries;
/// @dev the slot of the Capella fork on this network
uint256 public immutable CAPELLA_SLOT;
/// @dev the slot of the Dencun fork on this network
uint256 public immutable DENEB_SLOT;
event ImportBlockSummary(uint256 indexed slot, bytes32 summary);
/// @dev types of beacon block proofs
enum BeaconProofType {
Oracle,
Summary,
Relative,
Header
}
/// @dev a proof of a `HistoricalSummary.block_summary_root` at some slot
struct HistoricalBlockSummaryProof {
bytes beaconBlockProof;
bytes32[] slotProof;
bytes32[] stateRootProof;
uint256 blockSummaryIndex;
bytes32[] blockSummaryProof;
}
/// @dev a proof that a particular beacon block root is valid by reference to a historical summary
struct SummaryBlockRootProof {
/// @dev the proof to verify block summary
bytes summaryProof;
/// @dev the index of the slot in the summary
uint256 index;
/// @dev the summary merkle proof
bytes32[] indexProof;
}
/// @dev a proof that a particular beacon block root is valid by querying the oracle
struct OracleBlockRootProof {
/// @dev the timestamp to query the oracle with
uint256 timestamp;
}
/// @dev a proof that a particular beacon block root is valid by reference to `BeaconState.block_roots`
/// at some other beacon block
struct RelativeBlockRootProof {
/// @dev the proof of the base block root
bytes baseProof;
/// @dev the proof the base block's state root
bytes32[] stateRootProof;
/// @dev the index in the `block_roots` buffer
uint256 index;
/// @dev the proof of the entry in the state's `block_roots` vector
bytes32[] relativeRootProof;
}
/// @dev a proof that a particular beacon block root is valid by reference to the
/// `parent_beacon_block_root` field of a verifiable execution block header
struct HeaderBlockRootProof {
/// @dev the raw execution block header
bytes header;
/// @dev the proof of the header's validity (if needed)
bytes proof;
}
/// @dev a proof that a particular execution block is valid
struct ExecutionBlockProof {
/// @dev the proof of the beacon block
bytes beaconProof;
/// @dev the proof of the block's slot
bytes32[] slotProof;
/// @dev the proof of the exeuction payload in the beacon block
bytes32[] payloadProof;
}
constructor(
uint256 _CAPELLA_SLOT,
uint256 _DENEB_SLOT
) {
CAPELLA_SLOT = _CAPELLA_SLOT;
DENEB_SLOT = _DENEB_SLOT;
}
function _castHistoricalBlockSummaryProof(
bytes calldata rawProof
) internal pure returns (HistoricalBlockSummaryProof calldata proof) {
assembly {
proof := rawProof.offset
}
}
function _castOracleBlockRootProof(
bytes calldata rawProof
) internal pure returns (OracleBlockRootProof calldata proof) {
assembly {
proof := rawProof.offset
}
}
function _castSummaryBlockRootProof(
bytes calldata rawProof
) internal pure returns (SummaryBlockRootProof calldata proof) {
assembly {
proof := rawProof.offset
}
}
function _castRelativeBlockRootProof(
bytes calldata rawProof
) internal pure returns (RelativeBlockRootProof calldata proof) {
assembly {
proof := rawProof.offset
}
}
function _castHeaderBlockRootProof(
bytes calldata rawProof
) internal pure returns (HeaderBlockRootProof calldata proof) {
assembly {
proof := rawProof.offset
}
}
function _castExecutionBlockProof(
bytes calldata rawProof
) internal pure returns (ExecutionBlockProof calldata proof) {
assembly {
proof := rawProof.offset
}
}
/**
* @notice verifies and caches a `HistoricalSummary` root at some beacon block
* @param proof the proof required to access the root
* @dev requires the slot to be aligned to SLOTS_PER_HISTORICAL_ROOT
*/
function cacheBlockSummary(bytes calldata proof) external {
(uint256 slot, bytes32 blockSummary) = _verifyBlockSummary(proof);
blockSummaries[slot] = blockSummary;
emit ImportBlockSummary(slot, blockSummary);
}
/**
* @notice returns a cached block summary
* @param slot the slot for the summary
*/
function _getBlockSummary(uint256 slot) internal view returns (bytes32 result) {
result = blockSummaries[slot];
}
/**
* @dev either accesses a cached historical block summary or verifies a proof of one
* @dev If verifying a proof, it first verifies that a beacon block root is valid, and then
* verifies the SSZ proof of `BeaconState.historical_summaries[idx].block_summary_root`.
* @return slot the slot number of this historical summary
* @return blockSummary the block summary root
*/
function _verifyBlockSummary(
bytes calldata rawProof
) internal view returns (uint256 slot, bytes32 blockSummary) {
// check if the proof references a cached summary
if (rawProof.length == 32) {
// load the cached summary
slot = uint256(bytes32(rawProof));
blockSummary = blockSummaries[slot];
require(blockSummary != bytes32(0), "block summary not cached");
} else {
HistoricalBlockSummaryProof calldata proof = _castHistoricalBlockSummaryProof(rawProof);
// first verify a beacon block root
bytes32 blockRoot = _verifyBeaconBlockRoot(proof.beaconBlockProof);
uint256 baseSlot = SSZ.verifyBlockSlot(proof.slotProof, blockRoot);
// now access the block's state root
bytes32 stateRoot = SSZ.verifyBlockStateRoot(proof.stateRootProof, blockRoot);
// finally, extract the block summary field from the target state
blockSummary = SSZ.verifyHistoricalBlockSummary(
proof.blockSummaryProof,
proof.blockSummaryIndex,
stateRoot
);
// compute the slot for this summary - note that summaries started at Capella
slot = CAPELLA_SLOT + SLOTS_PER_HISTORICAL_ROOT * (proof.blockSummaryIndex + 1);
// ensure the base slot actually contains this summary
require(baseSlot >= slot, "index out of bounds");
}
}
/**
* @dev uses a beacon block summary proof to verify a block root
*/
function _verifySummaryBlockRoot(
bytes calldata rawProof
) internal view returns (bytes32 blockRoot) {
SummaryBlockRootProof calldata proof = _castSummaryBlockRootProof(rawProof);
// otherwise use a block summary to access the block root
(uint256 baseSlot, bytes32 blockSummary) = _verifyBlockSummary(proof.summaryProof);
assert(baseSlot % SLOTS_PER_HISTORICAL_ROOT == 0);
uint256 index = proof.index;
require(index < SLOTS_PER_HISTORICAL_ROOT, "invalid index");
blockRoot = SSZ.verifySummaryIndex(
proof.indexProof,
index,
blockSummary
);
}
/**
* @dev uses the `block_roots` vector of another accessible block root
*/
function _verifyRelativeBlockRoot(
bytes calldata rawProof
) internal view returns (bytes32 blockRoot) {
RelativeBlockRootProof calldata proof = _castRelativeBlockRootProof(rawProof);
// first verify the base block root
blockRoot = _verifyBeaconBlockRoot(proof.baseProof);
// now access the base block's state root
bytes32 stateRoot = SSZ.verifyBlockStateRoot(proof.stateRootProof, blockRoot);
uint256 index = proof.index;
require(index < SLOTS_PER_HISTORICAL_ROOT, "block_roots index out of bounds");
// verify the target block root relative to the base block root
blockRoot = SSZ.verifyRelativeBlockRoot(
proof.relativeRootProof,
index,
stateRoot
);
require(blockRoot != bytes32(0), "invalid blockRoot proven");
}
/**
* @dev uses the `parent_beacon_block_root` field of a verifiable execution header
* to verify a beacon block root
*/
function _verifyHeaderBlockRoot(
bytes calldata rawProof
) internal view returns (bytes32 blockRoot) {
HeaderBlockRootProof calldata proof = _castHeaderBlockRootProof(rawProof);
// hash and parse the provided header
bytes32 blockHash = keccak256(proof.header);
CoreTypes.BlockHeaderData memory header = CoreTypes.parseBlockHeader(proof.header);
require(
_validBlockHash(blockHash, header.Number, proof.proof),
"block hash not valid"
);
blockRoot = header.ParentBeaconBlockRoot;
require(blockRoot != bytes32(0), "header does not contain parent_beacon_block_root");
}
/**
* @dev verifies an execution layer block hash using SSZ merkle proofs.
*/
function _verifyELBlockData(
bytes calldata rawProof
) internal view returns (bytes32 blockHash, uint256 blockNum) {
ExecutionBlockProof calldata proof = _castExecutionBlockProof(rawProof);
// verify the beacon block root is valid
bytes32 blockRoot = _verifyBeaconBlockRoot(proof.beaconProof);
// verify the block slot number to determine which hardfork it's from
uint256 slot = SSZ.verifyBlockSlot(proof.slotProof, blockRoot);
require(slot >= CAPELLA_SLOT, "slot is before capella fork");
bool isCapella = slot < DENEB_SLOT;
// verify the execution header data within it
(blockHash, blockNum) = SSZ.verifyExecutionPayloadFields(
proof.payloadProof,
blockRoot,
isCapella
);
}
/**
* @dev verifies an execution layer block hash using SSZ merkle proofs.
* Returns true if the data is valid. May either revert or return
* false if the proof is invalid.
*/
function _validBlockHashWithBeacon(
bytes32 hash,
uint256 num,
bytes calldata rawProof
) internal view returns (bool) {
(bytes32 blockHash, uint256 blockNum) = _verifyELBlockData(rawProof);
// return whether it matches the query
return hash == blockHash && num == blockNum;
}
/**
* @notice Parses a beacon proof type and proof from the encoded proof
*
* @param encodedProof the encoded proof
* @return typ the proof type
* @return proof the remaining encoded proof
*/
function parseBeaconProofType(bytes calldata encodedProof)
internal
pure
returns (BeaconProofType typ, bytes calldata proof)
{
require(encodedProof.length > 0, "cannot parse beacon proof type");
typ = BeaconProofType(uint8(encodedProof[0]));
proof = encodedProof[1:];
}
function _verifyBeaconBlockRoot(
bytes calldata proof
) internal virtual view returns (bytes32 blockRoot);
/**
* @notice Checks if an execution block hash is valid.
*
* @param hash the hash to check
* @param num the block number for the alleged hash
* @param proof the merkle witness or SNARK proof (if needed)
* @return the validity
*/
function _validBlockHash(
bytes32 hash,
uint256 num,
bytes calldata proof
) internal virtual view returns (bool);
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
pragma solidity >=0.8.13;
// custom bytes calldata pointer storing (length | offset) in one word,
// also allows calldata pointers to be stored in memory
type BytesCalldata is uint256;
using BytesCalldataOps for BytesCalldata global;
// can't introduce global using .. for non UDTs
// each consumer should add the following line:
using BytesCalldataOps for bytes;
/**
* @author Theori, Inc
* @title BytesCalldataOps
* @notice Common operations for bytes calldata, implemented for both the builtin
* type and our BytesCalldata type. These operations are heavily optimized
* and omit safety checks, so this library should only be used when memory
* safety is not a security issue.
*/
library BytesCalldataOps {
function length(BytesCalldata bc) internal pure returns (uint256 result) {
assembly {
result := shr(128, shl(128, bc))
}
}
function offset(BytesCalldata bc) internal pure returns (uint256 result) {
assembly {
result := shr(128, bc)
}
}
function convert(BytesCalldata bc) internal pure returns (bytes calldata value) {
assembly {
value.offset := shr(128, bc)
value.length := shr(128, shl(128, bc))
}
}
function convert(bytes calldata inp) internal pure returns (BytesCalldata bc) {
assembly {
bc := or(shl(128, inp.offset), inp.length)
}
}
function slice(
BytesCalldata bc,
uint256 start,
uint256 len
) internal pure returns (BytesCalldata result) {
assembly {
result := shl(128, add(shr(128, bc), start)) // add to the offset and clear the length
result := or(result, len) // set the new length
}
}
function slice(
bytes calldata value,
uint256 start,
uint256 len
) internal pure returns (bytes calldata result) {
assembly {
result.offset := add(value.offset, start)
result.length := len
}
}
function prefix(BytesCalldata bc, uint256 len) internal pure returns (BytesCalldata result) {
assembly {
result := shl(128, shr(128, bc)) // clear out the length
result := or(result, len) // set it to the new length
}
}
function prefix(bytes calldata value, uint256 len)
internal
pure
returns (bytes calldata result)
{
assembly {
result.offset := value.offset
result.length := len
}
}
function suffix(BytesCalldata bc, uint256 start) internal pure returns (BytesCalldata result) {
assembly {
result := add(bc, shl(128, start)) // add to the offset
result := sub(result, start) // subtract from the length
}
}
function suffix(bytes calldata value, uint256 start)
internal
pure
returns (bytes calldata result)
{
assembly {
result.offset := add(value.offset, start)
result.length := sub(value.length, start)
}
}
function split(BytesCalldata bc, uint256 start)
internal
pure
returns (BytesCalldata, BytesCalldata)
{
return (prefix(bc, start), suffix(bc, start));
}
function split(bytes calldata value, uint256 start)
internal
pure
returns (bytes calldata, bytes calldata)
{
return (prefix(value, start), suffix(value, start));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
pragma solidity >=0.8.0;
import "./BytesCalldata.sol";
import "./RLP.sol";
/**
* @title CoreTypes
* @author Theori, Inc.
* @notice Data types and parsing functions for core types, including block headers
* and account data.
*/
library CoreTypes {
using BytesCalldataOps for bytes;
struct BlockHeaderData {
bytes32 ParentHash;
address Coinbase;
bytes32 Root;
bytes32 TxHash;
bytes32 ReceiptHash;
uint256 Number;
uint256 GasLimit;
uint256 GasUsed;
uint256 Time;
bytes32 MixHash;
uint256 BaseFee;
bytes32 WithdrawalsHash;
uint256 BlobGasUsed;
uint256 ExcessBlobGas;
bytes32 ParentBeaconBlockRoot;
}
struct AccountData {
uint256 Nonce;
uint256 Balance;
bytes32 StorageRoot;
bytes32 CodeHash;
}
struct LogData {
address Address;
bytes32[] Topics;
bytes Data;
}
struct WithdrawalData {
uint256 Index;
uint256 ValidatorIndex;
address Address;
uint256 AmountInGwei;
}
function parseHash(bytes calldata buf) internal pure returns (bytes32 result, uint256 offset) {
uint256 value;
(value, offset) = RLP.parseUint(buf);
result = bytes32(value);
}
function parseAddress(bytes calldata buf)
internal
pure
returns (address result, uint256 offset)
{
uint256 value;
(value, offset) = RLP.parseUint(buf);
result = address(uint160(value));
}
function parseBlockHeader(bytes calldata header)
internal
pure
returns (BlockHeaderData memory data)
{
(uint256 listSize, uint256 offset) = RLP.parseList(header);
header = header.slice(offset, listSize);
(data.ParentHash, offset) = parseHash(header); // ParentHash
header = header.suffix(offset);
header = RLP.skip(header); // UncleHash
(data.Coinbase, offset) = parseAddress(header); // Coinbase
header = header.suffix(offset);
(data.Root, offset) = parseHash(header); // Root
header = header.suffix(offset);
(data.TxHash, offset) = parseHash(header); // TxHash
header = header.suffix(offset);
(data.ReceiptHash, offset) = parseHash(header); // ReceiptHash
header = header.suffix(offset);
header = RLP.skip(header); // Bloom
header = RLP.skip(header); // Difficulty
(data.Number, offset) = RLP.parseUint(header); // Number
header = header.suffix(offset);
(data.GasLimit, offset) = RLP.parseUint(header); // GasLimit
header = header.suffix(offset);
(data.GasUsed, offset) = RLP.parseUint(header); // GasUsed
header = header.suffix(offset);
(data.Time, offset) = RLP.parseUint(header); // Time
header = header.suffix(offset);
header = RLP.skip(header); // Extra
(data.MixHash, offset) = parseHash(header); // MixHash
header = header.suffix(offset);
header = RLP.skip(header); // Nonce
if (header.length > 0) {
(data.BaseFee, offset) = RLP.parseUint(header); // BaseFee
header = header.suffix(offset);
}
if (header.length > 0) {
(data.WithdrawalsHash, offset) = parseHash(header); // WithdrawalsHash
header = header.suffix(offset);
}
if (header.length > 0) {
(data.BlobGasUsed, offset) = RLP.parseUint(header); // BlobGasUsed
header = header.suffix(offset);
}
if (header.length > 0) {
(data.ExcessBlobGas, offset) = RLP.parseUint(header); // ExcessBlobGas
header = header.suffix(offset);
}
if (header.length > 0) {
(data.ParentBeaconBlockRoot, offset) = parseHash(header); // ParentBeaconBlockRoot
header = header.suffix(offset);
}
}
function getBlockHeaderHashAndSize(bytes calldata header)
internal
pure
returns (bytes32 blockHash, uint256 headerSize)
{
(uint256 listSize, uint256 offset) = RLP.parseList(header);
unchecked {
headerSize = offset + listSize;
}
blockHash = keccak256(header.prefix(headerSize));
}
function parseAccount(bytes calldata account) internal pure returns (AccountData memory data) {
(, uint256 offset) = RLP.parseList(account);
account = account.suffix(offset);
(data.Nonce, offset) = RLP.parseUint(account); // Nonce
account = account.suffix(offset);
(data.Balance, offset) = RLP.parseUint(account); // Balance
account = account.suffix(offset);
(data.StorageRoot, offset) = parseHash(account); // StorageRoot
account = account.suffix(offset);
(data.CodeHash, offset) = parseHash(account); // CodeHash
account = account.suffix(offset);
}
function parseLog(bytes calldata log) internal pure returns (LogData memory data) {
(, uint256 offset) = RLP.parseList(log);
log = log.suffix(offset);
uint256 tmp;
(tmp, offset) = RLP.parseUint(log); // Address
data.Address = address(uint160(tmp));
log = log.suffix(offset);
(tmp, offset) = RLP.parseList(log); // Topics
bytes calldata topics = log.slice(offset, tmp);
log = log.suffix(offset + tmp);
require(topics.length % 33 == 0);
data.Topics = new bytes32[](tmp / 33);
uint256 i = 0;
while (topics.length > 0) {
(data.Topics[i], offset) = parseHash(topics);
topics = topics.suffix(offset);
unchecked {
i++;
}
}
(data.Data, ) = RLP.splitBytes(log);
}
function extractLog(bytes calldata receiptValue, uint256 logIdx)
internal
pure
returns (LogData memory)
{
// support EIP-2718: Currently all transaction types have the same
// receipt RLP format, so we can just skip the receipt type byte
if (receiptValue[0] < 0x80) {
receiptValue = receiptValue.suffix(1);
}
(, uint256 offset) = RLP.parseList(receiptValue);
receiptValue = receiptValue.suffix(offset);
// pre EIP-658, receipts stored an intermediate state root in this field
// post EIP-658, the field is a tx status (0 for failure, 1 for success)
uint256 statusOrIntermediateRoot;
(statusOrIntermediateRoot, offset) = RLP.parseUint(receiptValue);
require(statusOrIntermediateRoot != 0, "tx did not succeed");
receiptValue = receiptValue.suffix(offset);
receiptValue = RLP.skip(receiptValue); // GasUsed
receiptValue = RLP.skip(receiptValue); // LogsBloom
uint256 length;
(length, offset) = RLP.parseList(receiptValue); // Logs
receiptValue = receiptValue.slice(offset, length);
// skip the earlier logs
for (uint256 i = 0; i < logIdx; i++) {
require(receiptValue.length > 0, "log index does not exist");
receiptValue = RLP.skip(receiptValue);
}
return parseLog(receiptValue);
}
function parseWithdrawal(bytes calldata withdrawal)
internal
pure
returns (WithdrawalData memory data)
{
(, uint256 offset) = RLP.parseList(withdrawal);
withdrawal = withdrawal.suffix(offset);
(data.Index, offset) = RLP.parseUint(withdrawal); // Index
withdrawal = withdrawal.suffix(offset);
(data.ValidatorIndex, offset) = RLP.parseUint(withdrawal); // ValidatorIndex
withdrawal = withdrawal.suffix(offset);
(data.Address, offset) = parseAddress(withdrawal); // Address
withdrawal = withdrawal.suffix(offset);
(data.AmountInGwei, offset) = RLP.parseUint(withdrawal); // Amount
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32")
mstore(0x1c, hash)
message := keccak256(0x00, 0x3c)
}
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, "\x19\x01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
data := keccak256(ptr, 0x42)
}
}
/**
* @dev Returns an Ethereum Signed Data with intended validator, created from a
* `validator` and `data` according to the version 0 of EIP-191.
*
* See {recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x00", validator, data));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
pragma solidity >=0.8.0;
import "./IBlockHistory.sol";
/**
* @title Beacon Block history provider
* @author Theori, Inc.
* @notice IBeaconBlockHistory provides a way to verify beacon block roots as well as execution block hashes
*/
interface IBeaconBlockHistory is IBlockHistory {
function UPGRADE_BLOCK() external view returns (uint256 blockNum);
/**
* @notice verifies a beacon block root
* @param proof the proof of the beacon blcok
* @return blockRoot the `BeaconBlock` root
*/
function verifyBeaconBlockRoot(
bytes calldata proof
) external view returns (bytes32 blockRoot);
/**
* @notice gets the cached block summary for the given slot (if it exists)
* @param slot the slot number to query
* @return result the cached block summary (or bytes32(0) if it is not cached)
*/
function getBlockSummary(uint256 slot) external view returns (bytes32 result);
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
pragma solidity >=0.8.0;
/**
* @title Block history provider
* @author Theori, Inc.
* @notice IBlockHistory provides a way to verify a blockhash
*/
interface IBlockHistory {
/**
* @notice Determine if the given hash corresponds to the given block
* @param hash the hash if the block in question
* @param num the number of the block in question
* @param proof any witness data required to prove the block hash is
* correct (such as a Merkle or SNARK proof)
* @return boolean indicating if the block hash can be verified correct
*/
function validBlockHash(
bytes32 hash,
uint256 num,
bytes calldata proof
) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
pragma solidity >=0.8.0;
/**
* @title Merkle Tree
* @author Theori, Inc.
* @notice Gas optimized SHA256 Merkle tree code.
*/
library MerkleTree {
/**
* @notice performs one merkle combination of two node hashes
*/
function combine(bytes32 left, bytes32 right) internal view returns (bytes32 result) {
assembly {
mstore(0, left)
mstore(0x20, right)
// compute sha256
if iszero(staticcall(gas(), 0x2, 0x0, 0x40, 0x0, 0x20)) {
revert(0, 0)
}
result := mload(0)
}
}
/**
* @notice computes a SHA256 merkle root of the provided hashes, in place
* @param temp the mutable array of hashes
* @return the merkle root hash
*/
function computeRoot(bytes32[] memory temp) internal view returns (bytes32) {
uint256 count = temp.length;
assembly {
// repeat until we arrive at one root hash
for {
} gt(count, 1) {
} {
let dataElementLocation := add(temp, 0x20)
let hashElementLocation := add(temp, 0x20)
for {
let i := 0
} lt(i, count) {
i := add(i, 2)
} {
if iszero(
staticcall(gas(), 0x2, hashElementLocation, 0x40, dataElementLocation, 0x20)
) {
revert(0, 0)
}
dataElementLocation := add(dataElementLocation, 0x20)
hashElementLocation := add(hashElementLocation, 0x40)
}
count := shr(1, count)
}
}
return temp[0];
}
/**
* @notice compute the root of the merkle tree according to the proof
* @param index the index of the node to check
* @param leaf the leaf to check
* @param proofHashes the proof, i.e. the sequence of siblings from the
* node to root
*/
function proofRoot(
uint256 index,
bytes32 leaf,
bytes32[] calldata proofHashes
) internal view returns (bytes32 result) {
assembly {
result := leaf
let start := proofHashes.offset
let end := add(start, mul(proofHashes.length, 0x20))
for {
let ptr := start
} lt(ptr, end) {
ptr := add(ptr, 0x20)
} {
let proofHash := calldataload(ptr)
// use scratch space (0x0 - 0x40) for hash input
switch and(index, 1)
case 0 {
mstore(0x0, result)
mstore(0x20, proofHash)
}
case 1 {
mstore(0x0, proofHash)
mstore(0x20, result)
}
// compute sha256
if iszero(staticcall(gas(), 0x2, 0x0, 0x40, 0x0, 0x20)) {
revert(0, 0)
}
result := mload(0x0)
index := shr(1, index)
}
}
require(index == 0, "invalid index for proof");
}
/**
* @notice compute the root of the merkle tree containing the given leaf
* at index 0 and default values for all other leaves
* @param depth the depth of the tree
* @param leaf the non-default leaf
* @param defaultLeaf the default leaf for all other positions
*/
function rootWithDefault(
uint256 depth,
bytes32 leaf,
bytes32 defaultLeaf
) internal view returns (bytes32 result) {
assembly {
result := leaf
// the default value will live at 0x20 and be updated each iteration
mstore(0x20, defaultLeaf)
for { } depth { depth := sub(depth, 1) } {
// compute sha256 of result || default
mstore(0x0, result)
if iszero(staticcall(gas(), 0x2, 0x0, 0x40, 0x0, 0x20)) {
revert(0, 0)
}
result := mload(0x0)
if iszero(depth) {
break
}
// compute sha256 of default || default
mstore(0x0, mload(0x20))
if iszero(staticcall(gas(), 0x2, 0x0, 0x40, 0x20, 0x20)) {
revert(0, 0)
}
}
}
}
/**
* @notice check if a hash is in the merkle tree for rootHash
* @param rootHash the merkle root
* @param index the index of the node to check
* @param hash the hash to check
* @param proofHashes the proof, i.e. the sequence of siblings from the
* node to root
*/
function validProof(
bytes32 rootHash,
uint256 index,
bytes32 hash,
bytes32[] calldata proofHashes
) internal view returns (bool result) {
return rootHash == proofRoot(index, hash, proofHashes);
}
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
/*
* @author Theori, Inc.
*/
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
uint256 constant BASE_PROOF_SIZE = 34;
uint256 constant SUBPROOF_LIMBS_SIZE = 16;
struct RecursiveProof {
uint256[BASE_PROOF_SIZE] base;
uint256[SUBPROOF_LIMBS_SIZE] subproofLimbs;
uint256[] inputs;
}
struct SignedRecursiveProof {
RecursiveProof inner;
bytes signature;
}
/**
* @notice recover the signer of the proof
* @param proof the SignedRecursiveProof
* @return the address of the signer
*/
function getProofSigner(SignedRecursiveProof calldata proof) pure returns (address) {
bytes32 msgHash = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n", "32", hashProof(proof.inner))
);
return ECDSA.recover(msgHash, proof.signature);
}
/**
* @notice hash the contents of a RecursiveProof
* @param proof the RecursiveProof
* @return result a 32-byte digest of the proof
*/
function hashProof(RecursiveProof calldata proof) pure returns (bytes32 result) {
uint256[] calldata inputs = proof.inputs;
assembly {
let ptr := mload(0x40)
let contigLen := mul(0x20, add(BASE_PROOF_SIZE, SUBPROOF_LIMBS_SIZE))
let inputsLen := mul(0x20, inputs.length)
calldatacopy(ptr, proof, contigLen)
calldatacopy(add(ptr, contigLen), inputs.offset, inputsLen)
result := keccak256(ptr, add(contigLen, inputsLen))
}
}
/**
* @notice reverse the byte order of a uint256
* @param input the input value
* @return v the byte-order reversed value
*/
function byteReverse(uint256 input) pure returns (uint256 v) {
v = input;
uint256 MASK08 = 0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00;
uint256 MASK16 = 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000;
uint256 MASK32 = 0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000;
uint256 MASK64 = 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000;
// swap bytes
v = ((v & MASK08) >> 8) | ((v & (~MASK08)) << 8);
// swap 2-byte long pairs
v = ((v & MASK16) >> 16) | ((v & (~MASK16)) << 16);
// swap 4-byte long pairs
v = ((v & MASK32) >> 32) | ((v & (~MASK32)) << 32);
// swap 8-byte long pairs
v = ((v & MASK64) >> 64) | ((v & (~MASK64)) << 64);
// swap 16-byte long pairs
v = (v >> 128) | (v << 128);
}
/**
* @notice reads a 32-byte hash from its little-endian word-encoded form
* @param words the hash words
* @return the hash
*/
function readHashWords(uint256[] calldata words) pure returns (bytes32) {
uint256 mask = 0xffffffffffffffff;
uint256 result = (words[0] & mask);
result |= (words[1] & mask) << 0x40;
result |= (words[2] & mask) << 0x80;
result |= (words[3] & mask) << 0xc0;
return bytes32(byteReverse(result));
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
pragma solidity >=0.8.0;
library Propogate {
/**
* @notice propogates the current calldata to the destination
* via a staticcall() and returns or reverts accordingly
* @dev this is much cheaper than manually building the calldata again
*/
function staticcall(address destination) internal view {
assembly {
// we are not returning to solidity, so we can take ownership of all memory
calldatacopy(0, 0, calldatasize())
let success := staticcall(gas(), destination, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
// Depending on the success, either revert or return
switch success
case 0 {
// End execution and revert state changes
revert(0, returndatasize())
}
default {
// Return data with length of size at pointers position
return(0, returndatasize())
}
}
}
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
pragma solidity >=0.8.0;
/**
* @title RLP
* @author Theori, Inc.
* @notice Gas optimized RLP parsing code. Note that some parsing logic is
* duplicated because helper functions are oddly expensive.
*/
library RLP {
function parseUint(bytes calldata buf) internal pure returns (uint256 result, uint256 size) {
assembly {
// check that we have at least one byte of input
if iszero(buf.length) {
revert(0, 0)
}
let first32 := calldataload(buf.offset)
let kind := shr(248, first32)
// ensure it's a not a long string or list (> 0xB7)
// also ensure it's not a short string longer than 32 bytes (> 0xA0)
if gt(kind, 0xA0) {
revert(0, 0)
}
switch lt(kind, 0x80)
case true {
// small single byte
result := kind
size := 1
}
case false {
// short string
size := sub(kind, 0x80)
// ensure it's not reading out of bounds
if lt(buf.length, size) {
revert(0, 0)
}
switch eq(size, 32)
case true {
// if it's exactly 32 bytes, read it from calldata
result := calldataload(add(buf.offset, 1))
}
case false {
// if it's < 32 bytes, we've already read it from calldata
result := shr(shl(3, sub(32, size)), shl(8, first32))
}
size := add(size, 1)
}
}
}
function nextSize(bytes calldata buf) internal pure returns (uint256 size) {
assembly {
if iszero(buf.length) {
revert(0, 0)
}
let first32 := calldataload(buf.offset)
let kind := shr(248, first32)
switch lt(kind, 0x80)
case true {
// small single byte
size := 1
}
case false {
switch lt(kind, 0xB8)
case true {
// short string
size := add(1, sub(kind, 0x80))
}
case false {
switch lt(kind, 0xC0)
case true {
// long string
let lengthSize := sub(kind, 0xB7)
// ensure that we don't overflow
if gt(lengthSize, 31) {
revert(0, 0)
}
// ensure that we don't read out of bounds
if lt(buf.length, lengthSize) {
revert(0, 0)
}
size := shr(mul(8, sub(32, lengthSize)), shl(8, first32))
size := add(size, add(1, lengthSize))
}
case false {
switch lt(kind, 0xF8)
case true {
// short list
size := add(1, sub(kind, 0xC0))
}
case false {
let lengthSize := sub(kind, 0xF7)
// ensure that we don't overflow
if gt(lengthSize, 31) {
revert(0, 0)
}
// ensure that we don't read out of bounds
if lt(buf.length, lengthSize) {
revert(0, 0)
}
size := shr(mul(8, sub(32, lengthSize)), shl(8, first32))
size := add(size, add(1, lengthSize))
}
}
}
}
}
}
function skip(bytes calldata buf) internal pure returns (bytes calldata) {
uint256 size = RLP.nextSize(buf);
assembly {
buf.offset := add(buf.offset, size)
buf.length := sub(buf.length, size)
}
return buf;
}
function parseList(bytes calldata buf)
internal
pure
returns (uint256 listSize, uint256 offset)
{
assembly {
// check that we have at least one byte of input
if iszero(buf.length) {
revert(0, 0)
}
let first32 := calldataload(buf.offset)
let kind := shr(248, first32)
// ensure it's a list
if lt(kind, 0xC0) {
revert(0, 0)
}
switch lt(kind, 0xF8)
case true {
// short list
listSize := sub(kind, 0xC0)
offset := 1
}
case false {
// long list
let lengthSize := sub(kind, 0xF7)
// ensure that we don't overflow
if gt(lengthSize, 31) {
revert(0, 0)
}
// ensure that we don't read out of bounds
if lt(buf.length, lengthSize) {
revert(0, 0)
}
listSize := shr(mul(8, sub(32, lengthSize)), shl(8, first32))
offset := add(lengthSize, 1)
}
}
}
function splitBytes(bytes calldata buf)
internal
pure
returns (bytes calldata result, bytes calldata rest)
{
uint256 offset;
uint256 size;
assembly {
// check that we have at least one byte of input
if iszero(buf.length) {
revert(0, 0)
}
let first32 := calldataload(buf.offset)
let kind := shr(248, first32)
// ensure it's a not list
if gt(kind, 0xBF) {
revert(0, 0)
}
switch lt(kind, 0x80)
case true {
// small single byte
offset := 0
size := 1
}
case false {
switch lt(kind, 0xB8)
case true {
// short string
offset := 1
size := sub(kind, 0x80)
}
case false {
// long string
let lengthSize := sub(kind, 0xB7)
// ensure that we don't overflow
if gt(lengthSize, 31) {
revert(0, 0)
}
// ensure we don't read out of bounds
if lt(buf.length, lengthSize) {
revert(0, 0)
}
size := shr(mul(8, sub(32, lengthSize)), shl(8, first32))
offset := add(lengthSize, 1)
}
}
result.offset := add(buf.offset, offset)
result.length := size
let end := add(offset, size)
rest.offset := add(buf.offset, end)
rest.length := sub(buf.length, end)
}
}
function encodeUint(uint256 value) internal pure returns (bytes memory) {
// allocate our result bytes
bytes memory result = new bytes(33);
if (value == 0) {
// store length = 1, value = 0x80
assembly {
mstore(add(result, 1), 0x180)
}
return result;
}
if (value < 128) {
// store length = 1, value = value
assembly {
mstore(add(result, 1), or(0x100, value))
}
return result;
}
if (value > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) {
// length 33, prefix 0xa0 followed by value
assembly {
mstore(add(result, 1), 0x21a0)
mstore(add(result, 33), value)
}
return result;
}
if (value > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) {
// length 32, prefix 0x9f followed by value
assembly {
mstore(add(result, 1), 0x209f)
mstore(add(result, 33), shl(8, value))
}
return result;
}
assembly {
let length := 1
for {
let min := 0x100
} lt(sub(min, 1), value) {
min := shl(8, min)
} {
length := add(length, 1)
}
let bytesLength := add(length, 1)
// bytes length field
let hi := shl(mul(bytesLength, 8), bytesLength)
// rlp encoding of value
let lo := or(shl(mul(length, 8), add(length, 0x80)), value)
mstore(add(result, bytesLength), or(hi, lo))
}
return result;
}
}
/// SPDX-License-Identifier: UNLICENSED
/// (c) Theori, Inc. 2022
/// All rights reserved
pragma solidity >=0.8.0;
import "./MerkleTree.sol";
import {byteReverse} from "./Proofs.sol";
/**
* @title SSZ
* @author Theori, Inc.
* @notice Selected SSZ merkle verification code for Beacon chain data structures
*
* @dev The indices hardcoded in this contract are primarily for the Dencun hardfork.
* One exception is verifying execution payload fields, where Capella is also
* supported. Also, this contract uses raw merkle indices rather than the
* "generalized indices" specified in SSZ.
*
*/
library SSZ {
// the total proof length for a historical block summaries proof
uint256 constant HISTORICAL_SUMMARIES_TREE_DEPTH = 24;
// the total proof length for a historical block summaries proof
uint256 constant HISTORICAL_BLOCK_SUMMARIES_PROOF_LENGTH = 32;
// index of the block_roots merkle root relative to a block root
uint256 constant STATE_ROOT_INDEX = 3;
// index of the block_roots field relative to a state root
uint256 constant BLOCK_ROOTS_INDEX = 5;
// index of the historical_summaries field relative to a state root
uint256 constant HISTORICAL_SUMMARIES_INDEX = 27;
// index of the slot value relative to a block root
uint256 constant SLOT_INDEX = 0;
// index of the execution payload relative to a block root
uint256 constant EXECUTION_PAYLOAD_INDEX = 73;
// index of the block number in the left subtree of the execution payload
uint256 constant BLOCK_NUMBER_LEFT_SUBTREE_INDEX = 6;
// index of the block hash in the right subtree of the execution payload
uint256 constant BLOCK_HASH_RIGHT_SUBTREE_INDEX = 4;
/**
* @notice verify an SSZ merkle proof for `BeaconBlock.body.execution_payload.block_{number,hash}`
*/
function verifyExecutionPayloadFields(
bytes32[] calldata proof,
bytes32 blockRoot,
bool isCapella
) internal view returns (bytes32 blockHash, uint256 blockNumber) {
if (isCapella) {
require(proof.length == 15, "invalid proof length");
} else {
require(proof.length == 16, "invalid proof length");
}
blockHash = proof[0];
bytes32 blockNumberAsHash = proof[1];
bytes32 rightSubtreeRoot = MerkleTree.proofRoot(
BLOCK_HASH_RIGHT_SUBTREE_INDEX,
blockHash,
proof[2:5]
);
bytes32 leftSubtreeRoot = MerkleTree.proofRoot(
BLOCK_NUMBER_LEFT_SUBTREE_INDEX,
blockNumberAsHash,
proof[5:8]
);
bytes32 executionPayloadSubtreeRoot = MerkleTree.combine(leftSubtreeRoot, rightSubtreeRoot);
// if in capella, we're already at the execution payload root
// otherwise, we need one extra proof node
bytes32 computedRoot = MerkleTree.proofRoot(
isCapella ? EXECUTION_PAYLOAD_INDEX : EXECUTION_PAYLOAD_INDEX << 1,
executionPayloadSubtreeRoot,
proof[8:]
);
require(computedRoot == blockRoot, "invalid execution proof");
blockNumber = byteReverse(uint256(blockNumberAsHash));
}
/**
* @notice verify an SSZ merkle proof for `BeaconBlock.state_root`
*/
function verifyBlockStateRoot(
bytes32[] calldata proof,
bytes32 blockRoot
) internal view returns (bytes32 stateRoot) {
require(proof.length == 4, "invalid proof length");
stateRoot = proof[0];
bytes32 computedRoot = MerkleTree.proofRoot(STATE_ROOT_INDEX, stateRoot, proof[1:]);
require(computedRoot == blockRoot, "invalid stateRoot proof");
}
/**
* @notice verify an SSZ merkle proof for `BeaconBlock.slot`
*/
function verifyBlockSlot(
bytes32[] calldata proof,
bytes32 blockRoot
) internal view returns (uint256 slot) {
require(proof.length == 4, "invalid proof length");
bytes32 slotAsHash = proof[0];
bytes32 computedRoot = MerkleTree.proofRoot(SLOT_INDEX, slotAsHash, proof[1:]);
require(computedRoot == blockRoot, "invalid slot proof");
slot = byteReverse(uint256(slotAsHash));
require(slot <= type(uint64).max, "invalid slot value");
}
/**
* @notice verifies `state.historical_summaries[index].block_summary_root`
*/
function verifyHistoricalBlockSummary(
bytes32[] calldata proof,
uint256 index,
bytes32 stateRoot
) internal view returns (bytes32 historicalBlockSummary) {
// proof length is an upper bound in this case, see below
require(proof.length <= HISTORICAL_BLOCK_SUMMARIES_PROOF_LENGTH, "proof too long");
historicalBlockSummary = proof[0];
bytes32 historicalSummary = MerkleTree.combine(historicalBlockSummary, proof[1]);
bytes32[] calldata topProof = proof[2:8];
bytes32 intermediate = MerkleTree.proofRoot(
index,
historicalSummary,
proof[8:]
);
// any missing proof nodes are implicit "default" values on the right side of the tree
uint256 numImplicitNodes = HISTORICAL_BLOCK_SUMMARIES_PROOF_LENGTH - proof.length;
// compute the defaultValue for our current depth
bytes32 defaultValue = bytes32(0);
for (uint256 i = 0; i < HISTORICAL_SUMMARIES_TREE_DEPTH - numImplicitNodes; i++) {
defaultValue = MerkleTree.combine(defaultValue, defaultValue);
}
// compute the historical_summaries data root assuming default value
bytes32 listDataRoot = MerkleTree.rootWithDefault(
numImplicitNodes,
intermediate,
defaultValue
);
// finally, compute the overall state root
bytes32 computedRoot = MerkleTree.proofRoot(
HISTORICAL_SUMMARIES_INDEX << 1, // one extra proof node on the right for the list length
listDataRoot,
topProof
);
require(computedRoot == stateRoot, "invalid summary proof");
}
/**
* @notice verifies `state.block_roots[index]`
*/
function verifyRelativeBlockRoot(
bytes32[] calldata proof,
uint256 index,
bytes32 stateRoot
) internal view returns (bytes32 blockRoot) {
require(proof.length == 19, "invalid proof length");
blockRoot = proof[0];
bytes32 vectorRoot = MerkleTree.proofRoot(
index,
blockRoot,
proof[1:14]
);
bytes32 computedRoot = MerkleTree.proofRoot(
BLOCK_ROOTS_INDEX,
vectorRoot,
proof[14:]
);
require(computedRoot == stateRoot, "invalid relative proof");
}
/**
* @notice verify an SSZ merkle proof for a Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
* @dev intended to be used with block summaries, i.e. `BeaconState.block_roots`
*/
function verifySummaryIndex(
bytes32[] calldata proof,
uint256 index,
bytes32 summaryRoot
) internal view returns (bytes32 blockRoot) {
require(proof.length == 14, "invalid proof length");
blockRoot = proof[0];
bytes32 computedRoot = MerkleTree.proofRoot(index, blockRoot, proof[1:]);
require(computedRoot == summaryRoot, "invalid summary proof");
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
{
"compilationTarget": {
"contracts/BeaconBlockHistory.sol": "BeaconBlockHistory"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_reliquary","type":"address"},{"internalType":"address","name":"_preDencunBlockHistory","type":"address"},{"internalType":"address","name":"_beaconOracleContract","type":"address"},{"internalType":"uint256","name":"_CAPELLA_SLOT","type":"uint256"},{"internalType":"uint256","name":"_DENEB_SLOT","type":"uint256"},{"internalType":"uint256","name":"_UPGRADE_BLOCK","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"slot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"summary","type":"bytes32"}],"name":"ImportBlockSummary","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"blockNum","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"name":"PrecomittedBlock","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CAPELLA_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DENEB_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"QUERY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UPGRADE_BLOCK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"beaconOracleContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"proof","type":"bytes"}],"name":"cacheBlockSummary","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNum","type":"uint256"}],"name":"commitRecent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"slot","type":"uint256"}],"name":"getBlockSummary","outputs":[{"internalType":"bytes32","name":"result","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"preDencunBlockHistory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reliquary","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"num","type":"uint256"},{"internalType":"bytes","name":"proof","type":"bytes"}],"name":"validBlockHash","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"proof","type":"bytes"}],"name":"verifyBeaconBlockRoot","outputs":[{"internalType":"bytes32","name":"blockRoot","type":"bytes32"}],"stateMutability":"view","type":"function"}]