top of page

Como a Optimism funciona?


Below is a brief summary of some of the planned Optimism roadmap (opens new window)releases.

#Next gen fault proofs

As part of the OVM 2.0 upgrade, the Optimism fault proof mechanism had to be temporarily disabled. This means that users of the OP Mainnet network currently need to trust the Sequencer node (run by Optimism Foundation) to publish valid state roots to Ethereum. You can read more about our security model here.

We're making progress on the upgrade fault proof mechanism and we expect to productionize our work in 2023. You can keep up with developments in the Cannon repository (opens new window).

#Decentralizing the sequencer

Currently, the Optimism Foundation runs the sole sequencer on OP Mainnet. This does not mean that Optimism can censor user transactions. However, it is still desirable to decentralize the sequencer over time, eliminating Optimism's role entirely so that anyone can participate in the network as a block producer.

The first step to decentralizing the sequencer is to still have one sequencer at a time, but rotate that sequencer with some frequency. The precise mechanic for sequencer rotation is not yet finalized, but will involve two components:

  • an economic mechanism which creates a competitive market for sequencing, and redirects excess sequencer profits towards protocol development (opens new window).

  • a governance mechanism which prevents sequencers from prioritizing short-term profits over the long-term health of the network.

After this, the next step is to support multiple concurrent sequencers. This can be simply achieved by adopting a standard BFT consensus protocol, as used by other L1 protocols and sidechains like Polygon and Cosmos.

You can keep up with the roadmap progress in Cannon repository (opens new window)for the fault proofs and Optimism specs repository (opens new window)for the overall protocol work.

Design Philosophy

On this page


 Discord community

 Get support for going live

 Make an issue on GitHub

 Contribute to Optimism

Optimism is built according to a strong design philosophy that stands on four main pillars: simplicity, pragmatism, sustainability, and, of course, optimism. It's important to understand these pillars as they heavily influence the design of Optimism as a whole.


Optimism is designed to be as simple as possible for the featureset it provides. Ideally, Optimism should be composed of the minimum number of moving parts required for a secure, scalable, and flexible L2 system. This simplicity gives Optimism's design a number of significant advantages over other more complex L2 constructions.

Simplicity reduces engineering overhead, which in turn means we can spend our time working on new features instead of re-creating existing ones. Optimism prefers to use existing battle-tested Ethereum code and infrastructure where possible. The most visible example of this philosophy in practice is the choice to use Geth as Optimism's client software.

When dealing with critical infrastructure, simplicity is also security. Every line of code we write is an opportunity to introduce unintentional bugs. A simple protocol means there's less code to write and, as a result, less surface area for potential mistakes. A clean and minimal codebase is also more accessible to external contributors and auditors. All of this serves to maximize the security and correctness of the Optimism protocol.

Simplicity is also important for the long-term vision of Optimism. By limiting the amount of code that we write on top of Ethereum tooling, we're able to spend most of our time working directly with existing codebases. Engineering effort that goes into Optimism can also directly benefit Ethereum, and vice versa. This will only become more pronounced as the Optimism protocol solidifies and existing resources can be redirected towards core Ethereum infrastructure.


For all its idealism, the design process behind Optimism is ultimately driven by pragmatism. The core Optimism team has real-world constraints, the projects that build on Optimism have real-world needs, and the users that engage with Optimism have real-world problems. Optimism's design philosophy prioritizes user and developer needs over theoretical perfection. Sometimes the best solution isn't the prettiest one.

Optimism is also developed with the understanding that any core team will have limited areas of expertise. Optimism is developed iteratively and strives to continuously pull feedback from users. Many core Optimism features today (like EVM Equivalence (opens new window)) were only made possible by this iterative approach to protocol development.


Optimism is in it for the long haul. Application developers need assurance that the platform they're building on will remain not only operational but competitive over long periods of time. Optimism's design process is built around the idea of long-term sustainability and not taking shortcuts to scalability. At the end of the day, a scalable system means nothing without the ecosystem that sustains it.

Sustainability actively influences Optimism's protocol design in ways that go hand-in-hand with our philosophy of simplicity. The more complex a codebase, the more difficult it is for people outside of the core development team to actively contribute. By keeping our codebase simple we're able to build a bigger community of contributors who can help maintain the protocol long-term.


