# Cairo Starknet contracts -- Simple style-guide and contract layout This is a simple style-guide we are using at Cygnus to re-write our core contracts in Cairo. But first, what is Cairo? Cairo is a programming language for writing provable programs, where one party can prove to another that a certain computation was executed correctly. Cairo and similar proof systems can be used to provide scalability to blockchains. StarkNet uses the Cairo programming language both for its infrastructure and for writing StarkNet contracts. Since our core contracts are based on Solidity and rely on the language's unique features, we have had to adopt a different mindset to transcribe the contracts. We encountered a few differences between the two, thus the purpose of this document. Our Solidity contracts were written with a very specific layout: >pragma solidity 0.8.4: >1. Libraries >2. Storage >3. Constructor >4. Modifiers >5. Constant functions >6. Non-Constant functions <br> | ![](https://i.imgur.com/NP1vdLF.png) |:--:| *Our Solidity core contracts layout* <br> Every single contract on our core (can be found here: https://github.com/CygnusDAO/cygnusdao-core) follows this practice. Starts with the `pragma solidity >= 0.8.4` and follows with the contract layout mentioned above. The contract instance from our core inherits all the structs/errors/events/functions from the interface, and they commit to implement all the functions found on there. In Cairo, **there are interfaces but there is no inheritance**. This means that we can't just rely on the interface itself to provide us with the basic structure of our contract. That's the first big difference. The second difference is that Cairo only supports **1 constructor per contract**. Our contracts relied on inheritance, and follow this path: <center> <img src="https://i.imgur.com/xHJuaPp.png"> </center> With collateral contracts inherting from CygLP, and borrow contracts inheriting from CygDAI and all the contracts have constructors. This can easily be resolved using an initializer instead of a constructor, so this is simple enough. The third and most important difference is data types and execution types: - There is only 1 data type: **felt**. A felt stands for an unsigned integer with up to 76 decimals. It is used to store addresses, strings, integers, etc. We declare it with: ``` magic_number : felt ``` ``` my_address : felt ``` - There is only 1 execution type: **func**. All storage variables (except structs) such as mappings, events, constructors and functions are declared with the same func keyword: ``` func this_is_single_storage_slot() -> (value : felt): end ``` ``` func this_is_a_mapping_slot(key : felt) -> (value : felt): end ``` ``` func this_is_an_event(event_param1 : felt, event_param2 : felt): end ``` Since declaring everything wtih a `func` keyword can get confusing, Cairo provides **decorators** which are used on top of the function declarations to distinguish each. These all start with `@`: - **@event** - Represents an event - **@storage_var** - Represents a contract state variable (ie mapping, address, etc.) - **@constructor** - A constructor which runs once at deployment - **@view** - Used to *read* from storage_vars - **@external** - Used to *write* to storage_vars - **@l1_handler** - Used to to process a message sent from an L1 contract <br> # Layout Style We tried to follow our Solidity pattern of: `Storage -> Constructor -> Modifiers -> Constant Functions -> Non-Constant Functions` And this fits perfect with Cairo. We bring the structs and events into the contract itself (isntead of the interfaces like with Solidity) and with the decorators we separate the contracts into the following: >%lang starknet >1. Structs >2. Events >3. Storage >4. Constructor >5. Storage Getters >6. Constant functions >7. Non-Constant functions <br> | ![](https://i.imgur.com/BVRMyQb.png) |:--:| *Our Cairo core contracts layout* <br> The layout is similar to our Solidity contracts, but we define the structs and events in the contract themselves, and add `Storage Getters` between constructors and constant functions, and that's pretty much it. Instead of pragma solidity, we first start the contract with `%lang starknet` and follow the pattern: `Structs -> Events -> Storage -> Constructor -> Storage Getters -> Constant functions -> Non-Constant Functions` The storage getters are necessary if you want to make them public. In solidity, the compiler creates getters for all state variables declared as public, in Cairo all @storage_vars are private. Thus if we want to make them public we must make a getter function ourselves. <br> # Naming Style Solidity follows the camelCase approach for every function and state variable, whether it is a uint256 or a string or an address. We then use PascalCase for structs, contracts and events which is the standard in Solidity but never camel case. Cairo follows the Python or Ruby approach of camel_case in and we followed this approach. The style we used is very similar to OnlyDust's but with a few changes (https://github.com/onlydustxyz/development-guidelines/blob/main/starknet/README.md). The main change is that we use Mixed_Case_Underscore for storage vars. Since we can't declare an event as `event {EventName}` or a mapping as `mapping`, we have to differentiate between all the funcs. Storage vars are the only ones which follow this approach along with library names, but the rest follow OnlyDust's conventions. We are still not sure if we will remain with this approach. Also, We customized the error message to see exactly where the error is coming from for debugging purposes, but that's pretty much it. |![](https://i.imgur.com/7dPn2bE.png) |:--:| *Our Cairo naming style* # Examples Single storage example: ``` # 3. Storage @storage_var func Magic_Number() -> (number : felt): end ---------------------------- # 5. Storage Getters @view func magic_number() -> (number : felt): return Magic_Number.read() end ---------------------------- # 6. Constant functions @view func multiply_magic_numbers(multiplier : felt) -> (number : felt): let (number : felt) = Magic_Number_Read() return (number * multiplier) end ---------------------------- # 7. Non-Constant Functions @external func new_magic_number(number : felt) -> (number : felt): return magic_number.write(10) end ``` Mapping storage example with a struct: ``` # 1. Structs struct MyStruct: member my_name : felt member my_age : felt end ---------------------------- # 2. Event # we can pass structs to events func NewStruct(new_struct : MyStruct): end ---------------------------- # 3. Storage @storage_var func My_Struct(struct_id : felt) -> (get_struct : MyStruct): end ---------------------------- # 5. Storage getters @view func my_struct(struct_id : felt) -> (get_struct : MyStruct): return My_Struct.read(struct_id) end ---------------------------- # 7. Non-Constant Functions @external func new_struct(id : felt, name : felt, age : felt): let my_struct = MyStruct(name, age) My_Struct.write(id, my_struct) NewStruct.emit(my_struct) return () end ```