Reading Sierra: Starknet's secret sauce for Cairo 1.0
The best Solidity developers can program in Yul (an intermediate language used in Solidity code compilation) and understand the EVM. They do this because it helps them understand security vulnerabilities and optimize their code to make it efficient. They also do this because it's fun. At an extreme, OpenSea's Seaport protocol was written with significant amounts of assembly code.
Starknet also has an intermediate language called Sierra, built as a stable layer to allow for rapid iteration and innovation at the Cairo 1.0 language level. While not absolutely essential to write efficient programs (Starknet contracts written in high-level Cairo will be orders of magnitude more efficient than hand-optimized Solidity contracts), reading Sierra code can help us get a deeper understanding of the underlying type system, security risks and improving efficiency where it matters (e.g., reducing the storage footprint or execution steps in highly recursive functions).
For Cairo 0.x programmers, Sierra is the bridge between our past and the future, a familiar blueprint guiding our craft. Let's take a look.
If you'd like to follow along, you can generate readable Sierra output for your contracts by using the make sierra
command in the Minimal Cairo 1.0 Template. This effectively runs cairo-compile . -r
on your code. While Sierra is reported as stabilized, the present examples are accurate as of March 2023.
Starting from the bottom
The natural place to start is with a function that does nothing:
The Sierra output is organized into 4 separate sections always presented in the same order. First come the type declarations. Next, declarations of built-in library functions that will be used. Then the sequence of statements and finally the declared Cairo functions.
A couple of observations:
- The Unit type
()
is a special case of an empty struct and is used as a default return type for procedures (functions without a declared return type) - New temporary variables are created and indexed using square brackets (
[0]
and[1]
) - Statements have the following form
<libfunc>(<inputs>) -> (<outputs>);
, i.e., we don't see the more traditional[0] = struct_construct<Unit>();
- Code blocks are separate from Cairo function declarations. A function is tied to a specific block of code by starting at a dedicated statement index location (note the
do_nothing@0
which indicates that the function begins at the first statement).
Types & Code generation
Sierra output can be used to understand how built-in types are used in compilation. Consider this simple function to add two felt
values:
The output above is simple enough, showing us how function arguments become unnamed variables in statements.
Let's try a more complex type like u128
:
In this case, the output is much larger given that u128
is not a primitive type. This is reflected well in the function declarations:
- Our add function now has a phantom argument with a type of
RangeCheck
. This is a note to include the ZK circuit to help with felt comparisons, which is needed to perform addition. A welcome change from Cairo 0.x where built-ins had to be explicitly provided - We see that some additional functions were generated in this case. These functions were included based on traits defined in the Cairo core library (
corelib
). In short, any Cairo code needed for compilation will be present in the final Sierra output.
Control Flow
To study how control flow is represented, we can use a recursive function to compute the factorial:
In line 22, a function_call
libfunc is used to make the recursive call. We also see the jump construct in line 13. The jump label is the index of the statement so jump() { 28() };
would take execution to row 29.
Learning more
I hope that this has been a useful teaser on how to read Sierra. Cairo smart contract developers and auditors alike shouldn't hesitate to jump down into Sierra to understand how their contracts work. There is a lot more going on behind the scenes, but Sierra strikes a good balance between readability and insight.