Tagged

Getting Started With Cairo 1.0

Learn everything you need to know about the new and improved Cairo

Darlington Nnam
Feb 14, 2023

In September last year, StarkWare made two major announcements that would significantly improve the developer experience on the network. 

They were:

  1. Cairo 1.0: A huge upgrade to Cairo, the native programming language for Starknet, that promises a safer, simpler, and better programming experience for Starknet developers. 
  2. Sierra: An intermediary representation layer that will offer greater long-term stability for Cairo programs.

The tedious experience many developers faced when writing code with older versions of Cairo will be a thing of the past, as Cairo 1.0 makes writing code significantly easier and neater as the language becomes similar to Rust. 

With this change to Cairo, it has the additional benefit of onboarding new developers familiar with Rust into the Starknet ecosystem.

That’s why Cairo 1.0, with the introduction of Sierra, has gotten the Starknet developer community very excited, and for good reason. 

In this article, we’ll teach you how to write and compile your first Cairo 1.0 contract and explore all the new features that come with improvement. Now let’s get into it all! 

Setting up a development environment

Before we start writing our code, we need to set up a developer environment. Unfortunately, tools that you may be familiar with for Starknet, like Protostar, Nile, or the official Starknet CLI for development, currently don’t support Cairo 1.0. That means we’ll need to use an alternative solution…

We’ve solved this by creating a custom development environment for Cairo 1.0 by cloning and trimming the official repo. You will need to clone this repo to write and compile your Cairo 1.0 contract.  

Setting up your Cairo 0.1 developer environment

All our contracts will go into the src folder. We’ve already created a boilerplate contract which we will be compiling to sierra and casm in the next section of this article.

Once we’ve cloned the repo, we can now go ahead to activate our Cairo 1.0 VS code extension for syntax highlighting.

To do this:

1. Install Node.js 18 LTS

2. Navigate into the vscode-cairo folder

Run the commands below in a terminal:

sudo npm install --global @vscode/vsce

npm install

vsce package

code --install-extension cairo1*.vsix

3. Build the language server:

cargo build --bin cairo-language-server --release

4. Open Vscode in your current directory

Run these commands:

npm install

npm run compile

5. Finally, reload VScode and press F5 

PS: You should add this extra snippet to your settings.json file in VScode to ensure things run correctly.

"cairo1.languageServerPath": "/Some/Path/target/release/cairo-language-server",
Succesfully setting up your Cairo 0.1 development environment

If your installation was successful, head to src/hello.cairo, and you should have syntax highlighting activated, as seen in the image above.

Compiling your first Cairo 1.0 contract

Having set up our development environment, we can now move to compile our Cairo 1.0 contract.

We will be working with a straightforward contract located in src/hello.cairo 

#[contract]

mod HelloStarknet {

   struct Storage {

       balance: felt,

   }

   #[external]

   fn increase_balance(amount: felt) {

       balance::write(balance::read() + amount);

   }

   #[view]

   fn get_balance() -> felt {

       balance::read()

   }

}

This contract implements simple functions to increase and get the balance variable. The #[contract] signifies that we are writing a Starknet contract rather than a Cairo program.

Unlike with 0.x contracts, where we compile straight to Cairo assembly (casm), with Cairo 1.0, compiling our contracts will be made in two stages:

  1. Cairo > Sierra
  2. Sierra > Casm

Sierra stands for Safe Intermediate Representation and aims to constitute a representation layer between Cairo programs and its bytecodes. Sierra opens up the possibility to prove every Cairo run, thereby allowing for robust Denial of Service attack (DoS) protection.

To compile to Sierra, run the command below:

cargo run --bin starknet-compile -- src/hello.cairo src/hello.sierra --replace-ids

If the compilation was successful, you should see the Sierra output in your src/hello.sierra.

To further compile from Sierra to Casm, run the command below:

cargo run --bin starknet-sierra-compile -- src/hello.sierra src/hello.casm

If the compilation was successful, you should see the casm output in your src/hello.casm.

Starknet Alpha v0.11.0 hasn’t been released, so we can’t deploy our compiled contracts to testnet or mainnet. However, this network upgrade is expected to happen at the end of February 2023.