Of course, none of this would be possible without a sense of optimism. Our optimism about the Ethereum vision keeps this project moving forward. We believe in an optimistic future for Ethereum, a future where we get to redesign our relationships with the institutions that coordinate our lives.

Although Optimism looks like a standalone blockchain, it's ultimately designed as an extension to Ethereum. We keep this in mind whenever we're creating new features or trying to simplify existing ones. Optimism is as close to Ethereum as possible not only for pragmatic reasons, but because Optimism exists so that Ethereum can succeed. We hope that you can see the influence of this philosophy when looking at Optimism's design.

Rollup Protocol

On this page


 Discord community

 Get support for going live

 Make an issue on GitHub

 Contribute to Optimism

The big idea that makes Optimism possible is the Optimistic Rollup. We'll go through a brief explainer of how Optimistic Rollups work at a high level. Then we'll explain why Optimism is built as an Optimistic Rollup and why we believe it's the best option for a system that addresses all of our design goals.

#Optimistic Rollups TL;DR

Optimism is an "Optimistic Rollup," which is basically just a fancy way of describing a blockchain that piggy-backs off of the security of another "parent" blockchain. Specifically, Optimistic Rollups take advantage of the consensus mechanism (like PoW or PoS) of their parent chain instead of providing their own. In OP Mainnet's case this parent blockchain is Ethereum.

#Block storage

In Bedrock L2 blocks are saved to the Ethereum blockchain using a non-contract address (0xff00...0420 on Goerli (opens new window)0xff00..0010 on Ethereum (opens new window)) to minimize the L1 gas expense. As these blocks are submitted as transaction calldata on Ethereum, there is no way to modify or censor them after the "transaction" is included in a block that has enough attestations. This is the way that OP Mainnet inherits the availability and integrity guarantees of Ethereum.

Blocks are written to L1 in a compressed format (opens new window)to reduce costs. This is important because writing to L1 is the major cost of OP Mainnet transactions.

#Block production

Optimism block production is primarily managed by a single party, called the "sequencer," which helps the network by providing the following services:

  • Providing transaction confirmations and state updates.

  • Constructing and executing L2 blocks.

  • Submitting user transactions to L1.

In Bedrock the sequencer does have a mempool, similar to L1 Ethereum, but the mempool is private to avoid opening opportunities for MEV. In OP Mainnet blocks are produced every two seconds, regardless of whether they are empty (no transactions), filled up to the block gas limit with transactions, or anything in between.

Transactions get to the sequencer in two ways:

  1. Transactions submitted on L1 (called deposits whether they have assets attached or not) are included in the chain in the appropriate L2 block. Every L2 block is identified by the "epoch" (the L1 block to which it corresponds, which typically has happened a few minutes before the L2 block) and its serial number within that epoch. The first block of the epoch includes all the deposits that happened in the L1 block to which it corresponds. If the sequencer attempts to ignore a legitimate L1 transaction it ends up with a state that is inconsistent with the verifiers, same as if the sequencer tried to fake the state by other means. This provides OP Mainnet with L1 Ethereum level censorship resistance. You can read more about this mechanism in the protocol specifications (opens new window).

  2. Transactions submitted directly to the sequencer. These transactions are a lot cheaper to submit (because you do not need the expense of a separate L1 transaction), but of course they cannot be made censorship resistant, because the sequencer is the only entity that knows about them.

For the moment, The Optimism Foundation (opens new window)runs the only block producer on OP Mainnet. Refer to Protocol specs section for more information about how we plan to decentralize the Sequencer role in the future.

#Block execution

The execution engine (implemented as the op-geth component) receive blocks using two mechanisms:

  1. The execution engine can update itself using peer to peer network with other execution engines. This operates the same way that the L1 execution clients synchronize the state across the network. You can read more about it in the specs (opens new window).

  2. The rollup node (implemented as the op-node component) derives the L2 blocks from L1. This mechanism is slower, but censorship resistant. You can read more about it in the specs (opens new window).

#Bridging assets between layers

Optimism is designed so that users can send arbitrary messages between smart contracts on L2 (OP Mainnet, OP Goerli, etc.) and the underlying L1 (Ethereum mainnet, Goerli, etc.). This makes it possible to transfer assets, including ERC20 tokens, between the two networks. The exact mechanism by which this communication occurs differs depending on the direction in which messages are being sent.

