Optimism Smart Contract Breakdown
How Rollups Work at the Code Level

Optimism is an optimistic rollup built on top of Ethereum. What’s an optimistic rollup? And how does it work at the code level? This article will explain.
We will also cover why rollups need inter-chain communication and how this communication is implemented. We will see the actual code snippets that implement the most important functionality of rollups.
Here is the outline of this article
- What’s an optimistic rollup?
- A high-level overview of Optimism contracts
- Code for L1 — L2 bridge
- Code for rolling up transactions
- Code for disputes
What’s an optimistic rollup?
First, what is a rollup? It’s one of the ways to make Ethereum more efficient, commonly known as L2 solutions. There are 3 L2 solution types: state channels, plasma, and rollups. I have an article coming up on “Classification of L2 solutions” soon which will cover this in detail. Here is a short summary of what a rollup and specifically, an optimistic rollup is.
There is a smart contract on Ethereum (call it RollupL1
) that allows deposits/withdrawals of ETH. When your money is in RollupL1
, you can consider it to be in L2. The L2 money moves much faster than L1 money because L2 transactions (txns) are much more efficient and faster. How is this accomplished?
There is a program that lives outside of Ethereum (call it RollupL2
). It can process txns much faster because it does not have to go through Ethereum’s slow and expensive consensus mechanism. It can process a bunch of txns, combine them (roll them up) into a batch, and submit the batch to RollupL1
.
RollupL2
can be another smart contract that lives on a faster blockchain or it can be a traditional web2 server. There are pros and cons to each approach such as latency and decentralization.
By processing txns off-chain, you save from 2 axes:
- data compression: a batch takes up less space than individual txns stacked on top of each other. See this section to understand why.
- you only have to go through Ethereum’s slow and expensive consensus only once.
There is another saving axis: you don’t need to calculate the new state after each txn on Ethereum. You see, when you submit a txn on Ethereum directly, Ethereum needs to calculate the new state of accounts. This is expensive. By offloading this work to an L2, you avoid this expensive computation on Ethereum.
But should RollupL1
just trust the new state submitted to it by RollupL2
? Should it verify? If it verifies, it wastes the same computation so the point of rollups is lost.
Optimistic rollups get around this by blind trust: they simply trust the newly submitted state without doing any verification (they are very optimistic ✨). But, they lock up the newly submitted batch for a week (called the “challenge window”). Anyone can submit mathematical proof during this challenge window and receive a reward if they find a fraudulent state update. If the batch is not disputed during the week, it’s considered final.
The reward is financed by the deposit of those who submit the batch. If you want to submit a batch, you need to submit a deposit.
That’s how optimistic rollups work at a high level. ZK-rollups work differently (read my L2 article).
A high-level overview of Optimism contracts
Optimistic rollups need 3 functionality at a high level:
- a 2-way bridge to move money between L1 and L2
- processing transactions and rolling them up into a batch
- disputes/proof of invalid state update
Here is a diagram of Optimism smart contracts that implement the above:

Let’s now take a look at the actual code for the most important parts.
Code for L1 — L2 bridge
The bridge works by locking up funds on L1 and minting the equivalent on L2. To withdraw funds, the bridge burns the L2 funds and releases the locked L1 funds.
Here is the function for depositing funds:
The function is part of the L1StandardBridge contract which lives on Ethereum. It’s very simple: accept ETH (done automatically with the payable
keyword), encode all parameters of the function into a message, and send the message to a cross-domain messenger.
The cross-domain messenger broadcasts messages between L1 and L2. We will cover it in a bit.
There is a corresponding function for listening to these messages on L2. L2StandardBridge
contract does that. This contract lives on a separate L2 blockchain (that is faster than Ethereum).
The function just runs a few checks and mints new tokens. I should have mentioned that you can move arbitrary ERC-20 tokens using this bridge, not just ETH (ETH is just wrapped in an ERC-20 interface).
There are corresponding functions for moving funds from L2 to L1. Also done using a x-domain messenger. I will skip them for brevity.
Cross-domain messaging
The communication between L1 and L2 happens via a x-domain messenger contract (there is a copy on each chain). Internally, this contract just stores the message and relies on “relayers” to notify the other chain (L1 or L2) of a new message.
There is no native L1 ↔ L2 communication. There are functions such as
onNewMessage
on each side and relayers are supposed to call them using traditional web2 HTTPs.
For example, here is how L1 → L2 transactions is stored/enqueued on L1:
Relayers would notify L2 that there is a new message in the queue.
Code for rolling up transactions
There is a sequencer on Optimism whose job is to accept L2 transactions, check for their validity, and apply a state update to its local state as a pending block. These pending blocks are periodically submitted in large batches to Ethereum (L1) for finalization.
The function accepting these batches on Ethereum is appendSequencerBatch
which is part of the CanonicalTransactionChain
contract on L1. Internally, appendSequencerBatch
uses the function below to process batches.
batchesRef
is a helper contract used for data storage. That’s where batches are stored.- the function first computes the batch header and then computes its hash.
- it then computes the batch context. The batch header and context are just additional information about the batch.
- it then stores the hash and context in the storage (
batchesRef
).
Later, hash and context will be used to validate disputes.
Right now, the sequencer role, which rolls up transactions into a batch and submits them, is centralized — controlled by the Optimism org. But they have a plan to decentralize this role in the future. You can also submit your own batches to CanonicalTransactionChain
directly, without going through the sequencer, but it would be more expensive because the fixed cost of submitting the batch is paid entirely by you and is not amortized over many different transactions.
Code for disputes
At a high level, disputes work by submitting proof that one state update is invalid and validating this proof against stored state updates (stored batch metadata: hash and context).
The contract responsible for handling disputes is OVMFraudVerifier
. This contract is part of OVM — Optimism virtual machine (similar to EVM — Ethereum virtual machine). Here is the main functions for disputes:
finalizeFraudVerification
checks if_postStateRoot
(submitted by the verifier) is not equal to the root that was submitted by the sequencer.- if it’s not, then we delete the batch in
_cancelStateTransition
and slash the deposit of the sequencer (in order to become a sequencer, you need to lock up a deposit. When you submit a fraudulent batch, your deposit is slashed and this money goes to the verifier as an incentive to keep the entire mechanism going).
That’s all for the Optimism smart contract breakdown. I hope you feel optimistic about the future of Ethereum and L2s! Let me know if you have any questions in the comments.
I am planning to do more breakdowns of popular smart contracts like the Lens Protocol and MakerDAO.
You can also check out breakdowns of other smart contracts and more stuff for Solidity noobs at solidnoob.com.
Want to Connect?Follow me on Twitter.