Kakarot: from the basics to interaction - what makes it work
— OnlyDust
Welcome to our exploration of Kakarot, a forward-thinking implementation of the Ethereum Virtual Machine (EVM) crafted with the Cairo programming language!
This article aims to provide an understanding of Kakarot, highlighting its features and its role within the Ethereum ecosystem. We'll look into its technical aspects and evaluate how it can enhance and streamline your development process.
Whether you're deeply involved in blockchain technology or broadening your development horizons, Kakarot is a topic worthy of your attention.
We’ll start off with the basics (what is Kakarot?) then move on from there on how to interact with it and then finish off with how we can make sure it is EVM-compliant (that will be defined below).
Chapter 1: Basics
What is Kakarot?
To begin with, let’s take a look at what Kakarot is and why we use it.
Here’s a simple explanation from the Kakarot website itself:
Kakarot is an implementation of the Ethereum Virtual Machine (EVM), developed using the Cairo programming language. It can be compared to Geth’s EVM, which is written in Golang. The main differentiating factor for an EVM written in Cairo is that its transactions are provable. This capability is achieved by utilizing the CairoVM, a Turing-complete provable CPU architecture, which serves as the underlying layer for Kakarot. A zkEVM, or zero-knowledge EVM, is essentially an EVM where transactions can be proven and verified.
Great, so why do we use it? Why Kakarot?
Starknet is a layer-2 scaling solution for Ethereum that aims to improve scalability and reduce transaction costs.
Starknet uses zero-knowledge proofs, a cryptographic technique that allows a prover to prove possession of certain information without revealing the information itself. This enables the creation of succinct proofs that can be submitted to the Ethereum mainnet, providing a trustless and efficient mechanism for off-chain transaction processing.
But Starknet is not EVM compatible. All dapps must therefore be re-implemented in order to be deployed on Starknet. This means that each project has to spend a lot of energy rewriting smart contracts, redoing tests and audits, etc.
That’s where Kakarot comes in!
The idea behind Kakarot is to redeploy an EVM-compatible smart contract on a StarknetOS chain (also called CairoVM chains, or Starknet appchains). This means that dapps can be rapidly deployed on this new network, without having to rewrite contracts and benefiting from the tests and audits already carried out on the EVM contract.
But how does Kakarot execute an EVM contract on StarknetOS that isn’t EVM-compatible?
To understand this, we need to understand the basics of how Ethereum Virtual Machine (EVM) works.
Understanding the Ethereum Virtual Machine (EVM)
The Ethereum Virtual Machine (EVM) is at the heart of the Ethereum blockchain. It is a core component that enables the execution of smart contracts and the management of transactions on the chain.
The EVM is a Turing-complete virtual machine, meaning it can perform any computable operation. However, it is specifically designed for executing smart contracts.
Converting Solidity to Bytecode
When a developer creates a smart contract in Solidity, they write human-readable source code. This source code needs to be compiled into bytecode, a binary format that the EVM can understand and execute.
Here are the steps to convert a Solidity contract into bytecode:
- Writing the Contract in Solidity
The developer writes the contract using the Solidity programming language. This contract can perform various operations, store data, and interact with other contracts.Here is an example of a SimpleCounter
contract with an increment()
function that increments count
. The source code of this contract is human-readable, but not EVM-readable.
- Compilation into Bytecode
The Solidity contract is compiled using a Solidity compiler, such as solc. The compilation result is a binary file containing the bytecode of the contract. This bytecode is what will be executed on the EVM. Here is the result of compiling the previous SimpleCounter
contract. The bytecode of this contract is not human-readable, but it is readable by the EVM.
Contract Deployment The generated bytecode is then deployed to the Ethereum blockchain as a smart contract.
Bytecode Structure
The bytecode of an Ethereum smart contract is a sequence of binary data. Each byte in this bytecode has a specific meaning.
Here’s how it’s typically structured:
- Opcodes
Opcodes are instructions that tell the EVM what operations to perform. Each opcode is represented by a byte and corresponds to a fundamental operation (you can find the list of all opcodes here: evm.codes). For example, the opcode0x60
means to “PUSH1
”, which pushes a one byte value onto the stack. The byte that is pushed is specified immediately after thePUSH1
opcode in the bytecode. For example,PUSH1 0x80
means that the value0x80
will be pushed onto the stack. - Parameters
Opcodes are often followed by parameters that provide additional data required for the operation. Parameters can be numerical values, addresses, string data, and more. Parameters are also stored as bytes. For instance, if an opcode instructs to add a value into the stack, the parameter would be the value to be added. - Stack
The EVM uses a stack to temporarily store data. When an operation is performed, parameters are pushed onto the stack, and results are popped from the stack. The stack is a temporary storage space used for complex calculations. - Memory
Memory is a larger storage space that can be used to store data during contract execution. Memory data is accessible by index, and the EVM allows manipulation of this data. - Storage
Storage is a permanent and gas-costly storage space. It’s used to store data between contract executions. Storage data is associated with keys and can be read or modified by other contracts.
Example instruction
Let’s take a concrete example to understand how an opcode with parameters works. Suppose we have the following instruction in the bytecode:
0x60 0x80 0x60 0xFF 0x55
The first byte,0x60
, corresponds to the opcode PUSH1
, meaning that it should push a one byte value onto the stack. This opcode must therefore be followed by a single parameter, the value to be added to the stack. The parameter 0x80
indicates that we should push the value 0x80
onto the stack. Then we again find the opcode 0x60
, which this time stores the value 0xFF
onto the stack.
Next, 0x55
is another opcode, which can be interpreted as SSTORE
, indicating that we should store a value in storage. The SSTORE
opcode retrieves the first stack value to determine the storage address (0xFF
), then the second stack value as the value to be stored in storage (0x80
).
The execution of this example would push the value 0x80
, then the value 0xFF
onto the stack, and then the SSTORE
opcode would store 0x80
in storage at the address 0xFF
.
If you want, you can execute this bytecode step by step with this playground.
It’s essential to note that the EVM is a stack-based machine, which means that most operations involve stack manipulation. Opcodes define the actions, and parameters provide the data necessary to perform these actions.
In summary, the bytecode of a smart contract is a sequence of opcodes and parameters stored as bytes. Opcodes specify actions, and parameters provide the data needed to execute those actions. The stack, memory, and storage are essential components of the EVM that facilitate the execution of smart contracts.
Interpreting Bytecode by the EVM
Once a smart contract is deployed on the blockchain, the EVM is responsible for its execution.
Here’s how the EVM interprets the bytecode:
- Loading Bytecode
The EVM starts by loading the bytecode of the smart contract from the blockchain. This bytecode is stored at a specific address. - Decoding Opcodes
The bytecode is broken down into opcodes, which are specific instructions for the EVM. Each opcode is represented by a byte. For example, the opcode0x60
indicates that the EVM should push data onto the stack. - Executing Opcodes
The EVM executes opcodes sequentially. Each opcode is processed one by one. The execution of each opcode can result in changes to the state of the blockchain, such as modifying the value of a variable stored in the contract. - Gas Consumption
Each operation has a gas cost associated with it. Gas is a unit of measurement for the amount of work performed by the EVM. Users who want to execute a contract must pay in gas, ensuring that the EVM is not overloaded and will never enter an infinite loop, thus preventing a DoS (Denial of Service) of the system. - Termination of Execution
The bytecode execution ends when all opcodes have been processed, or when an error occurs. If execution is successful, all changes to the state of the blockchain are confirmed; otherwise, they are rolled back.
In summary, the Ethereum Virtual Machine is the engine that enables the execution of smart contracts on the Ethereum blockchain. Contracts written in Solidity are converted into bytecode, which is then interpreted and executed by the EVM. The EVM ensures the security and reliability of smart contract execution while using the concept of gas to incentivize users to pay for execution costs.
How Kakarot works?
Now that we’ve seen the basics of how EVM works, let’s try to understand how we can run an EVM-compatible contract on a blockchain that isn’t EVM-compatible.
Let’s imagine that we store EVM-compatible bytecode in Starknet’s storage. We could access it from a smart contract. Since this bytecode is accessible from a contract, we could decode it by implementing the various opcodes in Cairo.
This is what Kakarot does.
If you would like to delve more into this topic, below are a few useful links that can guide you.
In the meantime, on to part two where we will go through how to interact with Kakarot.
Chapter 2: Kakarot-RPC
xKakarot-RPC or how to speak EVM to Starknet
Just like in the first part, a little quote which briefly explains what Kakarot-RPC is may be useful. Here is one taken directly from GitHub, we’ll look into the terms further on:
“Kakarot-RPC is the JSON-RPC server adapter to interact with Kakarot ZK-EVM in a fully EVM-compatible way. The Kakarot-RPC layer's goal is to receive and output EVM-compatible JSON-RPC payloads & calls while interacting with an underlying StarknetOS chain. This enables Kakarot zkEVM to interact with the usual Ethereum tooling: Metamask, Hardhat, Foundry, etc.”
JSON-RPC server
To better understand what the Kakarot-RPC is, let’s first understand what a JSON-RPC is.
A RPC (Remote Procedure Call) is a protocol that allows one program to request a service or a function from another program.
A JSON-RPC server is, thus, a software that listens for incoming RPC requests that use JSON as the data format for encoding messages.
Therefore, Kakarot-RPC is, somewhat, the bridge between classic EVM tools and the Starknet blockchain.
Ok, but how does it work?
JSON-RPC request generation
When interacting with the Ethereum blockchain using Metamask, for example. Signing our transaction will make Metamask generate a JSON-RPC request that it will send to an Ethereum node. The node will then process the request and return a JSON-RPC response.
Here is an example of a JSON-RPC request:
And here is an example of JSON-RPC response:
Kakarot-RPC implication
Now that we understand how web3 tools like Metamask facilitate communication with blockchains via JSON-RPC, let’s dive into what Kakarot-RPC does for us. This tool serves as a sort of intermediary, allowing us to interact with the Starknet blockchain, instead of the Ethereum one.
Kakarot-RPC will therefore position itself between the user interface tool, such as Metamask, and the Starknet blockchain. It will listen for EVM transaction requests, convert them into a format compatible with Starknet, and then forward these transactions for processing on the Starknet blockchain. In turn, a response will be relayed back.
To better illustrate this process, because a picture is worth a thousand words, have a look at the process below:
The process therefore goes through the following order:
- Recover the EVM address of the sender
The address of the sender is recovered from the JSON-RPC requests.
- Recover EthAA address on Starknet
Recover the address of the equivalent Ethereum account abstraction on Starknet.
- Provide coherent gas estimation
Based on the requests in the transaction, Kakarot-RPC will provide a new gas estimation.
- Submit TX with sender_address = EthAA address
Kakarot-RPC will replace the EVM sender address by the equivalent EthAA address recovered earlier and then sends the transaction to the Starknet blockchain.
Inside Starknet
Once the transaction has been sent to the Starknet blockchain with the new sender address, the abstracted account’s validation function steps in. It’s primary task is to verify the signature of the EVM transaction. Only upon successful validation does it trigger the execute function.
This last function serves as the decoder for the transaction, extracting vital details like the user's request. At this part, it employs a Kakarot-specific function to process the decoded opcodes. This processing is carried out using CairoVM, emulating the execution as if it were happening within the EVM itself.
As we've seen, Kakarot-RPC serves as a sort of bridge, making communication between web3 tools and Starknet more seamless by ensuring a smooth translation of EVM transactions.
But how can we be sure that Kakarot is compliant with EVM standards? In the next part, we’ll take a look at EF tests, how they work to maintain network consistency and then conclude.
Chapter 3: Ethereum Foundation (EF) -Tests
How can we be sure that Kakarot is EVM-compliant?
When going deeper into the different layers of Kakarot, it’s worth looking into the role of EF tests and how they help ensure its functionality within the Ethereum ecosystem. In this section, we'll explore the specifics of these EF Tests, their components, and how Kakarot adapts and conforms to these critical standards to uphold its integrity and reliability in the blockchain domain.
So, first what are EF tests?
Defining EF tests
The Ethereum Foundation (EF) provides a suite of standard Execution Layer tests, known as EF Tests, that any Ethereum client should comply with. These tests, crucial for maintaining network consistency, are designed to be independent of the programming language used in the client’s implementation.
For example, Ethereum clients like Nethermind (C#, .NET), Go-Ethereum (Go), Besu (Java) and Reth (Rust) all use the EF Tests to make sure their execution layer works as expected by the Ethereum Network specifications.
Purpose and Scope of EF Tests
What are these tests doing?
EF tests are composed of JSON files filled with data, and cover all the components of the (Ethereum Virtual Machine) EVM. While some tests are designed to pass, others are expected to fail, thereby covering a broad range of scenarios. For example, there are some test suites specifically focused on testing transactions, while others deal with the general state or with specific EIPs (Ethereum Improvement Proposals).
Kakarot's integration of EF Tests within its ZK-EVM project shows its commitment to align with official EVM implementation standards. As stated in the EF-Tests repository of Kakarot Labs on Github, passing these tests instills confidence in the client's execution layer:
“The Ethereum Foundation provides a suite of official tests to verify the compliance of EVM clients. Passing all these tests allows a client to gain confidence in his execution layer.”
Therefore, it is of paramount importance that Kakarot uses these test suites to make sure that the EVM implementation in Cairo (and its constraints) matches the expected behavior.
Kakarot's Alignment with EF Test Suites
Some may wonder if Kakarot is supposed to comply with these EF test suites.
Due to its nature as a ZK-EVM implemented via Cairo contracts, running on the StarknetOS chains, EF Tests would need slight adjustments in order to fit perfectly with Kakarot implementation.
Kakarot also evolves in a more constrained environment, due to the immutable memory model of Cairo VM, essential for generating STARK validity proofs, and presents additional challenges. For instance, perfectly matching EF Tests for edge cases may be quite challenging.
EF Tests and Kakarot's Execution Layer
How are EF Tests therefore running to test Kakarot execution layer implementation?
Given the fact that Kakarot implements its own RPC, it could have been possible to run official EF Tests using the Ethereum Foundation Runner (retesteth). Nevertheless, Kakarot Labs decided to implement its own simplified test runner to make testing easier and more suitable, limiting the possible number of interactions and avoiding the risk of introducing errors.
To conclude, Kakarot is a great example of innovation in blockchain technology, offering a strong solution for integrating EVM-compatible contracts in a Cairo VM environment. It stands out for its adaptability and flexibility, embodying the principle of "change the network and it just works.” Thanks to its strong solution for integrating EVM-compatible contracts in the network, this integration not only simplifies development but also leverages existing EVM frameworks to ensure efficiency and security.
Moreover, the incorporation of EF Tests highlights Kakarot's commitment to EVM standards, addressing challenges with personalized solutions. As the web3 landscape evolves, Kakarot can be seen as an important tool, allowing developers to explore new possibilities within a scalable and efficient ecosystem.
Looking ahead, Kakarot's reliance on the Cairo architecture positions itself as a key player in adapting to Ethereum's changes, offering a scalable and efficient platform for further developments. It’s a promising future for this project, and we are excited to be part of this journey!
As a reminder, OnlyDust is a platform that helps connect developers with the most exciting open-source projects. The added bonus? A strong community and rewards for contributions.