EVM opcodes categorization - matter-labs/era-compiler-llvm GitHub Wiki

This document provides a full categorization of all EVM opcodes, sorted by whether they are:

  • readnone (stable, pure, constant after frame start)
  • volatile (may change due to external calls)
  • side-effecting (cause world-state changes)

EVM Opcode Stability Classification (Up to Cancun Fork)

Categories of Runtime Stability

Ethereum Virtual Machine (EVM) opcodes can be grouped by runtime stability – how their behavior or outputs are affected by state changes during a transaction:

  • Readnone (Pure) Opcodes: These do not read or write any persistent world-state once execution begins. Their results depend only on provided inputs or fixed environment data (constant during the transaction). They have no lasting side effects and return the same result given the same inputs.
  • Volatile (State-Dependent) Opcodes: These read data from the world state or execution environment that may change within the same transaction (for example, due to prior storage writes, value transfers, or external calls). Re-invoking them after a state mutation can yield a different result. They themselves do not permanently modify state.
  • Side-Effecting (State-Changing) Opcodes: These opcodes cause persistent changes in world state – writing to storage, emitting logs, creating or destroying contracts, or transferring Ether. Their execution produces lasting side effects recorded on-chain (storage updates, logs, new accounts, etc.).

Below is a full categorization of known opcodes (through the Cancun upgrade, including EIP-4844 blob opcodes) into these categories, with brief explanations and notes on special cases like SELFBALANCE, GAS, RETURNDATACOPY, and TLOAD.

