Journey Through Cairo VIII — Writing And Deploying your first Starknet Contract With Protostar

Darlington Nnam
8 min readSep 24, 2022

--

protostar

Welcome to the eight article, in our series, “Journey through Cairo”. We’ve come along way since we started, a big kudos to you! In our last article which can be found here, we took a look at Builtins, Hints and Revoked References in Cairo.

Today, we’d be doing something very amazing…. We’d be writing and deploying our very first Starknet Contract! As always, if you are just joining mid-flight, endeavour to checkout the previous articles, to better understand and flow with this series.

For local development, we’d be using Protostar, so if you still haven’t setup Protostar locally yet, please check out the first article in this series here, before proceeding.

PS: We finally get to write Cairo’s v0.10! yeap no Starklings today, so check your Cairo/Protostar version, and bump up to Protostar 0.4.2, if yours is still behind.

Aims and Objectives

By the end of this tutorial, you should be able to:

  1. Write a simple Starknet contract with Cairo.
  2. Understand Events, Constructors, External and View functions.
  3. Deploy your contract using Protostar.
  4. Interact with your contracts through Voyager.

Project Description

For this tutorial, we’d be building a simple Starknet Naming Service, that maps names to people’s wallets addresses.

Its a similar contract to the one, i used for the tutorial on how to use Starknet.js for building front end applications, and you can play around with the demo here.

Getting Started

Alright, let’s get started!

To shorten the length of this article, and focus on the key points, i’d not be going over how to setup Protostar. I already did that, in the first article here, so please check that out before proceeding.

Intializing a new Protostar project

Having installed Protostar, we’d initialize a new project by running the command:

protostar init

Once we do that, we’d be asked for the project name and lib name, which we’d need to input to successfully create a new project.

Opening the new project in our code editor, we should have these folders already created for us:

You’d notice protostar created a main.cairo file, which has some already pre-written Cairo codes. You could check that out after we are done and try to modify it to better solidify your knowledge.

The src folder, is where our contract codes go into, the lib folder is meant to contain all external imports such as openzeppelin contracts etc, the test folder contains our test scripts and the protostar.toml file is our project configuration file.

Reference article: Setting up Protostar

Creating a new file

Next up, we are going to create a new file called Starknet.cairo in our src folder, where we’d be writing our contract codes.

Imports

First we’d begin by specifying the %lang starknet directive to specify that our file contains codes for a Starknet contract, similarly to the way we do pragma solidity for solidity contracts.

Then, we are going to be importing all necessary library functions we might need to use in our contract.

%lang starknetfrom starkware.cairo.common.cairo_builtins import HashBuiltinfrom starkware.starknet.common.syscalls import get_caller_address

The HashBuiltin was imported because we’d be needing it for our pedersen builtin, and the get_caller_address is needed for getting our msg.sender(address of the person calling the contract’s function).

Reference article: Builtins

Storage Variables

Our contract is going to have just a single storage variable, which is going to be a mapping of addresses to names:

@storage_varfunc names(address) -> (name: felt) {}

Reference article: Storage variables

Events

Events allows a contract to log a change of state to the blockchain in a specific format to allow the VM easily retrieve and filter them.

To create an event in Cairo, we need the @event decorator. Our event is going to be emit the caller address and the inputted name, each time we store a new name.

@eventfunc stored_name(address: felt, name: felt){}

Reference article: Events

Constructors

Constructors are a very important piece in writing contracts. You can use them to intialize some certain state variables on contract deployment.

To create a constructor in Cairo, you’d use the @constructor decorator.

Although we don’t necessarily need one for our current project, to demonstrate its use, we’d be creating a constructor, where we set a default name for the caller!

Reference article: Constructors

@constructorfunc constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_name: felt) {   let (caller) = get_caller_address();   names.write(caller, _name);   return ();}

External functions

If you came from Solidity, you’d be used to 4 function types (public, private, external and internal), but in Cairo we just have two types of functions: External functions and View functions.

External functions are functions that changes the state of the blockchain, and are created using the @external decorator.

