// inside head tag

Cairo fundamentals stacked up against EVM and Solidity

Starknet

December 16, 2022

Introduction

A pivotal step in StarkNet’s advancement, Regenesis will mark the point at which only Cairo 1.0 contracts can be deployed and executed. In preparation for this transition, which will require developers and users alike to go through simple upgrades, we’d like to revisit the fundamental principles around Cairo, the programming language on StarkNet.

An increasing number of Solidity protocols are expanding onto StarkNet, making use of the vastly improved scalability without compromising Ethereum’s decentralization and security. However, as a relatively new language, smart contract development in Cairo may come with its own challenges. At Nethermind, we have gained experience with Cairo across several projects and are committed to sharing tips and recommendations to help developers get comfortable with Cairo.

In this article, we illustrate key differences between Cairo and EVM (the Ethereum Virtual Machine) as well as Cairo and Solidity smart contract development.

Cairo vs. the Ethereum Virtual Machine

The Cairo language was proposed to make proving computation easier. The majority of existing proof systems available require you to turn your program into an algebraic circuit, which is far from trivial. Cairo, on the other hand, is a CPU architecture designed to be easily described as a set of polynomial equations, which can be used for proving that the execution of a program is valid. To achieve this, design decisions were made that resulted in Cairo being significantly different from the Ethereum Virtual Machine. We explore these differences below:

Data Type

Unlike the Ethereum Virtual Machine (EVM) which relies on 32 byte integers for arithmetic operations, Cairo uses the felt (field element) data type. A felt is an integer x in the range of 0 ≤ x < P, where P is a very large prime with 252 bits. All arithmetic is done modulo P, which impacts how division is done in Cairo. This leads to an interesting behavior for division, where a divided by b equals to c is handled in such a way that b multiplied by c is equal to a. When a is a multiple of b, division will run normally. However, when a is not a multiple of b, Cairo will find an integer c that, when multiplied by b, equals a (recall that all operations are done modulo P). In practice, the number c will make the operation b * c overflow and wrap around (due to modulo P) to reach the value a. For instance, 6 divided by 3 is equal to 2, but 7 divided by 3 is equal to:

1,206,167,596,222,043,737,899,107,594,365,023,368,541,035,738,443,865,566,657,697,352,045,290,673,496.

For more information, check this reference.

Registers and Stack

In StarkNet, a word has 252 bits (to match the felt datatype). On the other hand, the EVM has a word composed of 256 bits. Not only do they differ in their base data representation, but they also have multiple differences in their design. The EVM uses a bounded stack-based model. This can be thought of as a machine with many general-purpose registers. The EVM puts all the operands for operations in the stack, and the operations can modify the stack by adding, removing, or swapping values. Some operations performed on the EVM involve reading from and writing to memory cells. In Cairo’s case, there is no structure similar to general-purpose registers. All operations are done directly over the memory cells.

Memory Model

The memory models of the EVM and Cairo are entirely different. The EVM’s memory model is random access for reading and writing. Specifically, the memory is a word-addressed byte array. This memory is readable and writable through different operations, like MLOAD and MSTORE. Even though the whole addressable space of memory is available, the current size is measured by the highest address that was accessed. In contrast, Cairo uses a nondeterministic read-only memory model, where the value of each memory cell is decided by the prover. The memory is effectively immutable as far as the Cairo program is concerned. Once a memory slot is written to, it cannot be overwritten, but only read from. The Cairo memory address space is also contiguous, meaning you write to memory one cell at a time and cannot skip over memory slots. It is worth mentioning that with the memory model being both immutable and contiguous, recursion is used rather than loops for any segments of code that should repeat multiple times.

Let us now dive into the differences between writing Cairo and Solidity smart contracts — what should smart contract developers look out for?

Cairo vs. Solidity Smart Contracts

Proved computations require a different memory model

In Cairo, the memory is filled by the prover, and we need to ensure that every memory cell has the proper value. Cairo programs are designed to prove the correct execution of the code, which is a new way of writing code.

Differences when writing smart contracts

