EVM/OVM/NVM Comparison
Due to forking the OVM, we inherit a lot of their architecture. Taken from: https://community.optimism.io/docs/protocol/evm-comparison.html
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

1
function ovmGETNONCE() public returns (address);
Copied!
Returns the nonce of the calling contract.

#ovmINCREMENTNONCE

1
function ovmINCREMENTNONCE() public;
Copied!
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

1
function ovmCREATEEOA(bytes32 _messageHash, uint8 _v, bytes32 _r, bytes32 _s) public;
Copied!
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

1
function ovmL1QUEUEORIGIN() public returns (uint8);
Copied!
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

1
function ovmL1TXORIGIN() public returns (address);
Copied!
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:
1
uint256 balance = address(this).balance;
Copied!

#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:
1
uint256 balance = ERC20(0x4200000000000000000000000000000000000006).balanceOf(address(this));
Copied!

#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:
1
require(msg.sender == tx.origin);
Copied!
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

#Balance field is always zero

Since ETH is treated as an ERC20, the balance field of any account will always be zero. User balances are tracked as storage slots inside the ETH ERC20. If you want to determine the ETH balance for a given account, you should directly query the ETH ERC20 contract.

#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.
Last modified 6h ago