Cairo 1.0 Data type 1

Mastering Cairo Data Types for StarkNet Contracts: Part 1

Oluwatosin Serah
6 min readMay 1, 2023

--

Welcome to another installment where I share my journey building on StarkNet. In this article, we’ll explore the part 1 of the exciting world of data types used in writing StarkNet contracts. Get ready for an informative and engaging deep dive into this topic. So without further ado, let’s jump right in!

As we know, smart contracts on StarkNet are written using the programming language Cairo, which is classified as a “statically or strongly typed” language. It shares this trait with another programming language called Rust. This means that when writing your Starknet contracts, you must declare the type of every variable, constant, and function argument you use in your code. By doing so, Cairo enforces strict type checking, which is a process of verifying that the type of data being used is compatible with what is expected. This helps to increase safety and significantly reduces the likelihood of runtime errors, which can occur when data types do not match or are not compatible with each other.

Scalar Types

A scalar type is a data type that represents a single value. There are three primary scalar types in Cairo: felts, short strings (which are felts under the hood), integers, and booleans. In Cairo, each scalar type works in its own unique way, and understanding how they function is important when writing your contract. Let’s look at them one by one.

Felts

“Felts” stands for “field elements.” In Cairo, it’s a data type used to represent unsigned integers with up to 76 decimal places. Specifically, the felt252 data type is used to represent a 252-bit integer within a range of 0 ≤ x < P, where P is a prime number. When using felt252, all computations are done modulo P, which ensures the results stay within the 252-bit range.

Booleans

The boolean data type in Cairo is used to store logical values, which can either be “true” or “false.”

fn main() {
let initial_state: bool = false;
}

This code initializes a boolean variable initial_state to false. The bool type is a built-in primitive type in Rust that can hold one of two values: true or false. In this case, the variable initial_state is assigned the value of false, indicating that it represents a state that is initially turned off or inactive.

Booleans are commonly used in conditional statements and logical operations, where the code execution depends on whether a particular condition is true or false.

Short strings

In Cairo, full strings are not supported, but you can use short strings, which are strings that have a maximum length of 31 characters. This enables them to be stored in a single field element(felt). Even though short strings may look like regular strings, they are actually encoded as ASCII characters, allowing them to be represented using felt under the hood.

In order to perform conversions between felt, strings, and hex in Cairo, a Python script is required. This script facilitates conversions between the aforementioned data types, allowing the resulting short strings to be viewed. To perform these conversions, follow the steps outlined below:

  • Install Python3 on your computer.
  • Create a file named “utils.py”.
  • Paste the provided code below into the “utils.py” file.
MAX_LEN_FELT = 31

def str_to_felt(text):
if len(text) > MAX_LEN_FELT:
raise Exception("Text length too long to convert to felt.")
return int.from_bytes(text.encode(), "big")

def felt_to_str(felt):
length = (felt.bit_length() + 7) // 8
return felt.to_bytes(length, byteorder="big").decode("utf-8")

def str_to_felt_array(text):
return [str_to_felt(text[i:i+MAX_LEN_FELT]) for i in range(0, len(text), MAX_LEN_FELT)]

def uint256_to_int(uint256):
return uint256[0] + uint256[1]*2**128

def uint256(val):
return (val & 2**128-1, (val & (2**256-2**128)) >> 128)

def hex_to_felt(val):
return int(val, 16)
  • In your terminal, run:
python3 -i utils.py

You should see something like this in your terminal:

You can then proceed to perform various conversions, such as converting a string to felt and converting felt to a string, using the Python script created earlier. To do so, follow the steps below:

str_to_felt("Hello World")
felt_to_str(87521618088882533792115812)

And yay👏, you have successfully made your conversion.

Integers

We have both unsigned integer types and integer literals.

Unsigned integers are a type of integer that only represents non-negative values (i.e., positive numbers and zero) and can’t represent negative values. The size of an integer determines the range of values it can represent. The larger the size, the wider the range of values it can represent. Here are the unsigned integer types and their respective sizes:

unsigned integer chart

The “u” in front of the numbers denotes that they are unsigned integers.

The example below is a simple function that takes in an unsigned 64-bit integer (u64) as input, which is passed in as an argument called value. Inside the function, the input value is multiplied by 2 and the result is stored in a new variable called multiply_value, which is also of type u64. Finally, the function returns the value of multiply_value, which is also of type u64.

fn main(value: u64) -> u64 {
let multiply_value: u64 = value * 2;
return multiply_value;
}

In other words, this function takes a number as input, multiplies it by 2, and returns the result. For example, if the input value is 5, the function would return 10.

Integer Literal: In Cairo, you can also assign a type to an integer literal by adding an underscore and the desired integer type after the value, which is similar to how it’s done in the Rust programming language. Integer literals can be specified with an underscore notation to make it easier to work with different integer types without the need for explicit type casting. E.g.:

// "num_u8" is an 8-bit unsigned integer initialized with a value of 1.
let num_u8 = 1_u8;

// "num_u32" is a 32-bit unsigned integer initialized with a value of 10.
let num_u32 = 10_u32;

Using the underscore notation makes it clear to the compiler what the intended type of the integer is and helps avoid type errors and the need for explicit casting. It’s a convenient feature that allows for more efficient and cleaner code. Let’s take a look at the function below:

fn main() {
// addition
let sum = 9_u64 + 20_u64;

// subtraction
let difference = 84_u64 - 12_u64;

// multiplication
let product = 5_u64 * 5_u64;

// division
let quotient = 100_u64 / 2_u64;
}

The main function defines several variables that perform basic arithmetic operations using the unsigned 64-bit integer type u64:

The + operator adds two numbers, - subtracts the second number from the first, * multiplies two numbers, and / divides the first number by the second, returning the whole number quotient (integer division).

In this specific case, sum will be assigned the value 29, difference will be assigned the value 72, product will be assigned the value 25, and quotient will be assigned the value 50.

Conclusion

It is essential to have a good grasp of Cairo data types while developing StarkNet contracts. It is important to comprehend scalar data types to guarantee that the data being utilized aligns with the anticipated type. The piece gives a detailed outline of felts, booleans, integers, and short strings, emphasizing the distinctiveness of each data type. In my upcoming article, I will delve into compound data types that can merge multiple values into one type. I will also create videos that showcase these contracts in practice for your better comprehension🤗. I look forward to sharing my next article with you.

--

--

Oluwatosin Serah

Smart Contract Developer || Blockchain Educator || Technical Writer || Researcher