The composition of Smart Contracts in Solidity involves the definition of classes having a well-defined set of methods and properties. Cairo has a different approach, where smart contracts are a collection of functions that can be imported from multiple files.

Layer-1 <> Layer-2 interaction is smooth

The interaction between Layer-1 (Solidity) and Layer-2 (Cairo) is easily achieved using built-in functions. StarkNet is a Layer-2 solution that settles on Ethereum and includes a native mechanism for interacting with Layer-1 — typically, a smart contract written in Solidity. To avoid data conversion errors between the layers, we need a clear understanding of how this mechanism works. A Layer-1 solidity code can send messages to the Layer-2 Cairo contract using the code below:

In this code, the STARKNET_MESSAGING_CONTRACT_ADDRESS is the address of the messaging contract. This address depends on the network being used. At the moment of writing, the messaging contract is available in the alpha-goerli and the mainnet networks. The address of the messaging contracts can be consulted here. The TO_ADDRESS is the address of your Cairo contract, while the SELECTOR indicates the function that we are calling in the Cairo contract. Finally, the PAYLOAD contains the data that we are sending from Layer-1 (Ethereum) to Layer-2 (StarkNet). Similarly, the Solidity code can confirm if a message was sent from Layer-2 by using this piece of code:

This call will only succeed if there is a non-consumed message, with the specified PAYLOAD, sent from FROM_ADDRESS in Layer-2 to the L1 caller. The message will be consumed after finishing the call. For safety reasons, any wrong data will lead to a failure in consuming the message. On Layer-2, in order to receive a message, the Cairo contract must specify one or more L1 handlers using the decorator @l1_handler.

In order to send a message from Layer-2 to Layer-1, the procedure is simple. Call the function send_message_to_l1(…) passing the proper arguments: the address of the Solidity smart contract in Layer-1, the length of the payload, and the reference to the payload data.

This process is rather easy. More information about the communication between Layer-1 and Layer-2 can be accessed here.

Virtual Machines with different word sizes

It is important to keep in mind that Ethereum and StarkNet do not share a common base structure — recall that the EVM word is 256 bits long, while the Cairo word is 252 bits long. Thus, in order to represent one EVM word, Cairo uses two words in the structure Uint256 defined in the library starkware.cairo.common.uint256. The structure is reproduced below:

Although addresses exist on both Ethereum and Starknet, they have key differences

Since they have different bounds, both Solidity and Cairo code must sanitize addresses between layers. Ethereum addresses have a length of 40 hexadecimal characters (160 bits). Checking if an Ethereum address is within a valid range can be done using this command:

https://cdn-images-1.medium.com/max/800/1*oeqH0mXPV8QIsc6tIQquTw.png

Restricted use of loops is another interesting feature of Cairo

When you think of running a segment of code multiple times, for most developers the for/while/do loops come to mind. However, the Cairo memory model is contiguous and immutable by design, so we cannot update the loop counter. Instead of using loops, in most situations, we have to write recursive algorithms. As an example, below is Cairo code for printing the values 1 to 10 in steps of 1.

Conclusion

While there are many small and large differences between Ethereum and Starknet, the arithmetic differences with felts and the immutable memory stand out the most. Keep these in mind, and your transition into Cairo development should go smoothly.

In the following Cairo-focused blog post, we’ll take a first look into Cairo 1.0 specifications: key changes and additions.

Are you interested in StarkNet but not quite ready to learn the Cairo language? At Nethermind we have developed Warp, an EVM to Cairo transpiler. Simply write your contracts in Solidity and transpile them to Cairo, ready to be deployed on StarkNet.

Disclaimer:

Please note that the above article is the product of our experience writing Cairo here at Nethermind, and has been prepared for the general information and understanding of the readers. No representation or warranty, express or implied, is given by Nethermind as to the accuracy or completeness of the information or opinions contained in the above article. Furthermore, no representation or warranty, express or implied, is given as to the quality of any code or project that may be developed by the reader, including without limitation any code and projects written in Cairo. No third party should rely on this article in any way. This article is not intended, and should not be relied on, as any form of financial, investment, tax, legal, regulatory, or other advice.

Latest articles