Although it may seem challenging, it will bring us much closer to our main goal: we will learn more about our favorite technology and improve our coding skills further down the road by building on the understanding of the core principles.
Before you scroll through the article, feel free to get a quick overview by scrolling through our PDF slides:
The Ethereum Virtual Machine, or as we will usually call it by its pet name, EVM, represents a runtime environment for Ethereum smart contracts.
For those of us who may be unfamiliar with the term, a runtime environment represents a computer infrastructure that enables a computer program execution. It usually consists of a software section (instruction set and software libraries) and a hardware section (working memory, storage, and a network).
An application runtime environment can have several flavors.
The most basic one is a bare-metal runtime environment, providing complete, unrestricted access to the underlying hardware and software resources of a physical host. This type of runtime environment is very rarely in use.
More complex and manageable runtime environments use an abstraction of physical host software and hardware resources and restrict an application from accessing the resources directly.
When physical host resources are managed and accessed in such a way, we consider them to be virtualized environments. From a resource accessibility, i.e. security perspective, virtualization can provide a sandboxed or isolated runtime environment. The difference lies in what an application can do when it runs.
A sandboxed application can access virtualized resources, such as network, filesystem, or other running applications’ processes. An isolated application is completely restricted from accessing anything other than its own process instance, and with possible exceptions, some other subset of running processes.
In that sense, an EVM runtime is an isolated one, enabling only limited access to other smart contracts.
The EVM, and by extension, Solidity support two kinds of accounts in Ethereum: the external accounts and the contract accounts. Both kinds of accounts share the same address space, but they also differ in the way they are controlled.
The external accounts are controlled by public-private key pairs, i.e. humans.
For the more interested among us, I recommend checking out the topic of asymmetric cryptography. I won’t go into much detail about it, but I’ll give a coarse summary: asymmetric cryptography implies a key pair where one of the keys is used for encryption, and the other one is used for decryption.
💡 The emphasis is on this crucial property: a single key cannot be used for both encryption and decryption in the same <plain data> encryption <encrypted data> decryption <plain data> operation sequence. You’ll choose one of the keys as a public key and share that key with the world; the other key will be your private key.
ℹ️ Info: a key’s ownership role (public or private) doesn’t determine its cryptographic role, i.e. both keys can be used in different encryption and decryption operation sequences.
In contrast to external accounts, contract accounts have their code attached to them, and the code (behavior) is what controls the contract account.
In the case of an external account, a public key determines the contract’s address. In the case of a contract account, its address is calculated at the time of the contract’s creation, based on the contract creator’s address and a nonce, i.e. a number of transactions sent from the contract creator’s address.
ℹ️ Definition: A nonce is a neologism (a new-coined word), meaning a “number used once”. A nonce, or to be more specific, a cryptographic nonce is a random or semi-random number used in many cryptographic processes. It ensures that there are no reused values when a process requires randomness.
From another point of view, we could make a simple distinction between the two account types by noticing that the contract accounts store code, and the external accounts don’t store code. EVM considers both types of accounts to be equivalent.
Every account is equipped with a persistent key-value memory object – storage, that maps 256-bit words to 256-bit words. Also, every account has a balance expressed in Ether’s sub-currency Wei, which gets modified by sending transactions that include Ether. One unit of Ether consists of 1018 units of Wei.
Accounts exchange messages to communicate in both directions, and a term transaction means one message exchanged in a communication session.
A transaction can be empty, it can also contain some binary data called payload,or it can carry Ether (Wei).
When a transaction is sent to a contract account, the contract account code is executed, and the transaction payload is interpreted as the contract input data.
If a transaction with a payload is being sent without specifying the recipient account (or if the recipient is set to null), the transaction will result in creating a new contract with an address calculated as we previously described.
In a described case, the payload is interpreted as EVM bytecode, and it is executed, creating, in turn, the code in its final form required for a smart contract to run.
💡 Summarized, what we start with is the smart contract source code, such as the one we use in our code examples. The source code gets compiled to the EVM bytecode, which is sent in a transaction, executed in an EVM runtime, and permanently stored as the code of the contract.
ℹ️ Info: “Bytecode is computer object code that an interpreter converts into binary machine code so it can be read by a computer’s hardware processor”. In general, bytecode is intermediary code, usually produced after compiling the original source code. Probably one of the most obvious advantages of using bytecode is semi-optimized, multiplatform-portable code.
At first, the contract gets created, then the constructor gets executed, and only after the constructor finishes the contract’s code is stored and available. Due to this delicate order of steps, a contract may be referenced and called into only if the constructor finished constructing the contract.
Contract-creating transactions and any other kinds of executed transactions are taxed with a certain cost.
This cost is expressed in special units called gas (this type of economy slightly reminds me of the one based on gasoline in a postapocalyptic movie classic Mad Max).
The gas units are paid for by the originator (initiator) of the transaction (we can view the originator’s account address in the
As the transaction is executed on an EVM, the gas gets gradually consumed in steps, according to a set of predefined rules. If case of an event when the entire amount of gas gets depleted before the transaction completes (the available amount of gas becomes smaller than zero, i.e. negative), the out-of-gas exception is raised, meaning that transaction execution ends and all its state changes in the current call frame are reverted.
🧠 Reminder: remember (from the previous article of this Solidity Crash Course) that every transaction ensures state consistency by successfully completing or reverting the changes it made up to the point of an exception.
By now, we probably wonder why we even have to have gas metering and taxing. The answer is made for two reasons.
- The first reason lies in a need for economical use of the EVM’s execution time. When a transaction takes a shorter time, it inherently consumes less gas, so the transaction originator is incentivized to make the transaction as short as possible. EVM execution is only possible because EVM executors, i.e. miners and stakers invest their equipment’s time and energy in mining (validating) the transaction block (every transaction is bundled in a block).
- This brings up the second reason: the consumed gas also compensates the EVM executors for their work. In other words, taxing the transaction originator works like a blind auction: the EVM executors will naturally be more inclined to accept the blocks that will provide them with a higher compensation = gas price * gas amount. It is up to the transaction originators to set the gas price somewhere in the sensible range to ensure their transaction block will be picked up and mined (validated) by an EVM executor. In case the transaction originator foresaw more than enough gas for the transaction, possible leftover gas will be refunded back to the transaction originator.
However, the gas price auctioning cannot continue indefinitely, again, for two reasons.
- First, there is a maximum amount of gas that can be charged for each block, so the maximum amount of work invested in mining a block is limited.
- Second, although the transaction originator may, in theory, set his gas price arbitrarily high, at some point, the gas price would exceed the real economic value represented by the transaction, so setting the gas price above that level would make no sense, i.e. it would result in a loss for the transaction originator.
- Third, we can easily recognize that it would make no sense to set the gas price too low simply because the incentive for a transaction block would not be interesting enough for an EVM executor to pick up the block and mine (validate) it.
In this article, we started creating a more detailed insight into the core principles of the Ethereum Virtual Machine.
- First, we acquired a taste for the Ethereum Virtual Machine as a runtime environment.
- Second, we started wondering about how cooking the books feels… Just kidding, that’s not that kind of account, but nevertheless, it’s still very interesting and crucial to understanding.
- Third, we faced the almighty transactions and saw how they breathe and live. As it turns out, it wasn’t so scary. Nonetheless, we’ll continue building on top of that knowledge on the first possible occasion.
- Fourth, we dipped our hands in gas and enjoyed the smell of knowledge we’ve gotten for understanding how it drives the Ethereum network. And we also remembered Mad Max.
This tutorial is part of our extended Solidity documentation with videos and more accessible examples and explanations. You can navigate the series here (all links open in a new tab):
Programmer Humor – Blockchain
I’m an experienced computer science engineer and technology enthusiast dedicated to understanding how the world works and using my knowledge and ability to advance it. I’m focused on becoming an expert in Solidity and crypto technology, with a passion for coding, learning, and contributing to the Finxter mission of increasing the collective intelligence of humanity.