Why event proofs?

Events are the de-facto way of getting data out of a blockchain. Almost from the beginning, logs were, and remain to be, a core part of the Ethereum block structure. The yellow paper baked-in a notion of “topics” that allow developers to easily tag logs with information relevant to their applications and filter them in their frontends and APIs.

  • Solidity events have been the primary (if not only) tool for developers to “send” data to code running off-chain. Solidity defines the standard for deriving topics from human-readable event names and type signatures.

  • The primary, best-supported APIs for getting data out of a node in pretty much every client library - web3.js, ethers, viem, etc - are APIs for fetching and/or subscribing to events.

  • Virtually every indexing stack - including janky in-house solutions, subgraphs, hosted services like Goldsky and Nxyz, data APIs like covalent - all ingest data from the blockchain using events.

  • A smart contract’s ABI - the primary way that off-chain code makes sense of the pile of bytes that exists in a full node’s database - do not define the storage layout. Instead, they provide event signatures, including complet types and names for each component of the event.

We don’t know anyone who builds application by reading storage values straight out of the storage trie. Virtually every application, tool, or service out there is built around events.

Events lead to an architecture that scales and generalizes in a manner such that storage does not. A storage proof consists of the following ingredients:

  1. A storage slot (or set thereof) one wishes to read

  2. A block hash at which to “read” the value

  3. The value itself

  4. A merkle proof for the value in state trie

What this means is that:

  • We can only prove data that exists in storage. But in practice, storage is extremely expensive, so smart contract developers do everything in their power to avoid putting data into storage - storage represents a small subset of the data we’d like to prove things about.

  • We need to know the precise storage slot we wish to access for our application. That means any application that doesn’t know the precise storage slots it needs at compile time will need additional logic to figure that out, and that logic will need to be proven as well. And as mentioned above, there are far fewer tools available for doing this.

  • A storage read is a “snapshot” of a particular state. Oftentimes we don’t care about the state, we care about what happened, so additional logic

Example

Uniswap pools don’t store the list of trades in storage, they store them in events. So if you wanted to get a time-weighted volume oracle, you’d need to read the pool balances on both sides of the pair at each, subtract, and then compute your time-weighted oracle atop that. What if you wanted to reward extra fees to addresses that contribute the most volume? You’re SOL because the address that invokes the swap function can only be found in the event.

In contrast, event proofs, in their “final” form, look like this:

Parse and extract every single event from every single block, convert it to a SNARK-friendly format, commit to it in a SNARK-friendly trie, and prove the two representations are equivalent.The work to extract data out of the chain is done exactly once, no matter how many applications are built, no matter what someone wants to prove.These circuits rarely, if ever, need to change, and they’re 100% decoupled from application logic. It is by default impossible for a malicious prover to “omit” data. Application developers do not need to think about how to prevent censorship.

Events are filtered by topic just like they are by indexers today, and can be further filtered or processed map-reduce-style with arbitrary, stateful logic run through a zkVM. This lets developers write pretty much whatever data pipeline they want, and, depending on the VM, in pretty much whatever language they want.

A solution based on event proofs has many properties that storage proofs do not have:

  • They natively integrate with applications the way they are built today, and can access all of the data they intended to expose.

  • The same technique can be performed over the transaction trie such that, together with storage proofs, we can prove arbitrary statements about every single byte in the blockchain.

  • Circuits that interface with the blockchain itself are totally decoupled from application-specific logic.

  • They abstract all of the thorny details away from application developers.

Last updated