Installation
We recommend working inside a python virtual environment, but you can also install the Cairo package directly. To create and enter the virtual environment, type:
python3.7 -m venv ~/cairo_venv source ~/cairo_venv/bin/activate
Make sure the venv is activated – you should see (cairo_venv)
in the command line prompt.
Make sure you can install the following pip packages: ecdsa
, fastecdsa
, sympy
(using pip3 install ecdsa fastecdsa sympy
). On Ubuntu, for example, you will have to first run:
sudo apt install -y libgmp3-dev
On Mac, you can use brew
:
brew install gmp
Install the cairo-lang
python package using:
pip3 install cairo-lang
Alternatively, you can download the package (cairo-lang-0.8.0.zip
) from https://github.com/starkware-libs/cairo-lang/releases/tag/v0.8.0, and install it using:
pip3 install cairo-lang-0.8.0.zip
Cairo was tested with python3.7. To make it work with python3.6, you will have to install contextvars
:
pip3 install contextvars
Compiling and running a Cairo program
-
Create a file, named
test.cairo
, with the following lines:
func main(): [ap] = 1000; ap++ [ap] = 2000; ap++ [ap] = [ap – 2] + [ap – 1]; ap++ ret end -
Compile: (make sure all commands are executed in the virtual environment)
cairo-compile test.cairo –output test_compiled.json -
Run:
cairo-run \ –program=test_compiled.json –print_output \ –print_info –relocate_prints -
You can open the Cairo tracer by providing the
--tracer
flag tocairo-run
. Then open it at http://localhost:8100/.
Visual Studio Code setup
Download the Cairo Visual Studio Code extension (cairo-0.8.0.vsix
) from https://github.com/starkware-libs/cairo-lang/releases/tag/v0.8.0, and install it using:
code –install-extension cairo-0.8.0.vsix
Configure Visual Studio Code settings:
“editor.formatOnSave”: true, “editor.formatOnSaveTimeout”: 1500
Note: You should start Visual Studio Code from the terminal running the virtual environment, by typing code
. For instructions for macOS, see here.
Your first Cairo Program
# Use the output builtin.
%builtins output
# Import the serialize_word() function.
from starkware.cairo.common.serialize import serialize_word
func main{output_ptr : felt*}():
# Output 100 by calling serialize_word.
serialize_word(100)
# Output 200.
serialize_word(200)
# Output 300.
serialize_word(300)
# Return.
return ()
end
The above code outputs the numbers 100, 200, 300.
How Cairo Functions Work
Let’s start by looking at the following Cairo function which computes the sum of the elements of an array:
Computes the sum of the memory elements at addresses:
arr + 0, arr + 1, …, arr + (size – 1). func array_sum(arr : felt*, size) -> (sum): if size == 0: return (sum=0) end
# size is not zero.
let (sum_of_rest) = array_sum(arr=arr + 1, size=size - 1)
return (sum=[arr] + sum_of_rest)
end
The first two lines are comment lines, and are ignored by the compiler. Comments in Cairo start with #
and continue until the end of the line.
The first non-comment line func array_sum(arr : felt*, size) -> (sum):
defines a function named array_sum
which takes two arguments: arr
and size
. arr
points to an array of size
elements. The type of arr
is felt*
which is a pointer (for more information about felt
, see below). The function declares that it returns one value called sum
. The scope of the function ends with the word end
(although end
doesn’t mean that the function returns – you must add the return statement explicitly).
The next line if size == 0:
instructs Cairo to execute the code in the if statement’s body if size
is zero, and skip to the end of the if statement’s body otherwise.
When size == 0
, there are no elements in the array, and so we can return that the sum is zero. Note that the syntax sum=
is not mandatory (you could write return (0)
), but it is recommended as it increases the readability of the code. On the other hand, the parentheses are required. As with other programming languages, the return statement ends the execution of the function immediately and returns the control to the calling function.
Now to the interesting part. The line let (sum_of_rest) = array_sum(arr=arr + 1, size=size - 1)
(which is executed only if size != 0
) makes a recursive call to array_sum()
, starting from the second element (as arr
points to the first memory cell of the array, arr + 1
points to the second cell. Also note that we need to pass size - 1
). As with named value in the return statement, you don’t have to write arr=
and size=
. The expression let (sum_of_rest) =
says that the function returns one value, which can be accessed using sum_of_rest
.
The scope of the return values of functions is restricted (for example, they may be revoked due to jumps or function calls). Later, we will see how to overcome this by using local variables. You can read more in Revoked references.
Note that you cannot call functions as part of other expressions. For example trying to write something like foo(bar())
will not compile.
Finally, we return the sum of the first element with the sum of the rest of the elements. [...]
is the dereference operator, so [arr]
is the value of the memory at address arr
, or in our case the first element of the array.
Conclusion
Cairo is the first Turing-complete language for creating provable programs for general computation. Its Builtins and field elements enable efficient generation of proofs. Cairo-based systems are already in production on Ethereum mainnet and is an interesting skill to have have in the web3 landscape.
Leave a Reply