Smart Contracts
Herodotus Smart Contract Workflow on L1 and L2
Last updated
Herodotus Smart Contract Workflow on L1 and L2
Last updated
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.
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.
All instances of the SharpFactsAggregator
can 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
The important functions from the perspective of E2E integration are:
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.
The L2 Roots Cache will be built off-chain.
The code shown is pseudocode. The exact code can be found on our GitHub.
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.
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.
MMRs can be instantiated by calling one of these methods:
processBlocksBatch(bytes customEncodedParams)
The reason behind having customEncodedParams is that in the previous versions, we used two methods:
AND
The main reason behind having two methods is the need for differentiation between:
Processing an element already present in the MMR implies traversing the chain of headers from the left.
Processing a recent blockhash
sent through the transport layer from L1, which is not present in the MMR.
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
.
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 Registries support for transaction_receipts
and transactions
.
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.
Our current smart contract deployments can be found here.
Function | Description |
---|---|
Function | Description |
---|---|
Method | Description |
---|---|
Function | Description |
---|---|
createAggregator
Orchestrates the creation of new, upgradeable SharpFactsAggregator contracts
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.
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.
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.