OP Mainnet uses this functionality in the Standard bridge to allow users to deposit assets (ERC20s and ETH) from Ethereum to OP Mainnet and also allow withdrawals of the same from OP Mainnet back to Ethereum. See the developer documentation and examples on details on the inner workings of the Standard bridge.

#Moving from Ethereum to OP Mainnet

In Optimism terminology, transactions going from Ethereum (L1) to OP Mainnet (L2) are called deposits, even if they do not have any assets attached to them.

You use L1CrossDomainMessenger (opens new window)or L1StandardBridge (opens new window). Deposit transactions become part of the canonical blockchain in the first L2 block of the "epoch" corresponding to the L1 block where the deposits were made. This L2 block will usually be created a few minutes after the corresponding L1 block. You can read more about this in the specs (opens new window).

#Moving from OP Mainnet to Ethereum

Withdrawals (the term is used for any OP Mainnet to Ethereum message, regardless of whether it has attached assets or not) have three stages:

  1. You initialize withdrawals with an L2 transaction.

  2. Wait for the next output root to be submitted to L1 (you can see this on the SDK) and then submit the withdrawal proof using proveWithdrawalTransaction. This new step enables offchain monitoring of the withdrawals, which makes it easier to identify incorrect withdrawals or output roots. This protects OP Mainnet users against a whole class of potential bridge vulnerabilities.

  3. After the fault challenge period ends (a week on mainnet, less than that on the test network), finalize the withdrawal.

You can read the full withdrawal specifications here(opens new window)

#Fault proofs

In an Optimistic Rollup, state commitments are published to L1 (Ethereum in the case of OP Mainnet) without any direct proof of the validity of these commitments. Instead, these commitments are considered pending for a period of time (called the "challenge window"). If a proposed state commitment goes unchallenged for the duration of the challenge window (currently set to 7 days), then it is considered final. Once a commitment is considered final, smart contracts on Ethereum can safely accept withdrawal proofs about the state of OP Mainnet based on that commitment.

When a state commitment is challenged, it can be invalidated through a "fault proof" (formerly known as a "fraud proof" (opens new window)) process. If the commitment is successfully challenged, then it is removed from the StateCommitmentChain to eventually be replaced by another proposed commitment. It's important to note that a successful challenge does not roll back OP Mainnet itself, only the published commitments about the state of the chain. The ordering of transactions and the state of OP Mainnet is unchanged by a fault proof challenge.

The fault proof process is currently undergoing major redevelopment as a side-effect of the November 11th EVM Equivalence (opens new window)update. You can read more about this process within the Protocol specs section of this website.

Contract Overview

On this page


 Discord community

 Get support for going live

 Make an issue on GitHub

 Contribute to Optimism

#L1 contracts


The L2OutputOracle contract (opens new window)contains the state root of the Optimism blockchain (OP Mainnet, OP Goerli, etc.). Once fault proofs are activated, it will be the one that receives the result of the fault proof process.

This is the contract that replaces the old State Commitment Chain.


The OptimismPortal contract (opens new window)provides the low-level API for communications between layers. Unless you are trying to send L2 transactions via L1 to bypass the sequencer, we strongly recommend sending messages between L1 and L2 via the L1CrossDomainMessenger and L2CrossDomainMessenger.


The L1CrossDomainMessenger contract (opens new window)is used for sending messages between the underlying L1 (Ethereum, Goerli, etc.) and L2 (OP Mainnet, OP Goerli, etc.). Those messages may or may not have assets attached to them.


The L1StandardBridge contract (opens new window)uses L1CrossDomainMessenger to transfer ETH and ERC-20 tokens between the underlying L1 (Ethereum, Goerli, etc.) and L2 (OP Mainnet, OP Goerli, etc.).

#L2 contracts (predeploys)


The L1Block contract (opens new window)sits at address 0x4200000000000000000000000000000000000015. You can use the getter functions (opens new window)to get these parameters:

  • number: The latest L1 block number known to L2 (the L1BlockNumber contract is still supported to avoid breaking existing applications)

  • timestamp: The timestamp of the latest L1 block

  • basefee: The base fee of the latest L1 block

  • hash: The hash of the latest L1 block

  • sequenceNumber: The number of the L2 block within the epoch (the epoch changes when there is a new L1 block)

