# Complete EVM/OVM Comparison

OVM 1.0 Page

This page refers to the current state of the Optimistic Ethereum network. Some of the information may be relevant to OVM 2.0, which will be deployed in October, but some of it may change.

For the most part, the EVM and the OVM are pretty much identical. However, the OVM does slightly diverge from the EVM in certain ways. This page acts as a living record of each of these discrepancies and their raison d'être.

# Missing Opcodes

Some EVM opcodes don't exist in the OVM because they make no sense (like DIFFICULTY). Others don't exist because they're more trouble than they're worth (like SELFDESTRUCT). Here's a record of every missing opcode.

EVM Opcode Solidity Usage Reason for Absence
COINBASE block.coinbase No equivalent in the OVM.
DIFFICULTY block.difficulty No equivalent in the OVM.
BLOCKHASH blockhash No equivalent in the OVM.
GASPRICE tx.gasprice No equivalent in the OVM.
SELFDESTRUCT selfdestruct It's dumb.
ORIGIN tx.origin Coming soon™. See Account Abstraction.

# Replaced Opcodes

Certain opcodes are banned and cannot be used directly. Instead, they must be translated into calls to the OVM_ExecutionManager (opens new window) contract. Our fork of the Solidity compiler handles this translation automatically so you don't need to worry about this in practice.

The following opcodes must be translated into calls to the execution manager:

  • ADDRESS
  • NUMBER
  • TIMESTAMP
  • CHAINID
  • GASLIMIT
  • REVERT
  • SLOAD
  • SSTORE
  • CALL
  • STATICCALL
  • DELEGATECALL
  • CREATE
  • CREATE2
  • EXTCODECOPY
  • EXTCODESIZE
  • EXTCODEHASH
  • BALANCE
  • CALLVALUE

# Behavioral differences of replaced opcodes

# Differences for STATICCALL

Event opcodes (LOG0, LOG1, LOG2, and LOG3) normally cause a revert when executed during a STATICCALL in the EVM. However, these opcodes can be triggered within a STATICCALL within the OVM without causing a revert.

# Differences for TIMESTAMP and NUMBER

The behavior of the TIMESTAMP (block.timestamp) and NUMBER (block.number) opcodes depends on the manner in which a transaction is added to the OVM_CanonicalTransactionChain (opens new window).

For transactions that are directly added to the chain via the enqueue (opens new window) function, TIMESTAMP and NUMBER will return the timestamp and block number of the block in which enqueue was called.

For transactions added to the chain by the Sequencer, the TIMESTAMP and NUMBER can be any arbitrary number that satisfies the following conditions:

  1. TIMESTAMP and NUMBER on L2 may not be greater than the timestamp and block number at the time the transaction is bundled and submitted to L1.
  2. TIMESTAMP and NUMBER cannot be more than forceInclusionPeriodSeconds (opens new window) or forceInclusionPeriodBlocks (opens new window) in the past, respectively.
  3. TIMESTAMP and NUMBER must be monotonic: the timestamp of some transaction N must be greater than or equal to the timestamp of transaction N-1.

# Custom Opcodes

The OVM introduces some new "opcodes" which are not present in the EVM but may be accessed via a call to the execution manager.

# ovmGETNONCE

function ovmGETNONCE() public returns (address);

Returns the nonce of the calling contract.

# ovmINCREMENTNONCE

function ovmINCREMENTNONCE() public;

Increments the nonce of the calling contract by 1. You can call this function as many times as you'd like during a transaction. As a result, you can increment your nonce as many times as you have gas to do so. Useful for implementing smart contracts that represent user accounts. See the default contract account (opens new window) for an example of this.

# ovmCREATEEOA

function ovmCREATEEOA(bytes32 _messageHash, uint8 _v, bytes32 _r, bytes32 _s) public;

Deploys the default contract account (opens new window) on behalf of a user. Account address is determined by recovering an ECDSA signature. If the account already exists, the account will not be overwritten. See Account Abstraction section for additional detail.

# ovmL1QUEUEORIGIN

function ovmL1QUEUEORIGIN() public returns (uint8);

Returns 0 if this transaction was added to the chain by the Sequencer and 1 if the transaction was added to the chain directly by a call to OVM_CanonicalTransactionChain.enqueue (opens new window).

# ovmL1TXORIGIN

function ovmL1TXORIGIN() public returns (address);

If the result of ovmL1QUEUEORIGIN is 0 (the transaction came from the Sequencer), then this function returns the zero address (0x00...00). If the result of ovmL1QUEUEORIGIN is 1, then this function returns the address that called OVM_CanonicalTransactionChain.enqueue (opens new window) and therefore triggered this transaction.

# Native ETH

Because we thought it was cool and because it's quite useful, we turned ETH into an ERC20. This means you don't need to use something like wETH (opens new window) -- ETH is wETH by default. But it's also ETH. WooOooooOOOoo spooky.

# Using ETH normally

To use ETH normally, you can just use Solidity built-ins like msg.value and address.transfer(value). It's the exact same thing you'd do on Ethereum.

For example, you can get your balance via:

uint256 balance = address(this).balance;

# Using ETH as an ERC20 token

To use ETH as an ERC20 token, you can interact with the OVM_ETH (opens new window) contract deployed to Layer 2 at the address 0x4200000000000000000000000000000000000006.

For example, you can get your balance via:

uint256 balance = ERC20(0x4200000000000000000000000000000000000006).balanceOf(address(this));

# Account Abstraction

The OVM implements a basic form of account abstraction (opens new window). In a nutshell, this means that all accounts are smart contracts (there are no "externally owned accounts" like in Ethereum). This has no impact on user experience, it's just an extension to Ethereum that gives developers a new dimension to experiment with. However, this scheme does have a few minor effects on developer experience that you may want to be aware of.

# Compatibility with existing wallets

Our account abstraction scheme is 100% compatible with existing wallets. For the most part, developers don't need to understand how the account abstraction scheme works under the hood. We've implemented a standard contract account (opens new window) which remains backwards compatible with all existing Ethereum wallets out of the box. Contract accounts are automatically deployed on behalf of a user when that user sends their first transaction.

# No transaction origin

The only major restriction that our account abstraction scheme introduces is that there is no equivalent to tx.origin (the ORIGIN EVM opcode) in the OVM. Some applications use tx.origin to try to block certain transactions from being executed by smart contracts via:

require(msg.sender == tx.origin);

This will not work on Optimistic Ethereum. You cannot tell the difference between contracts and externally owned accounts (because externally accounts do not exist).

# Upgrading accounts

The default contract account (opens new window) sits behind a proxy (opens new window) that can be upgraded. Upgrades can be triggered by having the contract account call the upgrade(...) method attached to the proxy. Only the account itself can trigger this call, so it's not possible for someone else to upgrade your account.

Upgrades are disabled

Contract account upgrades are currently disabled until future notice. We're staying on the safe side and making sure that everything is working 100% with the default account before we start allowing users to play with upgrades.

# State Structure

# Storage slots are never deleted

In Ethereum, setting the value of a storage slot to zero will delete the key associated with that storage slot from the trie. Because of the technical difficulty of implementing deletions within our Solidity Merkle Trie library (opens new window), we currently do not delete keys when values are set to zero. This discrepancy does not have any significant negative impact on performance. We may make an update to our Merkle Trie library that resolves this discrepancy at some point in the future.

# Gas metadata account

A special account 0x06a506A506a506A506a506a506A506A506A506A5 is used to store gas-related metadata (cumulative gas spent, gas spent since the last epoch, etc.). You'll see this account pop up in transaction traces and during transaction result challenges.