For our contract, we are going to create an external function store_name, that takes in a name input, and updates our names variable.

@externalfunc store_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_name: felt){   let (caller) = get_caller_address();   names.write(caller, _name);   stored_name.emit(caller, _name);   return ();}

As you can see from the codes, we first get the caller using the get_caller_address library function we imported earlier, then we update our names storage variable, and lastly we emit a stored_name event.

View Functions

View functions are getter functions. They do not alter the state of the blockchain, and are created using the @view decorator.

For our contract, we are going to create a view function get_name, that takes in an address input, and returns the corresponding name.

@viewfunc get_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_address: felt) -> (name: felt){   let (name) = names.read(_address);   return (name,);}

Having created our store_name and get_name functions, we now have our contract code completed. Your full contract code should look like this:

%lang starknetfrom starkware.cairo.common.cairo_builtins import HashBuiltinfrom starkware.starknet.common.syscalls import get_caller_address
@storage_varfunc names(address) -> (name: felt) {}
@eventfunc stored_name(address: felt, name: felt){}@constructorfunc constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_name: felt) { let (caller) = get_caller_address(); names.write(caller, _name); return ();}@externalfunc store_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_name: felt){ let (caller) = get_caller_address(); names.write(caller, _name); stored_name.emit(caller, _name); return ();}@viewfunc get_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_address: felt) -> (name: felt){ let (name) = names.read(_address); return (name,);}

Woah! Thats a lot of codes we have right there. Now let’s try to deploy our contract on Starknet.

Deploying Contract

With Protostar, you could easily deploy your contract without breaking a sweat!

First thing we’d need to do is update the protostar.contracts section of our configuration file, protostar.toml to contain the path to our contract code.

Next we’d need to build our contract. Building contracts in Protostar is similar to compiling contracts with Hardhat. To do this, we’d run the command:

protostar build

If there are no errors, we should get this:

Lastly we’d need a python script for strings to felt conversions, as our constructor requires a name of type felt.

To do this, create a utils.py file in your root folder, and paste the following code in it:

MAX_LEN_FELT = 31def 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) // 8return 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**128def uint256(val):return (val & 2**128-1, (val & (2**256-2**128)) >> 128)def hex_to_felt(val):return int(val, 16)

Next up, open up your terminal and run

python3 -i utils.py

And you should have your terminal looking like this:

Next, we are going to try converting our short string to felt. To do this, run

str_to_felt("Darlington")

You could see the resulting short string in the terminal. Now we are fully ready to deploy our contract!

Protostar Deploy Command

We’d run the Protostar deploy command, passing in the test network and the name input in felt format:

protostar deploy ./build/starknet.json --network testnet -i 322918500091226412576622
  • the deploy command is an in-built command from protostar.
  • the ./build/main.json specifies the path to our compiled file.
  • the --network variable is used to specify the network we are deploying to.
  • the -i variable is used to specify the inputs needed by our constructor.

For more info on protostar deploy commands, check out the docs here.

Once deployed, we’d get our contract address and transaction hash outputted on the screen, which we can copy and interact with on Voyager.

Interacting with our contract

Having deployed our contract, we can check it up and interact with it through Voyager.

The Read Contract section is where we can interact with our view functions, and the Write Contract section is where we interact with our external functions.

Storing a name through Voyager

Getting the stored name through Voyager

Conclusion

Congratulations! you just deployed your first contract to Starknet. In this tutorial, we interacted with our contract, through Voyager, but you could also build a dedicated UI to do this. I go through the process on how you can do this here.

Code solutions to everything we just covered in this article can be found here.

In our next article, we’d be writing tests for our contract. Follow me on Medium and Twitter, so you don’t miss out when it drops!

As always, If you got value from this article, do well to share with others.

You could also connect with me on the following socials, especially on Twitter, where i share my little findings on Cairo!

Twitter: https://twitter.com/0xdarlington

LinkedIn: https://www.linkedin.com/in/nnamdarlington

Github: https://github.com/Darlington02

--

--