Currently the L1 information is delayed by two block confirmations (~24 seconds) to minimize the impact of reorgs.


The SequencerFeeVault contract (opens new window)handles funding the sequencer on L1 using the ETH base fee on L2.

The fees are calculated using EIP 1559 (opens new window), the same mechanism that Ethereum uses (but with different parameter values).


The L2ToL1MessagePasser contract (opens new window)is used internally by L2CrossDomainMessenger to initiate withdrawals.

Note that there are two contracts under this name:


The L2CrossDomainMessenger contract (opens new window)is used to send messages from L2 (OP Mainnet, OP Goerli, etc.) to the underlying L1 (Ethereum, Goerli, etc.).


The L2StandardBridge contract (opens new window)is used to "attach" assets (ETH and ERC-20 tokens) to messages that are then sent by L2CrossDomainMessenger.


The WETH9 contract (opens new window)is an ERC-20 token that wraps around ETH to provide extra functionality, such as approvals.

#Legacy Contracts

Those are contracts that have been superceded, but are kept in case any deployed contract depends on them.

Transaction Flow

On this page


 Discord community

 Get support for going live

 Make an issue on GitHub

 Contribute to Optimism


The process for a rollup transaction has two requirements.

  • The transaction needs to be written to L1 (Ethereum). This is typically performed by op-batcher, but any user can send an L1 transaction to submit an L2 transaction, in which case op-batcher is bypassed.

  • The transaction needs to be executed to modify the state (by op-geth). Afterwards, op-proposer writes a commitment (opens new window)to the post-transaction state to L1. Note that op-proposer does not need to write a commitment after each transaction to L1, it is OK to commit to the state after a block of transactions.


#Writing the transaction to L1

op-batcher (opens new window)has two main jobs:

  • Compress transactions into batches.

  • Post those batches to L1 to ensure availability and integrity.


The batcher aggregates sequencer batches (opens new window)into channels (opens new window). This allows for more data per compression frame, and therefore a better compression ratio. You can read more about this process in the specs (opens new window).

When a channel is full or times out it is compressed and written.

The maximum time that a channel can be open, from the first transaction to the last, is specified in units of L1 block time (so a value of 5 means 5*12=60 seconds). You can specify it either as an environment variable (OP_BATCHER_MAX_CHANNEL_DURATION) or a command line parameters (--max-channel-duration). Alternatively, you can set it to zero (the default) to avoid posting smaller, less cost efficient, transactions.

A channel is full when the anticipated compressed size is the target L1 transaction size. This is controlled by two parameters:

  1. The target L1 transaction size, which you can specify in bytes on the command line (--target-l1-tx-size-bytes) or as an environment variable (OP_BATCHER_TARGET_L1_TX_SIZE_BYTES)

  2. The expected compression ratio, which you can specify as a decimal value, again either on the command line (--approx-compr-ratio) or as an environment variable (OP_BATCHER_APPROX_COMPR_RATIO).

You can see the code that implements this process in channel_manager.go (opens new window)and channel_builder.go (opens new window).

#Posting to L1

When a channel is full it is posted, either as a single transaction or as multiple transactions (depending on data size) to L1.

Processed L2 transactions exist in one of three states:

  • unsafe transactions are already processed, but not written to L1 yet. A batcher fault might cause these transactions to be dropped.

  • safe transactions are already processed and written to L1. However, they might be dropped due to a reorganization at the L1 level.

  • finalized transactions are written to L1 in an L1 block that is old enough to be extremely unlikely to be re-organized.

When are transactions irrevocable?

Once a transaction is finalized, you can rely that it has "happened". While the state after the transaction is subject to fault challenges, the transaction itself is fix and immutable.

You can see the code that builds the channels to be written to L1 in channel_out.go (opens new window)and channel_builder.go (opens new window). The transactions themselves are sent in op-batcher's main loop (opens new window), which calls publishStateToL1(opens new window)

#Determining the status of a transaction

