Understanding the stack based architecture of EVM
Introduction:
As you may know, blockchains such as Ethereum, Polygon, Binance Chain, Optimism etc use EVM, Ethereum Virtual Machine, as the underlying environment to execute the transaction and run the smart contracts. The EVM itself is a stack based architecture as opposed to register based architecture which is more common among the traditional processors and computers. In this article we will shed light on the architecture of EVM and why flaws like stack-too-deep occur in smart contracts.
EVM:
Before diving into the architecture, let us first define an EVM and explain how it works. So EVM is an abbreviation for Ethereum Virtual Machine, and it is the execution component responsible for running and executing smart contracts on Ethereum and other blockchains utilizing bytecode stored on the chain. It is a sandboxed virtual machine that runs on each node in the Ethereum network and is in charge of keeping the blockchain alive. The EVM is platform-independent and may be implemented in any programming language. This enables developers to create smart contracts in well-known programming languages such as Solidity, Vyper, and others. After writing the contract, it is compiled into bytecode and deployed to the EVM.
When we state that EVM is a stack-based machine, we mean that it operates on a data structure called a stack, which holds values and executes actions. The EVM has its own set of instructions known as opcodes that it uses to execute tasks such as reading and writing to storage, calling other contracts, and performing mathematical operations. This enables Ethereum to support a wide range of decentralized applications, from simple token systems to large decentralized autonomous organizations (DAOs).
What is Stack Based Architecture:
So, what is a stack-based architecture? It is a sort of computer architecture that stores values and performs actions using a stack data structure. The stack operates on a last-in, first-out (LIFO) basis, which means that the most recently inserted item is stored at the top of the stack and is the first item removed. The program’s instructions and data are kept in memory in this architecture, and the program’s execution is controlled by a stack pointer that points to the top of the stack. The stack pointer keeps track of where the next value or instruction will be saved or retrieved on the stack.
When a program runs, it adds values to the stack and performs operations on the values that are already there. When a code wants to add two numbers, it pushes the numbers onto the stack and then performs the addition operation on the top two values. The result is then returned to the stack.
One of the most important characteristics of a stack-based architecture is that it allows for highly simple and efficient operation execution. Because the stack is a LIFO data structure, it enables for easy and quick data and instruction handling. This makes it ideal for usage in virtual machines like the Ethereum Virtual Machine (EVM), which must do a huge number of operations in a short period of time.
Register Based Architecture:
The other type of architecture is register-based architecture, it is a sort of computer architecture in which the primary storage for data and instructions is a set of registers within the CPU. Unlike memory-based architectures, such as stack-based architectures, in which data is kept in main memory and accessed by memory addresses, data in register-based architectures is stored directly in the CPU and accessed directly by the CPU’s registers. We will not delve more into this architecture because it is not relevant to the topic.
Types of Storage In EVM:
If we talk about the architecture of EVM, it’s storage can be further divided into four main components that are used to store and access the data during execution, namely EVM Stack, EVM Memory, EVM Storage and Calldata.
EVM Stack:
The Stack is the main component of the whole stack-based architecture, it is the data structure in which the values are stored and operations are executed. It is used to store the operands for mathematical and logical operations, as well as the return values for function calls. The size of the stack or stack depth is 1024 items, and each item is 256 bit word with a total of 16 slots. Each item is pushed on top of the stack and removed from there as well, the opcodes that are used to perform these tasks on the stack are PUSH/POP/SWAP/DUP.
EVM Memory:
The second component is the EVM Memory, it is the volatile memory in the architecture whose data is not persistent across the blockchain, meaning the memory is a random-access data structure that stores temporary data during the execution of a smart contract. It is used to store variables and data structures that are not needed to be stored in the storage. The memory can be resized during the execution of a smart contract, but it is slower and more expensive to access than the stack. The memory is zero initialized and the opcodes that are used for accessing the memory MLOAD, MSTORE, MSTORE8
EVM Storage:
Third one is EVM Storage, which is a non-volatile storage and holds key-value pairs of 256 bits -> 256 bits. The total number of storage slots in a contract is 2²⁵⁶ which is a relatively huge number of slots and each smart contract on the blockchain has its own storage space. During function calls it is used for data that needs to be remembered between function calls. It is used to store variables and data structures that need to be available even after the smart contract execution has ended. The opcodes for accessing storage are SLOAD and SSTORE
CallData:
The last component for storing data is the Calldata, which is a read-only memory. The calldata is laid-out and behaves in the same way as memory does, the only major difference is that it is read-only space where the data parameter of a transaction or a call is held. To use the data in calldata you have to specify byte offset and number of bytes that you want to read. The opcodes that are used for calldata are CALLDATASIZE, CALLDATALOAD, CALLDATACOPY
How Stack-Too-Deep Happens:
Despite its benefits, this architecture has numerous drawbacks. For example, the stack-based architecture is not very good at handling complex data structures like linked lists or trees, which
might be more difficult to handle using a stack. Aside from that, there are some faults that can occur in smart contracts as a result of this architecture, most notably the ‘stack too deep’ issue.
So, what is a stack too deep? When a function or contract exceeds the maximum depth of the call stack, the “stack too deep” error occurs. Assume you call a function in your contract, and each time a function is called, a new frame is added to the stack, and when the function returns, the top frame is removed. In order to prevent infinite recursion and other stack overflow issues, the maximum depth of the call stack in EVM is limited. The maximum stack depth in Solidity is 1024 items with each one of them being 256 bit words. Furthermore, the EVM can only access stack items that are up to 16 slots distant from the top-most slot. So if you push more than 16 items into the stack and then try to retrieve the 17th item, EVM will throw the “Stack too deep” error.
A “Stack too deep” error can occur when a function or contract repeats itself recursively too many times, or when another function calls the original function again, surpassing the maximum stack depth, which means that the number of objects on the stack surpasses 16. It also occurs when functions have an excessive number of local variables, parameters, or return values. It may also occur as a result of infinite recursion, a long chain of contract calls, or a mix of the two.
Why Stack Depth Is Only 1024:
You may be thinking why the stack depth is so undersized, or isn’t there a way to increase the depth?
The key rationale for keeping the depth at 1024 is that if the depth were higher, the contracts would be more expensive to execute since they would require more memory, hence 1024 is a fair choice for the depth limit. Furthermore, having a dynamic depth is troublesome since having a fixed sized depth simplifies the implementation of the EVM. Furthermore, the EVM can only access objects on the stack up to 16 spaces away from the top-most slot. So, even if you had a 4096 items stack, and you could put everything in a 4096 items stack, you’d only have direct access to data stored 16 slots down from the top.
Conclusion:
That was a basic overview of the stack-based architecture of EVM and what data storage components are used and how stack-too-deep issues arise because of the architecture. The EVM’s functionality is complicated, yet it is accessible to novice developers, resulting in a plethora of decentralized apps. However, EVM is not without flaws. The system is also plagued by problems with network throughput and transaction speed. These are now critical issues for the Ethereum development community to address, and tackling them provides a roadmap for Ethereum’s continued use and success.