Key Cairo 1.0 features you need to know about 

It will take more than a single article to go over all the new features that come with Cairo 1.0, but we’ll explore some key ones in this section:

A new syntax

As stated earlier, Cairo 1.0 comes with a cleaner and better syntax inspired by Rust. This includes adopting Rust-like features such as traits and their implementations, pattern matching, panic, etc.

Language directives and imports

With Cairo 1.0, to get started with writing a Starknet contract, you have to specify the macro:

#[contract]

While every Cairo program must implement a main function, importing a function has also become easier. 

You can do that like this:

use starknet::get_caller_address;

Alternatively, you can also import the entire 'package':

use starknet;

and use it like this:

starknet::get_caller_address() 

Data types

The signature of Starknet’s ecosystem is the felt data type, and of course, they are not disappearing anytime soon! 

In addition to the felt data type, there’s support for other Integer types, like u256, u128, u64, u32, and u8.

While these Integer types are implemented using felts behind the scenes, they are considered safer to use and can now support arithmetic operations without needing custom libraries. e.g., you can now perform operations such as:

let sum = 1_u128 + 2_u128;

let product = 5_u256 * 10_u256;

The use of these operators is overflow protected and causes panic if an overflow is detected.

There is also support for a contractAddress data type similar to Solidity’s address, which has only just been recently implemented in Cairo 0.1.

Type literals

Numerical literals such as 1,2,3 are felts by default. However, you can specify a different data type for them by appending the data type, like this:

let num_u256 = 1_u256

The Let keyword to rule them all

With Cairo 1.0, we finally eliminate multiple variable declaration patterns (tempvar, local, etc.) to embrace just using the let keyword.

This is very helpful as we finally say goodbye to Revoked references compiler errors.

You can now also create mutable variables using the mut keyword.

Structs and storage variables

Rather than using the @storage_var decorator, Cairo 1.0 divides a contract into three main sections: a storage struct, the contract traits, and its implementations.

All storage variables are packed into a single struct named Storage, though this is subject to change. 

At the time of writing, here’s what it looks like: 

struct Storage {

   name: felt,

   symbol: felt,

   decimals: u8,

   total_supply: u256,

   balances: LegacyMap::<felt, u256>,

   allowances: LegacyMap::<(felt, felt), u256>,

}

And can be read or written in this manner:

let name = name::read()

let name = name::write(_name)

Mappings can be created using the LegacyMap keyword, where the data types of the mapped variables are inserted between < and > . You can also map tuples.

Events

Events allow a contract to emit information during the course of its execution that can be used outside of Starknet. 

An event can be created like this:

#[event]

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

And subsequently emitted this way:

Transfer(sender, recipient, amount);

Functions

Functions in Cairo 1.0 look similar to the 0.x versions, with the omission of the implicit arguments and the change of the func keyword to fn following Rust’s convention. Here’s an example of a basic function signature in Cairo 1.0:

fn balance_of(account: felt) -> u256 {

   ...

}

You’ll also notice that return variables no longer contain the variable names but just their data types.

Return statements

Just like Rust, functions in Cairo 1.0 can either return values implicitly by omitting the ending semi-colon like this:

#[view]

fn get_name() -> felt {

   name::read()

}

In this case, the final expression of the function is automatically returned, or explicitly by using the return keyword, like this:

#[view]

fn get_name() -> felt {

   return name::read();

}

Decorators

Decorators still exist in Cairo 1.0 but with a new syntax similar to Rust’s macro. 

You can declare a decorator like this:

#[external]
#[view]

Enums and Pattern matching

Enums are a special data type consisting of a fixed set of constants that you define.

You can create an Enum in Cairo 1.0, like this:

enum Animals { Goat: felt, Dog: felt, Cat: felt }

Cairo 1.0 also enables us to create pattern matchings similar to Rust using the match keyword. When combined with Enums, pattern matchings can allow us to adapt a function’s behavior depending on the data variant it encounters.

enum Colors { Red: felt, Green: felt, Blue: felt }

func get_favorite_color() -> Colors {

   Colors::Green(0)

}