This is the procedure to see a transaction's status. The directions here are for Foundry (opens new window), but the concept is the same regardless of the method you use.

  1. Get the number of the L2 block in which the transaction is recorded.

    export ETH_RPC_URL=<URL to Optimism network> cast tx <transaction hash> blockNumber


  2. Get the number of the latest finalized block. If the result is greater than the block number of the transaction, or equal, the transaction is finalized.

    cast block finalized --field number


  3. Get the number of the latest safe block. If the result is greater than the block number of the transaction, or equal, the transaction is safe.

    cast block safe --field number


  4. If the transaction isn't finalized or safe, it's unsafe.

#State processing

State processing can be divided into two steps:

  1. Applying the transaction to the old state to produce the new state, which is performed by op-geth (opens new window).

  2. Proposing the new Merkle root (opens new window)of the state. Merkle roots are used because the actual state is long and would cost too much to write to L1. This step is performed by op-proposer.

#State changes

The state is stored and modified by op-geth (opens new window). It is a slightly modified (opens new window)version of the standard geth (opens new window).

#State root proposals

The state root proposals are posted by op-proposer (opens new window)to L2OutputOracle (opens new window)on L1.

Output proposals are not immediately valid. They can only be considered authoritative once the fault challenge period (7 days on the production network, less on test networks) has passed.

Deposit Flow

On this page


 Discord community

 Get support for going live

 Make an issue on GitHub

 Contribute to Optimism


In Optimism terminology, "deposit transaction" refers to any L2 transaction that is triggered by a transaction or event on L1. A deposit transaction may or may not have assets (ETH, tokens, etc.) attached to it.

The process is somewhat similar to the way most networking stacks work (opens new window). Information is encapsulated in lower layer packets on the sending side, and then retrieved and used by those layers on the receiving side while going up the stack to the receiving application.


#L1 Processing

  1. An L1 entity, either a smart contract or an externally owned account (EOA), sends a deposit transaction to L1CrossDomainMessenger (opens new window), using sendMessage (opens new window). This function accepts three parameters:

    • _target, target address on L2.

    • _message, the L2 transaction's calldata, formatted as per the ABI (opens new window)of the target account.

    • _minGasLimit, the minimum gas limit allowed for the transaction on L2. Note that this is a minimum and the actual amount provided on L2 may be higher (but never lower) than the specified gas limit. Note that the actual amount provided on L2 will be higher, because the portal contract on L2 needs to do some processing before submitting the call to _target.

    You can see code that implements this call in the tutorial (opens new window).

  2. The L1 cross domain messenger calls its own _send function (opens new window). It uses these parameters:

  3. _sendMessage (opens new window)calls the portal's depositTransaction function (opens new window).

    Note that other contracts can also call depositTransaction (opens new window)directly. However, doing so bypasses certain safeguards, so in most cases it's a bad idea.

  4. The depositTransaction function (opens new window)runs a few sanity checks, and then emits a TransactionDeposited (opens new window)event.

#L2 Processing

  1. The op-node component looks for TransactionDeposited events on L1 (opens new window). If it sees any such events, it parses (opens new window)them.

  2. Next, op-node converts (opens new window)those TransactionDeposited events into deposit transactions (opens new window).

  3. In most cases user deposit transactions call the relayMessage (opens new window)function of L2CrossDomainMessenger (opens new window).

  4. relayMessage runs a few sanity checks and then, if everything is good, calls the real target contract with the relayed calldata (opens new window).

#Denial of service (DoS) prevention

As with all other L1 transactions, the L1 costs of a deposit are borne by the transaction's originator. However, the L2 processing of the transaction is performed by the Optimism nodes. If there were no cost attached, an attacker could be able to submit a transaction that had high costs of run on L2, and that way perform a denial of service attack.

To avoid this DoS vector, depositTransaction (opens new window), and the functions that call it, require a gas limit parameter. This gas limit is encoded into the []TransactionDeposited event (opens new window), and used as the gas limit for the user deposit transaction on L2.

This L2 gas is paid for by burning L1 gas here (opens new window).

#Replaying messages

Deposits transactions can fail due to several reasons:

  • Not enough gas provided.

  • The state on L2 does not allow the transaction to be successful.

It is possible to replay a failed deposit, possibly with more gas,

#Replays in action