Readnone (Pure, No State Interaction) Opcodes

  • Arithmetic Operations: ADD, SUB, MUL, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTEND. These perform mathematical operations on values from the stack and push the result back. They do not access or depend on any account state – for example, ADD simply adds two 256-bit integers from the stack (modulo $2^{256}$) and is entirely deterministic from its inputs (Ethereum Virtual Machine Opcodes). No persistent state is read or written by these opcodes.

  • Comparison & Bitwise Ops: LT, GT, SLT, SGT, EQ, ISZERO, AND, OR, XOR, NOT, BYTE, SHL, SHR, SAR. These opcodes perform comparisons or bit-level operations on stack values only. They are pure functions of their inputs (e.g. AND returns the bitwise AND of two numbers) and do not touch storage or any external data. Their outputs are constant for given inputs and do not cause any state change.

  • Cryptographic Hash: SHA3 (also known as KECCAK256). This computes the Keccak-256 hash of a chunk of memory. It takes a memory offset and length from the stack, reads that portion of memory, and returns the 32-byte hash (Ethereum Virtual Machine Opcodes). Because it only processes in-memory data provided during execution, it’s pure – the same input bytes always produce the same hash, and it doesn’t depend on or alter world state.

  • Stack/Memory Management: POP, PUSH1PUSH32 (including PUSH0 from Shanghai), DUP1DUP16, SWAP1SWAP16, MLOAD, MSTORE, MSTORE8, MSIZE. All of these operate on the transient stack or memory of the EVM:

    • Stack ops: Pushing constants, duplicating, or swapping stack items have no interaction with storage or external state – they simply rearrange or insert data on the stack. For example, PUSH0 pushes the constant 0 onto the stack (Ethereum Virtual Machine Opcodes), and SWAPn swaps stack positions; these are self-contained operations.
    • Memory ops: MLOAD reads a word from the contract’s volatile memory, and MSTORE/MSTORE8 store values into memory. Memory is a local scratch space that exists only during execution, not part of the persistent state (Ethereum Virtual Machine Opcodes). For instance, MSTORE at some offset will only affect the in-memory data and not touch contract storage (Ethereum Virtual Machine Opcodes). MSIZE simply returns the current size of allocated memory in bytes, which grows as memory is used – it reflects a property of the local memory that does not persist beyond execution. None of these opcodes read or write the blockchain’s world-state, so they are considered pure (their effects are limited to the execution context).
  • Flow Control & Program Counter: JUMP, JUMPI, JUMPDEST, PC. These manage the execution flow without any external state interaction. PC pushes the current program counter (instruction address) to the stack (Ethereum Virtual Machine Opcodes) – a value that depends only on the code position, not on any mutable state. JUMP and JUMPI alter the control flow by moving to a jump destination, and JUMPDEST marks valid jump targets in the code. All are internal to the code execution; they neither read from nor write to storage or account state.

  • Halting and Returning: STOP, RETURN, REVERT. These opcodes terminate execution in various ways but do not themselves create a persistent change:

    • STOP simply halts execution successfully (no state is modified; it consumes no extra gas beyond the opcode cost).
    • RETURN halts execution and returns a specified chunk of memory as output to the caller, without affecting world state (aside from producing the return data).
    • REVERT aborts execution and reverts any state changes made during the call, without committing anything. It allows returning an error message but leaves no lasting state effects (Ethereum Virtual Machine Opcodes). All three are considered readnone since they don’t read external state or persistently modify it (a REVERT undoes changes rather than making a new one).
  • Call Context Information: ADDRESS, ORIGIN, CALLER, CALLVALUE, GASPRICE. These opcodes provide information about the execution context, all of which is fixed when the call/transaction starts:

    • ADDRESS pushes the address of the current executing contract.
    • ORIGIN gives the address of the external account that initiated the top-level transaction.
    • CALLER is the immediate caller’s address (the contract or EOA that called the current execution frame).
    • CALLVALUE is the amount of Ether (in wei) sent with this call.
    • GASPRICE returns the gas price (per unit gas) of the transaction.
      Each of these values is set at call entry and remains constant throughout the execution of that call, so these opcodes behave like constants within the transaction. They do not depend on any mutable state changes during the execution.
  • Call Data and Code Access: CALLDATASIZE, CALLDATALOAD, CALLDATACOPY, CODESIZE, CODECOPY. These opcodes read data that is also invariant during the execution:

    • CALLDATASIZE gives the size of the input data supplied to this call.
    • CALLDATALOAD reads 32 bytes from the call’s input data at a specified offset.
    • CALLDATACOPY copies a segment of the call’s input data into memory.
      The call data is an immutable byte array provided by the caller, so it cannot change once execution has started. Similarly, CODESIZE returns the size of the executing contract’s code, and CODECOPY copies bytes from the contract’s own code into memory. A contract’s code is fixed and immutable (it’s set at deployment), so these operations yield the same results any time during execution. They do not read any storage or external state – only the static code and input data – hence they are pure. (Even if the contract self-destructs, code removal doesn’t take effect until after execution, so during runtime the code is available for CODECOPY.)
  • Block and Chain Properties: BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO (formerly DIFFICULTY), GASLIMIT, CHAINID, BASEFEE. These opcodes retrieve information from the current block header or blockchain constants:

    • BLOCKHASH <n> returns the hash of block number n (for recent blocks within 256 block lookback) (Ethereum Virtual Machine Opcodes). This depends only on the blockchain’s history, which is fixed during the transaction – calling it multiple times with the same argument yields the same hash.
    • COINBASE provides the address of the block’s miner/validator (Ethereum Virtual Machine Opcodes).
    • TIMESTAMP pushes the Unix timestamp of the block (Ethereum Virtual Machine Opcodes).
    • NUMBER is the block number (height) of the current block (Ethereum Virtual Machine Opcodes).
    • PREVRANDAO (renamed from the former DIFFICULTY opcode after the Merge) gives the randomness value from the beacon chain for this block (previously this field held the PoW difficulty) (Ethereum Virtual Machine Opcodes).
    • GASLIMIT yields the block’s gas limit (the maximum gas allowed in the block) (Ethereum Virtual Machine Opcodes).
    • CHAINID returns the chain identifier for the network (a constant that doesn’t change during runtime) (Ethereum Virtual Machine Opcodes).
    • BASEFEE returns the base fee per gas for the block (EIP-1559 fee mechanism) (Ethereum Virtual Machine Opcodes).
      All of these values are set by the block and remain constant throughout the transaction; they are not influenced by what the contract code does. Therefore, these opcodes can be treated as reading constant environment data once execution has started (no intra-transaction variability).
  • Blob Transaction Opcodes (Cancun): BLOBHASH and BLOBBASEFEE (introduced in the Cancun/Dencun upgrade) are also pure environment reads:

    • BLOBHASH (EIP-4844) returns the ith blob versioned hash from the blob data attached to the current transaction (Ethereum Virtual Machine Opcodes). This is effectively an extension of transaction input – the blob data and its hash are part of the tx and fixed at the start of execution. If the index is out of range (no such blob), it returns a zero hash (EIPs/EIPS/eip-4844.md at master · ethereum/EIPs · GitHub). Since the transaction’s blob data cannot change during execution, BLOBHASH is constant for given inputs.
    • BLOBBASEFEE (EIP-7516) pushes the current block’s blob base fee (the base fee for blob data gas, analogous to EIP-1559’s basefee) (Ethereum Virtual Machine Opcodes). This value comes from the block header’s blob gas pricing mechanism and is constant within the block. Like BASEFEE, it’s predetermined before the transaction runs and does not change at runtime.

