Moving from Solidity to Cairo
Published on: June 28, 2023

Moving from Solidity to Cairo

Guiding Devs through the Expansion into Cairo Programming

 

Tl;dr

  • Developers are expanding their skills to include Cairo, driven by Starknet’s increasing prominence.
  • Solidity enabled globally interoperable smart contracts but required extensive computation repetition.
  • Cairo’s efficiency stems from its ability to create verifiable transaction execution proofs using a single machine.
  • Both Cairo and Solidity are smart contract languages, but their functionalities and principles are distinct.

Introduction

With the growth of Starknet (and continued excitement around rollups), Cairo has become one of the most in-demand skills in web3.

Cairo — which is used for writing smart contracts on Starknet and StarkEx as well as scaling applications such as dYdX and ImmutableX — has seen its number of full-time developers increase by a staggering 875% over the past two years and by 83% year-on-year. And since Cairo can be used outside blockchains whenever any proof of computation is required, adoption of Cairo by developers is expected to only rise.

Many of these new Cairo developers are moving over from Solidity — the smart contract language on L1. If you’re one of the developers making this move, we’re here to help!

In this article, we’ll give you a developer’s guide to moving from Solidity to Cairo. We’ll talk about what makes Cairo different, how Cairo works from a technical perspective, and then look at some code examples to show the key differences between the languages.

Let’s get started!

What Is Cairo?

Cairo is the Rust-inspired, native smart contract language for Starknet.

According to the Cairo docs, “Cairo is the first Turing-complete language for creating STARK-provable programs for general computation.” Let’s break that definition down.

Turing-complete means this programming language can simulate a Turing-complete machine. A Turing-complete (named after Alan Turing) machine is a computer that can perform any computation and execute any algorithm if given enough time and resources.

STARK-provable means that programs written in Cairo can be executed so that a STARK proof of the execution can be efficiently created. When a program written in Cairo is executed on any machine, a newly generated STARK (Scalable Transparent ARguments of Knowledge) proof is created that allows any independent verifier to succinctly verify the honest execution of the program — without having to trust the machine on which the program’s computations were performed.

Cairo 1.0 was just released this spring (replacing Cairo 0) and is already available on Starknet Mainnet.

Which Is Better — Cairo or Solidity?

Neither! In some ways, it is an “apples to oranges” comparison.

Solidity is the language first used for widely adopting composable computation — a programming paradigm in which different system components can be used in sync and composed into complicated software by using different components as Lego blocks.

Solidity made it possible for smart contracts to be intricately intertwined due to the following:

  • Byte code that was made public and transparent to the world
  • Standard ABI structures (that allowed others to interact with external smart contracts)
  • Use of the Ethereum network to have an almost 100% liveness (or uptime)
  • Use of the Ethereum network as a means of executing financial transactions
  • Global interoperability of smart contracts due to a single common EVM (a copy of which is held by all the validators in the network)

Even though Solidity achieved a myriad of firsts by being the most widely adopted smart contract language, Cairo has a single overpowering property: its ability to create provable programs for general computation.

This single differentiating property allows us to move from a system where every computation has to be repeated hundreds of thousands of times (that’s the number of validators in the Ethereum network) to a newer system where only a single machine (called the prover) creates a proof of correct execution of a transaction that others on the network can verify.

Differences Between the Cairo Virtual Machine and Ethereum Virtual Machine

For Cairo programs’ execution to be efficiently converted into a STARK proof, there are a few key differences between the Cairo virtual machine and the EVM (Ethereum Virtual Machine):

  1. Memory Model. The Cairo VM uses a single-write-only memory model. A memory slot cannot be overwritten (unlike the EVM). Even though this may seem like a highly complex overhead, the Cairo compiler solves this problem, and the abstracted version allows mutable variables to be used in code (more on this later). The single-write memory model makes Cairo a more predictable and verifiable system. Each memory address is written only once, and it retains its value until the execution of the program is complete. This means there’s a clear, unchanging record of the computation, which is vital when generating STARK proofs, as they rely on verifying the correctness of computations.
  2. Fields. When we talk of cryptographic proofs, we almost always speak of these proofs being created for operations on elements of a certain field. As discussed in our article on arithmetization, in mathematics, a field is a set of elements with two binary operations, addition, and multiplication. It satisfies certain axioms, such as closure, associativity, commutativity, and the existence of inverses and identity elements. Examples of fields include real numbers, rational numbers, and complex numbers. The elements of such a field are the subjects of operations that are part of any provable general computation. When discussing fields, it is important to note that some fields are finite (the one used in Cairo is also finite). In a finite field, the number of elements is finite, and all the elements are less than the highest “order” of the field. The “order” of a field equals the highest number that the elements of a field can attain plus one. Also, all addition and multiplication operations result in a number, the module of the highest order. So, for example, in a field of order 6, the addition 3+4 would result in (3+4) mod 6 = 7 mod 6 = 1. Cairo uses a finite field of order 2²⁵¹ + 17 * 2¹⁹² + 1. Since, while generating proofs, all the elements need to be field elements, the Cairo VM uses field elements as the base for all the operations. It does not follow the conventional system of uint256 or uint32 as the EVM does. However, abstraction techniques can be created to use the more convenient uint256 type (or similar). However, these structures require more resources (increasing the number of operations proving the same in order of tens) for execution.
  3. Inheritance and Polymorphism. Cairo does not have the concepts of inheritance and polymorphism. Though contracts can be extended by importing specific functions and storage variables, the much-used concepts of object-oriented programming require a bit of “out-of-the-box” thinking.

Cairo Compiles to Sierra