To see how replays work, you can use this contract on OP Goerli (opens new window).

  1. Call stopChanges, using this Foundry command:

    PRIV_KEY=<your private key here> export ETH_RPC_URL=<url to OP Goerli> GREETER=0x26A145eccDf258688C763726a8Ab2aced898ADe1 cast send --private-key $PRIV_KEY $GREETER "stopChanges()"


  2. Verify that getStatus() returns false, meaning changes are not allowed, and see the value of greet() using Foundry. Note that Foundry returns false as zero.

    cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()"


  3. Get the calldata. You can use this Foundry command:

    cast calldata "setGreeting(string)" "testing"


    Or just use this value:



  4. Send a greeting change as a deposit. Use these commands:

    L1_RPC=<URL to Goerli> L1XDM_ADDRESS=0x5086d1eef304eb5284a0f6720f79403b4e9be294 FUNC="sendMessage(address,bytes,uint32)" CALLDATA=`cast calldata "setGreeting(string)" "testing"` cast send --rpc-url $L1_RPC --private-key $PRIV_KEY $L1XDM_ADDRESS $FUNC $GREETER $CALLDATA 10000000


    The transaction will be successful on L1, but then emit a fail event on L2.

  5. The next step is to find the hash of the failed relay. The easiest way to do this is to look in the internal transactions of the destination contract (opens new window), and select the latest one that appears as a failure. It should be a call to L2CrossDomainMessenger at address 0x420...007. This is the call you need to replay.

    If the latest internal transaction is a success, it probably means your transaction hasn't relayed yet. Wait until it is, that may take a few minutes.

  6. Get the transaction information using Foundry.

    TX_HASH=<transaction hash from Etherscan> L2XDM_ADDRESS=0x4200000000000000000000000000000000000007 REPLAY_DATA=`cast tx $TX_HASH input`


  7. Call startChanges() to allow changes using this Foundry command:

    cast send --private-key $PRIV_KEY $GREETER "startChanges()"


    Don't do this prematurely

    If you call startChanges() too early, it will happen from the message is relayed to L2, and then the initial deposit will be successful and there will be no need to replay it.

  8. Verify that getStatus() returns true, meaning changes are not allowed, and see the value of greet(). Foundry returns true as one.

    cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()"


  9. Now send the replay transaction.

    cast send --private-key $PRIV_KEY --gas-limit 10000000 $L2XDM_ADDRESS $REPLAY_DATA


    Why do we need to specify the gas limit?





  10. Verify the greeting has changed:

    cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()"



To debug deposit transactions you can ask the L2 cross domain messenger for the state of the transaction.

  1. Look on Etherscan to see the FailedRelayedMessage event. Set MSG_HASH to that value.

  2. To check if the message is listed as failed, run this:

    cast call $L2XDM_ADDRESS "failedMessages(bytes32)" $MSG_HASH


    To check if it is listed as successful, run this:

    cast call $L2XDM_ADDRESS "successfulMessages(bytes32)" $MSG_HASH


About this page

  • Updated: June 24, 2023


Withdrawal Flow

On this page


 Discord community

 Get support for going live

 Make an issue on GitHub

 Contribute to Optimism

In Optimism terminology, a withdrawal is a transaction sent from L2 (OP Mainnet, OP Goerli etc.) to L1 (Ethereum mainnet, Goerli, etc.). This withdrawal may or may not have assets attached to it.