(In summary, all the above opcodes neither depend on any mutable state that can change after the start of execution nor produce lasting state effects. They are deterministic and “pure” in the context of a single transaction execution.)

Volatile (State-Dependent) Opcodes

  • Storage Read – SLOAD: This opcode reads a value from the contract’s permanent storage slot (using a 32-byte key from the stack). While a storage read itself doesn’t alter state, its return value can change if the storage was modified earlier in the transaction. For example, if the contract executed an SSTORE to a given key previously in this transaction (or one of its inner calls did so and the change wasn’t reverted), a subsequent SLOAD on that key will reflect the updated value (Ethereum Virtual Machine Opcodes). Thus, SLOAD is volatile – its result is not fixed at the start of execution, but can vary depending on prior state mutations in the same transaction.

  • Account Balance Queries – BALANCE & SELFBALANCE: These opcodes return an account’s Ether balance, which can fluctuate during a transaction:

    • BALANCE <addr> reads the balance of the given address from the world state (Ethereum Virtual Machine Opcodes). This can change if Ether is sent to or from that address during the transaction. For instance, if the contract sends funds to or receives funds from <addr> via an internal transaction, then a later BALANCE query on that address will yield a different result than an earlier one.
    • SELFBALANCE (EIP-1884) pushes the balance of the current executing contract (its own address) onto the stack (Ethereum's Istanbul Fork — Technical Explanation | by Luit Hollander). This is a gas-optimized way to get your own balance. The value can change after certain operations – notably if the contract itself transfers out Ether or another contract sends it Ether within the same transaction. In such cases, calling SELFBALANCE before and after a transfer will produce different results, making it volatile. (Corner case: although SELFBALANCE is cheaper than BALANCE for the same account, it remains state-dependent because the contract’s balance is not constant during execution.)
  • External Code Metadata – EXTCODESIZE, EXTCODECOPY, EXTCODEHASH: These opcodes read properties of another account’s code, and their results can vary if the target account’s code is created or destroyed during the transaction:

    • EXTCODESIZE <addr> returns the size of the code at address <addr>. If no contract is deployed at that address at the moment of calling, it returns 0. However, if a contract is created at <addr> later in the transaction (via a CREATE or CREATE2), then a subsequent EXTCODESIZE on the same address (after creation) will report the actual code length. Likewise, if a contract self-destructs, calls after destruction would see size 0.
    • EXTCODECOPY <addr> copies the bytecode of the contract at <addr> into memory. Its output bytes depend on the target’s code, which can appear or vanish mid-transaction due to contract creation or destruction.
    • EXTCODEHASH <addr> returns the keccak256 hash of the code at <addr> (or 0 if the address has no code). This too will change if the code at that address is updated. Normally code at an address is immutable, but if a new contract is created at an address where a contract was previously self-destructed in the same transaction, an EXTCODEHASH before and after would differ (0 before creation, non-zero after).
      All these opcodes read from world state (the state of another account’s code), so they are sensitive to world-state changes during the transaction.
  • Return Data Buffer – RETURNDATASIZE and RETURNDATACOPY: These opcodes deal with the last call’s returned data, which is an ephemeral piece of state that gets updated whenever an external call or contract creation returns some data.

    • RETURNDATASIZE pushes the size (in bytes) of the return data from the most recent external call made by the current execution frame (Ethereum Virtual Machine Opcodes). If no call has been made yet, this size is 0. After each CALL/STATICCALL/DELEGATECALL or CREATE (which returns initcode output), the return-data buffer is overwritten with the result of that call (or empty if it failed). Thus, RETURNDATASIZE can yield a different number after different calls.
    • RETURNDATACOPY dest_offs src_offs len copies len bytes from that last call’s return buffer into the contract’s memory (Ethereum Virtual Machine Opcodes). The content being copied is obviously volatile – it’s whatever the most recent call returned. If you invoke another call, the buffered return data changes, so subsequent RETURNDATACOPY will copy a new dataset. In short, these opcodes are tied to the data from the last external call, making them state-dependent (volatile) across calls. (Corner case: one must ensure RETURNDATASIZE is sufficient before copying, otherwise an out-of-range copy causes a revert.)
  • Remaining Gas – GAS: This opcode pushes the amount of gas remaining after accounting for the cost of the GAS instruction itself (Ethereum Virtual Machine Opcodes). Because the EVM continuously consumes gas as instructions execute, the result of GAS steadily declines over time. If you call GAS at different points in the program, it will return a smaller and smaller number (assuming gas is being spent). Thus, GAS is not a constant – it’s volatile during execution (it reflects the current execution state). Every step or call can alter the remaining gas, so GAS falls squarely in the volatile category. (It does not persist any change, but its return value depends on how much gas was used before.)

  • Transient Storage Read – TLOAD: This opcode (EIP-1153, Cancun) reads from transient storage, a new transaction-scoped storage area. It takes a key and pushes the corresponding value from transient storage. Unlike regular storage, transient storage is not permanent – it exists only during the transaction and is cleared afterward (EIP-1153: Transient storage opcodes). However, within a transaction, transient storage can be modified by TSTORE. If a contract has written a temporary value earlier, a later TLOAD of the same key will see that updated value (Ethereum Virtual Machine Opcodes). Because its return can change after a TSTORE (or be reset if a revert happened), TLOAD is volatile in the context of a transaction. Essentially, it’s analogous to SLOAD but for transient state: it reads data that can vary as the transaction proceeds. (Corner case: transient storage is fresh and empty at the start of a transaction, and all transient keys reset to zero at transaction end (EIP-1153: Transient storage opcodes). Thus, TLOAD yields 0 for any key until something is TSTORE’d to that key, and reverts to 0 after the transaction completes.)

  • Static (Read-Only) Calls – STATICCALL: This opcode invokes a call to another contract, similar to CALL, but with the important restriction that no state modifications are allowed during the call (Ethereum Virtual Machine Opcodes). STATICCALL itself does not write to state (and will throw if the callee attempts to do so), which is why it isn’t categorized as side-effecting. However, it is volatile because the data it returns or the success/failure can depend on the external contract’s state at that moment. For example, if the callee contract’s storage or balance was altered earlier in the transaction (by some prior call or event), the result of a STATICCALL that reads those values will differ. The STATICCALL opcode returns a boolean success flag and populates the return data buffer with whatever the callee returned. Since it’s an external call, its outcome is not predetermined at the start of the transaction – it depends on external state that might have changed. In summary, while STATICCALL guarantees no new state changes during its execution (EIPs/EIPS/eip-214.md at master · ethereum/EIPs · GitHub) (Ethereum Virtual Machine Opcodes), it reads state that may have been recently modified, making it volatile in terms of runtime stability.

(All the above opcodes produce results that can vary within a single transaction due to state changes or calls. They themselves do not commit permanent changes to the world state, but their outputs are not constant during execution.)

Side-Effecting (State-Changing) Opcodes

  • Persistent Storage Write – SSTORE: This opcode writes a 32-byte value to a given storage slot in the contract’s permanent storage. It directly alters the world state of the contract, consuming significant gas and potentially awarding gas refunds depending on the circumstances (Ethereum Virtual Machine Opcodes). An SSTORE is a permanent state change – after a successful transaction, the new value is stored in the contract’s storage and will persist into future transactions (unless reverted). Because it causes a lasting world-state update (storage update) that other transactions can observe, SSTORE is classified as side-effecting.

  • Transient Storage Write – TSTORE: This is the transient-storage counterpart to SSTORE, introduced in Cancun (EIP-1153). TSTORE writes a value to a transient storage slot (identified by a key) for the current transaction (Ethereum Virtual Machine Opcodes). This action has a side effect on the transaction’s state: any subsequent TLOAD in the same transaction (in any call frame, for the same contract) will see the written value. However, the effect is not permanent beyond the transaction – all transient storage values are discarded at the end of the transaction (EIP-1153: Transient storage opcodes). Thus, TSTORE produces a state change that is ephemeral. It is still considered side-effecting in the context of a single transaction (since it alters state that other calls in the transaction could rely on), but it doesn’t create a lasting world-state change. (Notably, like SSTORE, TSTORE is disallowed in static calls because it is a form of state mutation.)

  • Logging Events – LOG0, LOG1, LOG2, LOG3, LOG4: These opcodes consume data from the stack and memory to append log entries (events) to the transaction’s receipt (Ethereum Virtual Machine Opcodes). Each log opcode takes an arbitrary length data blob from memory and up to 4 topics from the stack (LOG0 has no topics, LOG4 has four topics), and produces a log record that is persisted on-chain as part of the transaction’s logs. Logging does not alter contract storage, but it is a permanent side effect in that the log is recorded in the blockchain’s event logs. Once emitted, these events can be observed by off-chain listeners and remain in the receipt/trie permanently. Therefore, the LOG opcodes are classified as side-effecting – they create an observable effect external to the EVM’s pure computation (a lasting event). (Logs are also disallowed in STATICCALL, since they are considered state-modifying in Ethereum’s rules).

  • Contract Creation – CREATE and CREATE2: These opcodes spawn new smart contracts, which changes the world state by adding a new account with code:

    • CREATE takes an initialization code from memory and a supplied endowment (Ether) and attempts to create a new contract account (Ethereum Virtual Machine Opcodes). If successful, it instantiates the new contract’s code (the init code’s output) at a new address and transfers the given Ether to it. The creation of a new account with code and balance is a permanent state change (a new contract is added to world state).
    • CREATE2 (EIP-1014) similarly creates a contract, but the address is precomputed deterministically from the sender, salt, and code hash. The end result is the same kind of side effect: a new account with code is introduced if creation succeeds.
      These opcodes also implicitly set the return data buffer to the created contract’s address (or zero on failure). Both are clearly side-effecting as they alter the set of contracts in the world state (and possibly storage of the new contract via its init code). A contract created in a transaction will persist beyond that transaction (unless immediately self-destructed), making this a lasting change.
  • External Calls (with State) – CALL, CALLCODE, DELEGATECALL: These opcodes invoke code that can produce state changes or transfer Ether, thus they are side-effecting operations:

    • CALL: Calls another contract (or account) at a given address with an optional Ether value transfer (Ethereum Virtual Machine Opcodes). It hands over control to the callee, which may execute arbitrary logic. If a non-zero value is provided, the call will deduct that amount from the caller’s balance and add it to the callee’s balance as part of the call, immediately affecting both accounts’ balances. During the call, the callee can modify its own storage, emit logs, or even call other contracts. All such actions (if the call succeeds and is not reverted) are permanent state changes triggered by the CALL opcode. Thus, CALL can have multiple side effects: Ether transfer, and any side effects the callee performs (storage writes, logs, etc.).
    • CALLCODE & DELEGATECALL: These are variants where the code at a target address is executed in the context of the caller. In CALLCODE (an older variant) and the more common DELEGATECALL (EIP-7), the caller’s own storage and balance can be manipulated by the callee code (Ethereum Virtual Machine Opcodes). No new contract is created and the original caller remains the context, but since the delegated code can execute SSTORE or emit logs using the caller’s state, it definitely can induce state changes. Both opcodes allow the code at another address to act as a library on the current state. Any storage modifications or events produced are changes to the caller’s state and thus persist. Like CALL, these opcodes also forward a certain gas amount and can return data. They are side-effecting because they effectively allow state changes (in the caller’s storage) via the invoked code.
      In summary, any call-like opcode that is not static will potentially cause world-state changes – either through direct value transfer or by enabling the callee (or delegated code) to perform storage/log operations. Even if the call fails or reverts, a value transfer may already have happened (it’s reverted if the callee reverts, but on a successful call the effect is applied). Because these opcodes initiate interactions that alter contract states, they are considered side-effecting.
  • Self-Destruct – SELFDESTRUCT: This opcode is used to delete the executing contract and send its remaining Ether balance to a designated beneficiary address. Historically, SELFDESTRUCT (formerly named SUICIDE) would remove the contract’s code and storage from the state immediately and schedule the balance transfer (Ethereum Virtual Machine Opcodes). After the Ethereum “Dencun” upgrade (Cancun for execution, circa late 2023), the semantics have changed under EIP-6780: if a contract that was not newly created in the same transaction calls SELFDESTRUCT, it will only transfer the Ether to the target address and will no longer actually remove the contract’s code/storage during that transaction (How does Selfdestruct work? - Ethereum Stack Exchange). The code and storage remain intact in state (the contract is effectively “zombie” but not gone), preventing certain griefing scenarios. The only time a full removal occurs now is if the contract executes SELFDESTRUCT in the same transaction it was created (How does Selfdestruct work? - Ethereum Stack Exchange) (in that case, the contract never truly “took root” in a mined state). Regardless of these nuances, SELFDESTRUCT produces a side effect: it guarantees the contract’s balance (at the time of execution) is sent to the specified address, which is a permanent balance change. If the contract is eligible for destruction (newly created in same tx), it will be removed from state as a permanent change; otherwise, the persistent state is still impacted by the balance transfer (and a future removal of code is no longer happening for older contracts). In both scenarios, something observable in world state occurs (at least Ether movement, and possibly contract deletion). Thus, SELFDESTRUCT is unequivocally a side-effecting opcode. (Note: after SELFDESTRUCT is executed, any remaining code execution in that frame is still halted, similar to a STOP – though the final removal of code may be deferred or skipped as per EIP-6780, the state change of balance transfer happens).)

Sources:

⚠️ **GitHub.com Fallback** ⚠️