1.13 EVM (Ethereum Virtual Machine) in Depth
The EVM is the execution component of the Ethereum blockchain: it is the runtime environment where all the smart contracts run. Recall that EVM is a quasi-Turing complete machine: it's turing complete because the underlying programming language supports arbitrary logic unbounded complexity, but it's also bounded by the amount of Gas provided as part of every transaction.
The Ethereum code runs within the EVM and it is written in a low level stack based language referred to as the EVM Machine Code. This code consists of a series of bytes (therefore referred to as a bytecode) where every byte represents a single operation. So the opcodes are very simple and each of them is a single byte.
EVM Arquitecture
Computer architectures are typically classified into either von Neumann architecture or Harvard architecture. This depends on how code and data are handled within the architecture: Are they stored together? Are they transported over the buses together? How are they cached? And so on...
In the case of the EVM, the code is stored separately in a virtual ROM and there is a special instruction to access the EVM code.
EVM is a very simple stack based architecture: the operands for EVM instructions are placed on the stack and the output of those instructions is also returned on the stack. There's no concept of registers, virtual registers or anything like that.
Every architecture has a concept of a word and in the case of the EVM, the word size is 256 bits. It's believed that this was chosen to facilitate some of the fundamental operations around the 256 hash scheme and the elliptic curve computations.
The architecture is made up of four fundamental components:
The stack The EVM has 1024 elements in the stack and each of those elements is 256 bits in length (equal to the word size). EVM instructions are allowed to operate with the top 16 stack elements. Most EVM instructions operate with the stack (because it's a stack based architecture) and there are also stack specific operations.
The volatile memory: in EVM, data placed in memory is not persistent across transactions on the blockchain. It is also linear (it's a byte array and therefore addressable at byte level) and zero initialized.\
There are three specific instructions that operate with memory, such as
MLOAD
which loads a word from memory and puts it onto the stack;MSTORE
which stores a word in memory from the stack; andMSTORE8
which stores a single byte in memory from the stack. These instructions (and more) will be reviewed in more detail in the following sections.The non-volatile storage. Unlike memory, storage in EVM is non-volatile: data put in storage is persistent across transactions on the blockchain. It is implemented as a
(key, value)
store between 256 bit keys and 256 bit values, and it is also zero initialized.\To understand how storage fits in within the concept of accounts and the blocks on the blockchain, recall that every account has a
storageRoot
field. ThisstorageRoot
field, implemented as a modified Merkle-Patricia tree, captures all the storage associated with that account. This is relevant for contract accounts that have associated storage. ThesestorageRoots
within the account are further captured as part ofthe stateRoot
, which is one of the fields in the block header.\There are two instructions that operate specifically on storage:
SLOAD
which loads a word from the storage and puts it onto the stack; andSSTORE
which takes a word from the stack and puts it into storage.Calldata
: it is used specifically for data parameters of transactions and message calls. It is read only (it cannot be written to) and it's also bite addressable.\There are three specific instructions that operate with call data:
CALLDATASIZE
which gives the size of the supplied call data and puts it onto the stack;CALLDATALOAD
which loads the call data supplied onto the stack; andCALLDATACOPY
that copies the supply call data to specific region of memory.
EVM Ordering
Another concept typically associated with architectures is the concept of ordering: big-endian ordering versus little-endian ordering. In the case of the EVM, it uses the big-endian ordering: the most significant byte of a word is stored at the smallest memory address while the least significant byte is stored at the largest address.
Instruction Set
Gas Costs
We have talked about Gas in the context of transaction, in the context of the block Gas limit and so on... But where it really matters is in the context of Turing complexity and quasi-Turing completeness. The boundedness imposed on the EVM programming language is stemming from the Gas costs that are associated with each of the different EVM instructions
All these instructions have different Gas costs and the reason for that is because each of them has a different requirements when it comes to the computation processing power of the executing Ethereum node, and also the storage requirements, memory accesses and the disk accesses on the real physical hardware that's running the Ethereum node in the context of a miner or anyone else.
When we look at the Gas costs, the simplest instructions like STOP
, INVALID
and REVERT
(that only affect the executing context in a very special way, without having a very high demand or no demand on the processing or the storage of that executing physical hardware), the Gas cost is zero.
For most of the arithmetic, logic and stack instructions, the Gas costs vary between 3 to 5 Gas units. Let's contrast this with some of the more demanding instructions like the call family of instructions, the BALANCE
, the EXTCODEHASH
, EXPORT
, COPY
..., those kinds of instructions have a much greater processing requirement from the Ethereum node: these now cost 2600 Gas units.
This again contrast with the memory instructions like MLOAD
and MSTORE
, which within the context of the EVM are very simple instructions that operate on EVM's internal data structures. These memory instructions cost only 3 Gas units.
However the storage instructions like SLOAD
and SSTORE
, because they deal with persistent state and have to access the disk or the persistent state within the physical machine of that Ethereum node, cost much more than the memory instructions: SLOAD
costs 2100 Gas and SSTORE
costs 20000 Gas units.
To set a storage slot costs 20000 Gas. To change that storage value from zero to a non-zero value (and there are optimizations here) costs only 5000 Gas in some of the other situations.
These Gas costs have changed over the duration of the last 5 to 6 years as Ethereum has evolved. These changes happen to prevent some denial of service attacks that have also happened in the past.
This can be researched in the documentation by looking at some of the EIPs that have been created specifically to address the Gas cost of these instructions in some of the most recent upgrades (like the Berlin upgrade) to see why these costs Gas costs were changed for some of these more demanding instructions and the rationale behind it.
These become important because not only they address the optimization aspect when somebody is deploying a contract (Gas usage becomes important because it affects the user experience of the user working or interfacing with these contracts) but from a security perspective (these Gas costs become important from the denial of service context as well).
The final set of instructions where the Gas costs are really high are the CREATE
instruction (which is probably the most expensive instruction with a cost of 32000 Gas units; and as you can imagine this is because create results in a new contract account being created, so a lot of the data structures within the EVM context are created, registered, have to be made persistent and so on...) and SELFDESTRUCT
(it costs 5000 Gas units).
Last updated