Withdrawals require the user to submit three transactions:

  1. Withdrawal initiating transaction, which the user submits on L2.

  2. Withdrawal proving transaction, which the user submits on L1 to prove that the withdrawal is legitimate (based on a merkle patricia trie root that commits to the state of the L2ToL1MessagePasser's storage on L2)

  3. Withdrawal finalizing transaction, which the user submits on L1 after the fault challenge period has passed, to actually run the transaction on L1, claim any assets attached, etc.

You can see an example of how to do this in the tutorials (opens new window).

#Withdrawal initiating transaction

  1. On L2 somebody (either an externally owned account (EOA) directly or a contract acting on behalf of an EOA) calls the sendMessage (opens new window)function of L2CrossDomainMessenger (opens new window).

    This function accepts three parameters:

    • _target, target address on L1.

    • _message, the L1 transaction's calldata, formatted as per the ABI (opens new window)of the target account.

    • _minGasLimit, The minimum amount of gas that the withdrawal finalizing transaction can provide to the withdrawal transaction. This is enforced by the SafeCall library, and if the minimum amount of gas cannot be met at the time of the external call from the OptimismPortal -> L1CrossDomainMessenger, the finalization transaction will revert to allow for re-attempting with a higher gas limit. In order to account for the gas consumed in the L1CrossDomainMessenger.relayMessage function's execution, extra gas will be added on top of the _minGasLimit value by the CrossDomainMessenger.baseGas function when sendMessage is called on L2.

  2. sendMessage is a generic function that is used in both cross domain messengers. It calls _sendMessage (opens new window), which is specific to L2CrossDomainMessenger (opens new window).

  3. _sendMessage calls initiateWithdrawal (opens new window)on L2ToL1MessagePasser (opens new window). This function calculates the hash of the raw withdrawal fields. It then marks that hash as a sent message in sentMessages (opens new window)and emits the fields with the hash in a MessagePassed event (opens new window).

    The raw withdrawal fields are:

    • nonce - A single use value to prevent two otherwise identical withdrawals from hashing to the same value

    • sender - The L2 address that initiated the transfer, typically L2CrossDomainMessenger(opens new window)

    • target - The L1 target address

    • value - The amount of WEI transferred by this transaction

    • gasLimit - Gas limit for the transaction, the system guarantees that at least this amount of gas will be available to the transaction on L1. Note that if the gas limit is not enough, or if the L1 finalizing transaction does not have enough gas to provide that gas limit, the finalizing transaction returns a failure, it does not revert.

    • data - The calldata for the withdrawal transaction

  4. When op-proposer proposes a new output (opens new window), the output proposal includes the output root (opens new window), provided as part of the block by op-node. This new output root commits to the state of the sentMessages mapping in the L2ToL1MessagePasser contract's storage on L2, and it can be used to prove the presence of a pending withdrawal within it.

#Withdrawal proving transaction

Once an output root that includes the MessagePassed event is published to L1, the next step is to prove that the message hash really is in L2. Typically this is done by the SDK (opens new window).

#Offchain processing

  1. A user calls the SDK's CrossDomainMessenger.proveMessage() (opens new window)with the hash of the L1 message. This function calls CrossDomainMessenger.populateTransaction.proveMessage() (opens new window).

  2. To get from the L2 transaction hash to the raw withdrawal fields, the SDK uses toLowLevelMessage (opens new window). It gets them from the MessagePassed event in the receipt.

  3. To get the proof, the SDK uses getBedrockMessageProof (opens new window).

  4. Finally, the SDK calls OptimismPortal.proveWithdrawalTransaction() (opens new window)on L1.

#Onchain processing

OptimismPortal.proveWithdrawalTransaction() (opens new window)runs a few sanity checks. Then it verifies that in L2ToL1MessagePasser.sentMessages on L2 the hash for the withdrawal is turned on, and that this proof have not been submitted before. If everything checks out, it writes the output root, the timestamp, and the L2 output index to which it applies in provenWithdrawals and emits an event.

The next step is to wait the fault challenge period, to ensure that the L2 output root used in the proof is legitimate, and that the proof itself is legitimate and not a hack.

#Withdrawal finalizing transaction

Finally, once the fault challenge period passes, the withdrawal can be finalized and executed on L1.

#Offchain processing

  1. A user calls the SDK's CrossDomainMessenger.finalizeMessage() (opens new window)with the hash of the L1 message. This function calls CrossDomainMessenger.populateTransaction.finalizeMessage() (opens new window).

  2. To get from the L2 transaction hash to the raw withdrawal fields, the SDK uses toLowLevelMessage (opens new window). It gets them from the MessagePassed event in the receipt.

  3. Finally, the SDK calls OptimismPortal.finalizeWithdrawalTransaction() (opens new window)on L1.

#Onchain processing

  1. OptimismPortal.finalizeWithdrawalTransaction() (opens new window)runs several checks. The interesting ones are:

    If any of these checks fail, the transaction reverts.

  2. Mark the withdrawal as finalized in finalizedWithdrawals.

  3. Run the actual withdrawal transaction (call the target contract with the calldata in data).

  4. Emit a WithdrawalFinalized (opens new window)event.

©2023 por Optimism em Português. 

bottom of page