Links

Smart Contracts

Herodotus Smart Contract Workflow on L1 and L2

Overview

Herodotus smart contracts can be deployed on L1 and L2s, L3s and app-chains. In this documentation, we will focus on our deployments targetting L1 and L2.
The black arrows represent smart contract calls that can mutate the state, and the green arrows are read-only calls.
Herodotus Smart Contract Overview

Layer 1

SHARP proofs aggregators factory

Factory contract for creating new SharpFactsAggregator contracts,
These contracts are created by Clones, which ensures they are upgradeable.
The purpose of using the Factory pattern is to have the ability to manage the creation and extension of multiple MMRs. By using this approach, we can simultaneously extend the tree with recent blocks by snapshotting a recent block hash and also extend the tree to the left by performing the cryptographic link check.

Notable Functions

Function
Description
createAggregator
Orchestrates the creation of new, upgradeable SharpFactsAggregator contracts
All instances of the SharpFactsAggregatorcan be validated whether they have been created by the Factory since smart contract addresses on Ethereum are deterministic and can be computed by:
keccak256(FACTORY_ADDRESS, factoryNonceAtCloneCreationTime)[0:20]
Or by querying the variable stored in the Factory:
aggregatorsById

SHARP proofs aggregator

The important functions from the perspective of E2E integration are:

Notable Functions

Function
Description
getAggregatorState
Retrieves the current state of the SHARP proofs aggregator.
getMMRKeccakRoot
Retrieves the MMR root of the MMR hashed using Keccak.
getMMRPoseidonRoot
Retrieves the MMR root of the MMR hashed using Poseidon.
getMMRSize
Retrieves the current size of the MMR.

Messages sender

The Messages Sender contract is responsible for transmitting data(messages) to Layer 2.
The contract is used to send block hashes as well as the Poseidon or Keccak MMR roots to Layer 2. This is done by providing the identifier of the tree. It’s imperative that the contract confirms that the Factory has genuinely created the tree before proceeding with the transmission of the MMR roots.

Future work

L2 Roots Cache

The L2 Roots Cache will be built off-chain.

Layer 2

The code shown is pseudocode. The exact code can be found on our GitHub.

Inbox/Dispatcher

The role of this contract is to receive and properly handle cross-rollup messages sent from the L1: Messages sender.
Depending on the received message, the contract will pass the call over to a different contract, which is why it acts as a dispatcher.
Each time it receives the message, it emits an event.

Headers Store

The Headers Store contract is responsible for managing the Merkle Mountain Range (MMR), which stores the hashes of provably valid Ethereum block headers.
A key feature of the Header Store contract is its implementation of the Memento pattern, which lets the contract recall its past roots, ensuring that inclusion proofs remain indefinitely valid.
This contract also supports buffering or branching. New MMRs can be created and mutated independently from each other, allowing resource management and avoidance of global locks that would be required due to the non-homomorphic nature of hash-based accumulators.
Each branch of the MMR is given its own distinctive ID. Additionally, it's tagged with its present size to mark its version.

Tree creation methods

MMRs can be instantiated by calling one of these methods:
Method
Description
create_branch_single_element
Creates an empty MMR, setting the provided element as the first one.
create_branch_from_message
Utilizes a message from the Inbox, which is received from the L1MessagesSender.
create_branch_from
Creates a branch that detaches from an existing one, maintaining its root intact.

Tree growth methods

processBlocksBatch(bytes customEncodedParams)
The reason behind having customEncodedParams is that in the previous versions, we used two methods:
function process_received_block(
uint256 block_number,
bytes header_rlp,
bytes32[] mmr_peaks,
uint256 mmr_size
)
AND
function process_batch(
bytes[] headers_rlp,
bytes32[] mmr_peaks,
uint256 mmr_id,
Nullable<uint256> reference_block,
Nullable<uint256> mmr_index,
Nullable<bytes32[]> mmr_proof,
)
The main reason behind having two methods is the need for differentiation between:
  1. 1.
    Processing an element already present in the MMR implies traversing the chain of headers from the left.
  2. 2.
    Processing a recent blockhash sent through the transport layer from L1, which is not present in the MMR.

Facts Registry/Storage proofs verifier

This contract is responsible for verifying actual storage proofs and executing the final step of the workflow, which involves the verification of inclusion proofs.
The contract's main responsibilities are:
  • Verify Merkle Mountain Range (MMR) inclusion proofs.
  • Verify state/inclusion proofs related to accounts, receipts, transactions and/or proofs that attest to storage slot values.
This contract only reads data from the HeadersStore.

Notable Functions

Function
Description
get_account_field(account: address, block: uint256, field: AccountField)
Returns a cached, previously authenticated value of an account field.
get_slot_value(account: address, block: uint256, slot: bytes32)
Retrieves a cached, previously authenticated storage slot value for a given account.
get_account(fields: AccountField[], block_header_rlp: bytes, account: address, mpt_proof: bytes, mmr_index: uint256, mmr_peaks: bytes32[], mmr_proof: bytes32[], mmr_id: uint256, mmr_size: uint256)
Queries the HeadersStore with the provided parameters, ensures the provided header is valid, verifies the MPT proof, and returns the requested account fields.
get_storage(block: uint256, account: address, slot: bytes32, mpt_proof: bytes)
Verifies that an MPT proof, attesting to a value of a storage slot, is valid by checking it against a previously cached storage hash of that account and returns the storage slot value.

Future work

Batched verification

Support for batch storage proof verification will enable up to 200 data entries to be proven per one generated proof, significantly decreasing costs.
Batched verification of storage proofs will provide a less latency-optimized alternative to using the FactsRegistry.

Facts Registry

Facts Registries support for transaction_receipts and transactions.

Compressed Facts Registry

A parallel version of FactsRegistry that is fully merkelized, similar to the HeadersStore. The only difference is that it would be paged to optimize for time-series queries.

Current Deployments

Our current smart contract deployments can be found here.