func main() -> felt {

   let my_color = get_favorite_color();

   let result = match my_color {

       Colors::Red(_) => {

           1

       },

       Colors::Green(_) => {

           2

       },

       Colors::Blue(_) => {

           3

       },

   };

   result // returns 2

}

Arrays

Cairo 1.0 makes array manipulations much easier, as the core library exports an array type in addition to some associated functions such as append, array_at, and array_len. Some basic array operations include adding new elements to an existing array, getting an element index, getting the array length, etc. These can be accessed from ArrayTrait

fn fib(n: usize) -> (Array::<felt>, felt, usize) {

   let mut arr = ArrayTrait::new();

   arr.append(1);

   arr.append(1);

   let mut arr = fib_inner(:n, :arr);

   let len = arr.len();

   let last = arr.at(len - 1_usize);

   return (arr, last, len);

}

Error messages and access controls

You can create custom errors in Cairo, which are outputted to the user upon failed execution. This can be very useful for implementing checks and proper access control mechanisms.

With Cairo 1.0, you can easily do this using the assert statement in a similar pattern to the require statement in Solidity:

assert(sender != 0, 'ERC20: transfer from 0');

Where the error message must be less than 31 characters.

Traits and their Implementations

Cairo 1.0 introduces traits and implementations. Think of traits as a special type of contract interface, as they define functionalities a particular type has and can share with others. They are defined using the trait keyword.

trait IContract{

   fn set_something(num: felt, pair: felt);

   fn get_something(num: felt) -> felt;

}

On the other hand, an implementation implements specific trait behaviors. All functions from the trait must be defined in the implementation. 

They are defined using the impl keyword like this:

impl Contract of IContract {

   fn set_something(num: felt, pair: felt) {

       number_pair::write(num, pair)

   }

   fn get_something(num: felt) -> felt {

       number_pair::read(num)

   }

}

Generics

Cairo 1.0, similar to Rust, also supports generics, which enables you to write codes that are flexible and can work with multiple types rather than being tied to a specific type.

A type parameter is specified as generic by the use of angle brackets <> like this:

fn foo<T>(arg: T) -> T {
    …
 }

Replace_class sysall

With Cairo 1.0 and Starknet v0.11, a new syscall will be added, enabling you to swap the underlying contract implementation (class hash) without changing the contract’s entry point. Think of a default proxy! 

And the best part is you can do this using just a single line of code:

#[contract]

mod upgradeable {
	use starknet::replace_class;
	
.	….

	#[external]
	fn upgradeable(cls_hash: felt) {
		replace_class(cls_hash);
	}
}

Conclusion

Congratulations, you’ve just written and compiled your first Cairo 1.0 contract and learned about key Cairo 1.0 features! 

Again, Cairo 1.0 is still a work in progress and evolving quickly. New features are getting added daily, so always check out the official repo and the Discord discussion channel to stay up to date!

Here are some additional resources that we think will help you along your Cairo 0.10 journey:

  1. A First Look at Cairo 1.0: A Safer, Stronger & Simpler Provable Programming Language
  2. Cairo 1.0 — changes, features, release date
  3. https://github.com/starkware-libs/cairo/blob/main/docs/reference/src/SUMMARY.adoc
  4. https://github.com/argentlabs/starknet-build/tree/main/cairo1.0/examples
  5. Cairo 1.0 by StarkWare

If you have any questions regarding this, reach out to me @0xdarlington, I’d love to help you build on Starknet with Argent X.

For more developer resources, follow us across our socials:

Twitter — @argentHq

Engineering Twitter — @argentDeveloper

LinkedIn — @argentHq

Youtube — @argentHQ

Interested in the topic? Join us!

We’re always looking for outstanding engineers to help us pioneer better UX and security in crypto. We work remotely across Europe.

Argent Careers

Related Blogs

Writing and deploying your first NFT on Starknet

Learn how to build your first Cairo smart contract!

Writing and deploying your first ERC20 token on Starknet

Learn how to build your first Cairo smart contract!

Understanding The Universal Deployer Contract And Deploying your Contracts through Argent X

Deploying contracts on Starknet using the Universal deployer contract

Own It

We use 🍪 cookies to personalise your experience on Argent. Privacy Policy

Accept

HQ London, made with ❤️ across Europe