One step to be aware of is that when you write a Cairo smart contract, it is first converted into Sierra code, which must be published on the network. Sierra code is an abstraction over the raw Cairo assembly (CASM) code interpreted by the Cairo VM. The compilation of Cairo code to Sierra surrounds the Cairo code with some security measures, the most important of which is a mechanism to avoid DoS attacks.

According to Starknet documentation, “A crucial property of every decentralized L2 is that the sequencers are guaranteed to be compensated for work they do. The notion of reverted transactions is a good example: even if the user’s transaction failed mid-execution, the sequencer should be able to include it in a block and charge execution fees up to the point of failure.”

However, sometimes a user may write a line of code or include a transaction, proving the execution is impossible. For example, the statement `assert 1 == 0` is a valid Cairo statement; however, including this execution in a cryptographic proof is not possible since this statement is false, and it translates to polynomial constraints that are not satisfiable. Hence Sierra adds a security layer that ensures even unprovable reverted transactions are charged for. This both mitigates the potential of a DoS attack on the sequencer and satisfies the economic incentives of the sequencer.

Comparing Cairo to Solidity

Now we have an idea of Cairo’s basic types, functions, and structures. Let’s compare those with their Solidity counterparts to draw a few parallels between the two smart contract languages. (Keep in mind that Cairo can also be used to write non-smart contract code that can be used for creating provable programs. However, that is out of our current scope of discussion.)

Note: From this point onward, all discussion regarding Cairo concerns Cairo 1. Cairo 0 is no longer recommended as the go-to language for writing smart contracts on Starknet.

Cairo Types

Here is the list of some fundamental types in Cairo:

fundamental types in Cairo

As you can see in the list above (we just added signed integers and we also have Dict.), starting from Cairo 1, unsigned integers have been added to Cairo, similar to their counterparts in Solidity. Even though using integers may be less cost-effective for the sequencer than directly using felts, integrating integers into Cairo promises to simplify developers’ lives.

Other than that, using Arrays is very similar to the syntax in Rust, and they are similar in logic to those in Solidity.

Functions in Cairo

Functions in Cairo can be of the following types:

  • internal
  • external
  • view
  • constructor

By default, all contract functions are considered internal (such functions can only be called from inside the contract — somewhat similar to private functions in other languages).

External functions are open to the world and can be called by other smart contracts as well — including accounts. (Hooray, account abstraction!)

View functions are a type of external function that can only read the state on-chain. View functions cannot modify the state of the chain.

Constructor is another attribute of functions in Cairo given to … constructors of a smart contract!

Now, let’s compare the syntax of function declaration between Cairo and Solidity:

Functions in Cairo

Let’s look in more detail at a few key differences:

  1. The keyword to declare a function is `fn` in Cairo, while it is `function` in Solidity.
  2. The function types are declared before the function keyword in Cairo (#[view]), but in Solidity, the format is different (see above).
  3. In the Cairo programming language, the syntax for declaring return values involves using the `→` symbol. On the other hand, in Solidity, the keyword `returns` is used to denote return values.

Modules in Cairo

Modules in Cairo serve the purpose of grouping related functionality within a namespace. The keyword `mod` is used to define a module, followed by the module name and a code block that includes functions and other declarations. Modules can import other modules and make use of their functionality.

These are similar to libraries in other languages, and in Solidity, modules can be compared to the inheritance of contracts.

Modules in Cairo

For example, in the above code, `starknet` and `array` modules are imported. The syntax differs from `import` statements in Solidity or inheritance which uses the `is` keyword (see this). Note that the `use` keyword makes all functions from the imported module accessible within the importing module in Cairo.

Arrays in Cairo

Using arrays in Cairo 1.0 has become easier since manipulations of the dynamic array are made possible with the array module’s exported functions such as `append,` `array_at,and `array_len.`

Arrays in Cairo

Sample Cairo Smart Contract

Now that we understand the set of types and functions in Cairo, let’s go over a sample Cairo ERC20 contract code. An ERC20 contract is a standard template smart contract for the tokens that people own on-chain. It allows the users to transfer tokens to one another, check balances for users, and approve transfers to another entity. Here’s our contract:

Note: The Cairo 1 syntax is changing slightly and will be updated later this year (2023). Please see this post for more details.

sample-Cairo-ERC20
ERC20 contract code
users to transfer tokens to one another
check balances for users
approve transfers to another entity

Above code is based on https://github.com/argentlabs/starknet-build/blob/main/cairo1.0/examples/erc20/ERC20.cairo

Let’s look in detail at some of the most important pieces.

Lines 1 and 2 initialize and give the name to the smart contract

#[contract]

mod ERC20 {

Line 4 imports `get_caller_address` function from `starknet` module. The get_caller_address() function returns the address of the contract that called the function.

use starknet::get_caller_address;

Lines 7 to 14 define a structure (similar to `struct` in Solidity) that is used later in the code.

define a structure

Lines 20 and 21 define an event Transfer that is emitted whenever the ERC20 token is transferred. Events provide an efficient way to track contract activities/state changes, thus allowing external listeners to react accordingly.

#[event]

fn Transfer(from_: felt, to: felt, value: u256) {}

Lines 36 to 54 contain a constructor function (the function that is called when contract is deployed) and a view function that reads the `name` storage variable and returns its value to the user.

constructor function

Lines 97 to 101 define an external (discussed earlier) function that is used to transfer ERC20 tokens from one account to another. This function calls the `transfer_helper()` function underneath that also emits the Transfer() event defined above.

transfer ERC20 tokens

Conclusion

If you are comfortable with Solidity, you should now have a great start to understanding how Starknet works, the differences between Cairo and Solidity, and how to read basic Cairo smart contracts.

For the next step in your Cairo development journey, refer to the Cairo docs, register for Cairo Basecamp, or go through the tutorials.

ON THIS PAGE

Contact us