diff options
Diffstat (limited to 'cairo.html.markdown')
-rw-r--r-- | cairo.html.markdown | 1277 |
1 files changed, 621 insertions, 656 deletions
diff --git a/cairo.html.markdown b/cairo.html.markdown index f106860a..18ac81af 100644 --- a/cairo.html.markdown +++ b/cairo.html.markdown @@ -8,10 +8,10 @@ contributors: # Cairo Cairo is a Turing-complete language that allows you write provable programs -(where one party can prove to another that a certain computation - was executed correctly) on StarkNet. +(where one party can prove to another that a certain computation was executed +correctly) on StarkNet. -# StarkNet +## StarkNet StarkNet is a decentralized ZK-rollup that operates as an Ethereum layer 2 chain. @@ -24,7 +24,7 @@ syntax and how you could create and deploy a Cairo smart contract on StarkNet. want to check the [official docs](https://www.cairo-lang.org/docs) to confirm this document is still up-to-date. Pull requests are welcome!** -# Setting Up A Development Environment +## Setting Up A Development Environment Before we get started writing codes, we will need to setup a Cairo development environment, for writing, compiling and deploying our contracts to StarkNet. @@ -44,23 +44,23 @@ Protostar version displayed on the screen. ## Initializing a new project Protostar similar to Truffle for solidity development can be installed once and -used for multiple projects. -To initialize a new Protostar project, run the following command: +used for multiple projects. To initialize a new Protostar project, run the +following command: ``` protostar init ``` -2. It would then request the project's name and the library's directory name, - you'd need to fill in this, and a new project will be initialized - successfully. +It would then request the project's name and the library's directory name, +you'd need to fill in this, and a new project will be initialized successfully. + +## Compiling, Declaring, Deploying and Interacting with StarkNet Contracts -# Compiling, Declaring, Deploying And Interacting With StarkNet Contracts Within the `src` folder you'll find a boilerplate contract that comes with initializing a new Protostar project, `main.cairo`. We are going to be compiling, declaring and deploying this contract. -## Compiling Contracts +### Compiling Contracts To compile a Cairo contract using Protostar, ensure a path to the contract is specified in the `[contracts]` section of the `protostar.toml` file. Once @@ -74,12 +74,11 @@ And you should get an output similar to what you see below, with a `main.json` and `main_abi.json` files created in the `build` folder. <img src="./cairo_assets/build.png" alt="building your contract"> -## Declaring Contracts +### Declaring Contracts With the recent StarkNet update to 0.10.3, the DEPLOY transaction was deprecated and no longer works. To deploy a transaction, you must first declare -a Contract to obtain the class hash, then deploy the declared contract using -the +a Contract to obtain the class hash, then deploy the declared contract using the [Universal Deployer Contract](https://community.starknet.io/t/universal-deployer-contract-proposal/1864). Before declaring or deploying your contract using Protostar, you should set the @@ -90,12 +89,14 @@ terminal. To set your private key in the terminal, run the command: export PROTOSTAR_ACCOUNT_PRIVATE_KEY=[YOUR PRIVATE KEY HERE] ``` -Then to declare our contract using Protostar run the following command: +Then to declare our contract using Protostar run the following command (for +visual clarity, the backslash sign symbolizes the continuing line): ``` -protostar declare ./build/main.json --network testnet --account -0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 --max-fee -auto +protostar declare ./build/main.json \ + --network testnet \ + --account 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 \ + --max-fee auto ``` where `network` specifies the network we are deploying to, `account` specifies @@ -104,48 +105,47 @@ be paid for the transaction. You should get the class hash outputted as seen below: <img src="./cairo_assets/declare.png" alt="declaring your contract"> -## Deploying Contracts +### Deploying Contracts After obtaining our class hash from declaring, we can now deploy using the -below command: +command below: ``` -protostar deploy -0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc --network -testnet --account -0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 --max-fee -auto +protostar \ + deploy 0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc \ + --network testnet \ + --account 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 \ + --max-fee auto ``` where `0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc` is the class hash of our contract. <img src="./cairo_assets/deploy.png" alt="deploying your contract"> -## Interacting With Contracts +### Interacting with Contracts -To interact with your deployed contract, we will be using Argent X -(alternative - Braavos), and Starkscan (alternative - Voyager). To install and -setup Argent X, check out this +To interact with your deployed contract, we will be using `Argent X` +(alternative: `Braavos`), and `Starkscan` (alternative: `Voyager`). To install +and setup `Argent X`, see this [guide](https://www.argent.xyz/learn/how-to-create-an-argent-x-wallet/). Copy your contract address, displayed on screen from the previous step, and head over to [Starkscan](https://testnet.starkscan.co/) to search for the -contract. Once found, you can make write calls to the contract by following the -steps below: +contract. Once found, you can make write calls to the contract in the following +sequence: -1. Click on the "connect wallet" button ++ click on the "connect wallet" button, <img src="./cairo_assets/connect.png" alt="connect wallet"> -2. Select Argent X and approve the connection ++ select `Argent X` and approve the connection <img src="./cairo_assets/connect2.png" alt="connect to argentX"> -3. You can now make read and write calls easily. ++ you can now make read and write calls easily. -# Let's learn Cairo +## Let's learn Cairo -First let's look at a default contract that comes with Protostar +First let's look at a default contract that comes with Protostar which allows +you to set balance on deployment, increase, and get the balance. ```cairo -// Allows you to set balance on deployment, increase, and get the balance. - // Language directive - instructs compiler its a StarkNet contract %lang starknet @@ -155,22 +155,21 @@ from starkware.cairo.common.cairo_builtins import HashBuiltin // @dev Storage variable that stores the balance of a user. // @storage_var is a decorator that instructs the compiler the function -// below it is a storage variable. +// below it is a storage variable. @storage_var -func balance() -> (res: felt) { -} +func balance() -> (res: felt){} // @dev Constructor writes the balance variable to 0 on deployment // Constructors sets storage variables on deployment. Can accept arguments too. @constructor func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}() {balance.write(0); return (); -} + range_check_ptr}() {balance.write(0); return(); + } // @dev increase_balance updates the balance variable // @param amount the amount you want to add to balance // @external is a decorator that specifies the func below it is an external -// function. +// function. @external func increase_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(amount: felt){ @@ -178,10 +177,10 @@ func increase_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, assert_nn(amount); } - let (res) = balance.read(); - balance.write(res + amount); - return (); -} + let (res) = balance.read(); + balance.write(res + amount); + return (); + } // @dev returns the balance variable // @view is a decorator that specifies the func below it is a view function. @@ -190,708 +189,674 @@ func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (res: felt) { let (res) = balance.read(); return (res,); -} - -// before proceeding, try to build, deploy and interact with this contract! -// NB: Should be at main.cairo if you are using Protostar. + } ``` -Now unto the main lessons +Before proceeding to the main lessons, try to build, deploy and interact with +this contract. +NB: You should be at `main.cairo` if you are using Protostar. -### 1. THE FELT DATA TYPE - -```cairo - // Unlike solidity, where you have access to various data types, Cairo - // comes with just a single data type..felts - // Felts stands for Field elements, and are a 252 bit integer in the range - // 0<=x<=P where P is a prime number. - // You can create a Uint256 in Cairo by utlizing a struct of two 128 bits - // felts. - - struct Uint256 { - low: felt, // The low 128 bits of the value. - high: felt, // The high 128 bits of the value. - } - - // To avoid running into issues with divisions, it's safer to work with the - // unsigned_div_rem method from Cairo-lang's library. -``` +### 1. The Felt data type -### 2. LANG DIRECTIVE AND IMPORTS +Unlike solidity, where you have access to various data types, Cairo comes with +just a single data type `..felts`. Felts stands for Field elements, and are a +252 bit integer in the range `0<=x<=P` where `P` is a prime number. You can +create a `Uint256` in Cairo by utlizing a struct of two 128 bits felts. ```cairo - // To get started with writing a StarkNet contract, you must specify the - // directive: - - %lang starknet +struct Uint256{ + low: felt, // The low 128 bits of the value. + high: felt, // The high 128 bits of the value. + } +``` - // This directive informs the compiler you are writing a contract and not a - // program. - // The difference between both is contracts have access to StarkNet's - // storage, programs don't and as such are stateless. +To avoid running into issues with divisions, it's safer to work with the +`unsigned_div_rem` method from Cairo-lang's library. - // There are important functions you might need to import from the official - // Cairo-lang library or Openzeppelin's. e.g. +### 2. Lang Directive and Imports - from starkware.cairo.common.cairo_builtins import HashBuiltin - from cairo_contracts.src.openzeppelin.token.erc20.library import ERC20 - from starkware.cairo.common.uint256 import Uint256 - from starkware.cairo.common.bool import TRUE -``` - -### 3. DATA STRUCTURES +To get started with writing a StarkNet contract, you must specify the directive: ```cairo - // A. STORAGE VARIABLES - // Cairo's storage is a map with 2^251 slots, where each slot is a felt - // which is initialized to 0. - // You create one using the @storage_var decorator - - @storage_var - func names() -> (name: felt){ - } - - // B. STORAGE MAPPINGS - // Unlike soldity where mappings have a separate keyword, in Cairo you - // create mappings using storage variables. - - @storage_var - func names(address: felt) -> (name: felt){ - } - - // C. STRUCTS - // Structs are a means to create custom data types in Cairo. - // A Struct has a size, which is the sum of the sizes of its members. The - // size can be retrieved using MyStruct.SIZE. - // You create a struct in Cairo using the `struct` keyword. - - struct Person { - name: felt, - age: felt, - address: felt, - } - - // D. CONSTANTS - // Constants are fixed and as such can't be altered after being set. - // They evaluate to an integer (field element) at compile time. - // To create a constant in Cairo, you use the `const` keyword. - // Its proper practice to capitalize constant names. - - const USER = -0x01C6cfC1DB2ae90dACEA243F0a8C2F4e32560F7cDD398e4dA2Cc56B733774E9b - - // E. ARRAYS - // Arrays can be defined as a pointer(felt*) to the first element of the - //array. - // As an array is populated, its elements take up contigous memory cells. - // The `alloc` keyword can be used to dynamically allocate a new memory - // segment, which can be used to store an array - - let (myArray: felt*) = alloc (); - assert myArray[0] = 1; - assert myArray[1] = 2; - assert myArray[3] = 3; - - // You can also use the `new` operator to create fixed-size arrays using - //tuples - // The new operator is useful as it enables you allocate memory and - // initialize the object in one instruction - - func foo() { - tempvar arr: felt* = new (1, 1, 2, 3, 5); - assert arr[4] = 5; - return (); - } - - // F. TUPLES - // A tuple is a finite, ordered, unchangeable list of elements - // It is represented as a comma-separated list of elements enclosed by - // parentheses - // Their elements may be of any combination of valid types. - - local tuple0: (felt, felt, felt) = (7, 9, 13); - - // G. EVENTS - // Events allows a contract emit information during the course of its - // execution, that can be used outside of StarkNet. - // To create an event: - - @event - func name_stored(address, name) { - } - - // To emit an event: - - name_stored.emit(address, name); +%lang starknet ``` -### 4. CONSTRUCTORS, EXTERNAL AND VIEW FUNCTIONS +This directive informs the compiler you are writing a contract and not a +program. The difference between both is contracts have access to StarkNet's +storage, programs don't and as such are stateless. + +There are important functions you might need to import from the official +Cairo-lang library or Openzeppelin's, e.g. ```cairo - // A. CONSTRUCTORS - // Constructors are a way to intialize state variables on contract - // deployment - // You create a constructor using the @constructor decorator - - @constructor - func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}(_name: felt) { - let (caller) = get_caller_address(); - names.write(caller, _name); - return (); - } - - // B. EXTERNAL FUNCTIONS - // External functions are functions that modifies the state of the network - // You create an external function using the @external decorator - - @external - func 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 (); - } - - // C. VIEW FUNCTIONS - // View functions do not modify the state of the blockchain - // You can create a view function using the @view decorator - - @view - func get_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}(_address: felt) -> (name: felt){ - let (name) = names.read(_address); - return (name,); - } - - // NB: Unlike Solidity, Cairo supports just External and View function - // types. - // You can alternatively also create an internal function by not adding any - // decorator to the function. +from starkware.cairo.common.cairo_builtins import HashBuiltin +from cairo_contracts.src.openzeppelin.token.erc20.library import ERC20 +from starkware.cairo.common.uint256 import Uint256 +from starkware.cairo.common.bool import TRUE ``` -### 5. DECORATORS +### 3. Data Structures + ++ Storage variables: Cairo's storage is a map with `2^251` slots, where each + slot is a felt which is initialized to `0`. You create one using the + `@storage_var` decorator + + ```cairo + @storage_var + func names() -> (name: felt){} + ``` + ++ Storage mappings: Unlike soldity where mappings have a separate keyword, in + Cairo you create mappings using storage variables. + + ```cairo + @storage_var + func names(address: felt) -> (name: felt){} + ``` + ++ Structs: are a means to create custom data types in Cairo. A `struct` has a + size, which is the sum of the sizes of its members. The size can be + retrieved using `MyStruct.SIZE`. You create a struct in Cairo using the + `struct` keyword. + + ```cairo + struct Person { + name: felt, + age: felt, + address: felt, + } + ``` + ++ Constants: Constants are fixed and as such can't be altered after being set. + They evaluate to an integer (field element) at compile time. To create a + constant in Cairo, you use the `const` keyword. Its proper practice to + capitalize constant names. + + ```cairo + const USER = 0x01C6cfC1DB2ae90dACEA243F0a8C2F4e32560F7cDD398e4dA2Cc56B733774E9b + ``` + ++ Arrays: Arrays can be defined as a `pointer(felt*)` to the first element of + the array. As an array is populated, its elements take up contigous memory + cells. The `alloc` keyword can be used to dynamically allocate a new memory + segment, which can be used to store an array: + + ```cairo + let (myArray: felt*) = alloc (); + assert myArray[0] = 1; + assert myArray[1] = 2; + assert myArray[3] = 3; + ``` + + You can also use the `new` operator to create fixed-size arrays using + tuples. The new operator is useful as it enables you allocate memory and + initialize the object in one instruction + + ```cairo + func foo() { + tempvar arr: felt* = new (1, 1, 2, 3, 5); + assert arr[4] = 5; + return (); + } + ``` + ++ Tuples: A tuple is a finite, ordered, unchangeable list of elements. It is + represented as a comma-separated list of elements enclosed by parentheses. + Their elements may be of any combination of valid types. + + ```cairo + local tuple0: (felt, felt, felt) = (7, 9, 13); + ``` + ++ Events: Events allows a contract emit information during the course of its + execution, that can be used outside of StarkNet. An event can be created, + subsequently emitted: + + ```cairo + @event + func name_stored(address, name) {} + + name_stored.emit(address, name); + ``` + +### 4. Constructors, External and View functions + ++ Constructors: Constructors are a way to intialize state variables on + contract deployment. You create a constructor using the `@constructor` + decorator. + + ```cairo + @constructor + func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}(_name: felt) { + let (caller) = get_caller_address(); + names.write(caller, _name); + return (); + } + ``` + ++ External functions: External functions are functions that modifies the state + of the network. You create an external function using the `@external` + decorator: + + ```cairo + @external + func 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 (); + } + ``` + ++ View functions: View functions do not modify the state of the blockchain. + You can create a view function using the `@view` decorator. + + ```cairo + @view + func get_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}(_address: felt) -> (name: felt){ + let (name) = names.read(_address); + return (name,); + } + ``` + + NB: Unlike Solidity, Cairo supports just External and View function types. + You can alternatively also create an internal function by not adding any + decorator to the function. + +### 5. Decorators + +All functions in Cairo are specified by the `func` keyword, which can be +confusing. Decorators are used by the compiler to distinguish between these +functions. + +Here are the most common decorators you'll encounter in Cairo: + ++ `@storage_var` — used for specifying state variables. ++ `@constructor` — used for specifying constructors. ++ `@external` — used for specifying functions that write to a state variable. ++ `@event` — used for specifying events ++ `@view` — used to specify functions reading from a state variable ++ `@contract_interface` — used for specifying function interfaces. ++ `@l1_handler` — used for specifying functions that processes message sent from + an L1 contract in a messaging bridge. + +### 6. BUILTINS, HINTS & IMPLICIT Arguments + ++ `BUILTINS` are predefined optimized low-level execution units, which are + added to Cairo’s CPU board. They help perform predefined computations like + pedersen hashing, bitwise operations etc, which are expensive to perform in + Vanilla Cairo. Each builtin in Cairo is assigned a separate memory location, + accessible through regular Cairo memory calls using implicit parameters. You + specify them using the `%builtins` directive + + Here is a list of available builtins in Cairo: + + + `output` — the output builtin is used for writing program outputs + + `pedersen` — the pedersen builtin is used for pedersen hashing + computations + + `range_check` — This builtin is mostly used for integer comparisons, + and facilitates check to confirm that a field element is within a range + `[0, 2^128)` + + `ecdsa` — the ecdsa builtin is used for verifying ECDSA signatures + + `bitwise` — the bitwise builtin is used for carrying out bitwise + operations on felts + ++ `HINTS` are pieces of Python codes, which contains instructions that only + the prover sees and executes. From the point of view of the verifier these + hints do not exist. To specify a hint in Cairo, you need to encapsulate it + within `%{` and `%}`. It is good practice to avoid using hints as much as + you can in your contracts, as hints are not added to the bytecode, and thus + do not count in the total number of execution steps. + + ```cairo + %{ + # Python hint goes here + %} + ``` + ++ `IMPLICIT ARGUMENTS` are not restricted to the function body, but can be + inherited by other functions calls that require them. Implicit arguments are + passed in between curly bracelets, like you can see below: + + ```cairo + func 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 (); + } + ``` + +### 7. Error Messages and Access Controls + +You can create custom errors in Cairo which is outputted to the user upon failed +execution. This can be very useful for implementing checks and proper access +control mechanisms. An example is preventing a user to call a function except +user is `admin`. ```cairo - // All functions in Cairo are specified by the `func` keyword, which can be - // confusing. - // Decorators are used by the compiler to distinguish between these - // functions. - - // Here are the most common decorators you'll encounter in Cairo: - - // 1. @storage_var — used for specifying state variables. - // 2. @constructor — used for specifying constructors. - // 3. @external — used for specifying functions that write to a state - // variable. - // 4. @event — used for specifying events - // 5. @view — used for specifying functions that reads from a state - // variable. - // 6. @contract_interface - used for specifying function interfaces. - // 7. @l1_handler — used for specifying functions that processes message - // sent from an L1 contract in a messaging bridge. -``` +// imports +from starkware.starknet.common.syscalls import get_caller_address -### 6. BUILTINS, HINTS & IMPLICIT ARGUMENTS +// create an admin constant +const ADMIN = 0x01C6cfC1DB2ae90dACEA243F0a8C2F4e32560F7cDD398e4dA2Cc56B733774E9b -```cairo - // A. BUILTINS - // Builtins are predefined optimized low-level execution units, which are - // added to Cairo’s CPU board. - // They help perform predefined computations like pedersen hashing, bitwise - // operations etc, which are expensive to perform in Vanilla Cairo. - // Each builtin in Cairo, is assigned a separate memory location, - // accessible through regular Cairo memory calls using implicit parameters. - // You specify them using the %builtins directive - - // Here is a list of available builtins in Cairo: - // 1. output — the output builtin is used for writing program outputs - // 2. pedersen — the pedersen builtin is used for pedersen hashing - // computations - // 3. range_check — This builtin is mostly used for integer comparisons, - // and facilitates check to confirm that a field element is within a range [0, - // 2^128) - // 4. ecdsa — the ecdsa builtin is used for verifying ECDSA signatures - // 5. bitwise — the bitwise builtin is used for carrying out bitwise - // operations on felts - - // B. HINTS - // Hints are pieces of Python codes, which contains instructions that only - // the prover sees and executes - // From the point of view of the verifier these hints do not exist - // To specify a hint in Cairo, you need to encapsulate it within %{ and%} - // Its good practice to avoid using hints as much as you can in your - // contracts, as hints are not added to the bytecode, and thus do not count in the - // total number of execution steps. - - %{ - # Python hint goes here - %} - - // C. IMPLICIT ARGUMENTS - // Implicit arguments are not restricted to the function body, but can be - // inherited by other functions calls that require them. - // Implicit arguments are passed in between curly bracelets, like you can - // see below: - - func 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 (); - } +// implement access control +with_attr error_message("You do not have access to make this action!"){ + let (caller) = get_caller_address(); + assert ADMIN = caller; + } + +// using an assert statement throws if condition is not true, thus +// returning the specified error. ``` -### 7. ERROR MESSAGES & ACCESS CONTROLS +### 8. Contract Interfaces + +Contract interfaces provide a means for one contract to invoke or call the +external function of another contract. To create a contract interface, you use +the `@contract_interface` keyword: ```cairo - // You can create custom errors in Cairo which is outputted to the user - // upon failed execution. - // This can be very useful for implementing checks and proper access - // control mechanisms. - // An example is preventing a user to call a function except user is admin. - - // imports - from starkware.starknet.common.syscalls import get_caller_address - - // create an admin constant - const ADMIN = -0x01C6cfC1DB2ae90dACEA243F0a8C2F4e32560F7cDD398e4dA2Cc56B733774E9b - - // implement access control - with_attr error_message("You do not have access to make this action!"){ - let (caller) = get_caller_address(); - assert ADMIN = caller; +@contract_interface + namespace IENS { + func store_name(_name: felt) { } - // using an assert statement throws if condition is not true, thus - // returning the specified error. + func get_name(_address: felt) -> (name: felt) { + } + } ``` -### 8. CONTRACT INTERFACES +Once a contract interface is specified, any contract can make calls to that +contract passing in the contract address as the first parameter like this: ```cairo - // Contract interfaces provide a means for one contract to invoke or call - // the external function of another contract. - // To create a contract interface, you use the @contract_interface keyword - - @contract_interface - namespace IENS { - func store_name(_name: felt) { - } - - func get_name(_address: felt) -> (name: felt) { - } - } - - // Once a contract interface is specified, any contract can make calls to - // that contract passing in the contract address as the first parameter like this: - - IENS.store_name(contract_address, _name); - - // Note that Interfaces excludes the function body/logic and the implicit - // arguments. +IENS.store_name(contract_address, _name); ``` -### 9. RECURSIONS +Note that Interfaces excludes the function body/logic and the implicit +arguments. -```cairo - // Due to the unavailability of loops, Recursions are the go-to for similar - // operations. - // In simple terms, a recursive function is one which calls itself - // repeatedly. - - // A good example to demonstrate this is writing a function for getting the - // nth fibonacci number: - - @external - func fibonacci{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}(n : felt) -> (result : felt){ - alloc_locals; - if (n == 0){ - return (0); - } - if (n == 1){ - return (1); - } - let (local x) = fibonacci(n - 1); - let (local y) = fibonacci(n - 2); - return (result=(x + y)); - } - - // The nth fibonacci term is the sum of the nth - 1 and the nth - 2 - // numbers, that's why we get these two as (x, y) using recursion. - // NB: when implementing recursive functions, always remember to implement - // a base case (n==0, n==1 in our case), to prevent stack overflow. -``` +### 9. Recursions -Some low-level stuffs +Due to the unavailability of loops, Recursions are the go-to for similar +operations. In simple terms, a recursive function is one which calls itself +repeatedly. -### 10. REGISTERS +A good example to demonstrate this is writing a function for getting the nth +fibonacci number: ```cairo - // Registers holds values that may change over time. - - // There are 3 major types of Registers: - // 1. ap (allocation pointer) points to a yet unused memory. Temporary - // variables created using `let`, `tempvar` are held here, and thus susceptible to - // being revoked - // 2. fp (frame pointer) points to the frame of the current function. The - // address of all the function arguments and local variables are relative to this - // register and as such can never be revoked - // 3. pc (program counter) points to the current instruction +@external +func fibonacci{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}(n : felt) -> (result : felt){ + alloc_locals; + if (n == 0){ + return (0); + } + if (n == 1){ + return (1); + } + let (local x) = fibonacci(n - 1); + let (local y) = fibonacci(n - 2); + return (result=(x + y)); + } ``` -### 11. REVOKED REFERENCES +The nth fibonacci term is the sum of the `nth - 1` and the `nth - 2` numbers, +that's why we get these two as `(x,y)` using recursion. -```cairo - // Revoked references occurs when there is a call instruction to another - // function, between the definition of a reference variable that depends on - // `ap`(temp variables) and its usage. This occurs as the compiler may not be able - // to compute the change of `ap` (as one may jump to the label from another place - // in the program, or call a function that might change ap in an unknown way). - - // Here is an example to demonstrate what I mean: - - @external - func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}() -> (res: felt) { - return (res=100); - } - - @external - func double_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}() -> (res: felt) { - let multiplier = 2; - let (balance) = get_balance(); - let new_balance = balance * multiplier; - return (res=new_balance); - } - - // If you run that code, you'll run into the revoked reference error as we - // are trying to access the `multiplier` variable after calling the get_balance - // function; - - // To solve revoked references, In simple cases you can resolve this issue, - // by adding the keyword, `alloc_locals` within function scopes, but in most - // complex cases you might need to create a local variable to resolve it. - - // resolving the `double_balance` function: - @external - func double_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}() -> (res: felt) { - alloc_locals; - let multiplier = 2; - let (balance) = get_balance(); - let new_balance = balance * multiplier; - return (res=new_balance); - } -``` +NB: when implementing recursive functions, always remember to implement a base +case (`n==0`, `n==1` in our case), to prevent stack overflow. -Miscellaneous +### 10. Registers -### 12. Understanding Cairo's punctuations +Registers holds values that may change over time. There are 3 major types of +registers: -```cairo - // ; (semicolon). Used at the end of each instruction ++ `ap` (allocation pointer) points to a yet unused memory. Temporary variables + created using `let`, `tempvar` are held here, and thus susceptible to being + revoked. ++ `fp` (frame pointer) points to the frame of the current function. The address + of all the function arguments and local variables are relative to this + register and as such can never be revoked. ++ `pc` (program counter) points to the current instruction. - // ( ) (parentheses). Used in a function declaration, if statements, and in - // a tuple declaration +### 11. Revoked References - // { } (curly brackets). Used in a declaration of implicit arguments and to - // define code blocks. +Revoked references occurs when there is a call instruction to another function, +between the definition of a reference variable that depends on `ap`(temp +variables) and its usage. This occurs as the compiler may not be able to compute +the change of `ap` (as one may jump to the label from another place in the +program, or call a function that might change ap in an unknown way). - // [ ] (square brackets). Standalone brackets represent the value at a - // particular address location (such as the allocation pointer, [ap]). Brackets - // following a pointer or a tuple act as a subscript operator, where x[2] - // represents the element with index 2 in x. +Here is an example to demonstrate what I mean: - // * Single asterisk. Refers to the pointer of an expression. +```cairo +@external +func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}() -> (res: felt) { + return (res=100); + } - // % Percent sign. Appears at the start of a directive, such as %builtins - // or %lang. +@external +func double_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}() -> (res: felt) { + let multiplier = 2; + let (balance) = get_balance(); + let new_balance = balance * multiplier; + return (res=new_balance); + } +``` - // %{ %} Represents Python hints. +If you run that code, you'll run into the revoked reference error as we are +trying to access the `multiplier` variable after calling the `get_balance` +function. - // _ (underscore). A placeholder to handle values that are not used, such - // as an unused function return value. +In simple cases you can resolve revoked references by adding the keyword +`alloc_locals` within function scopes. In most complex cases you might need to +create a local variable to resolve it. + +```cairo +// resolving the `double_balance` function: +@external +func double_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}() -> (res: felt) { + alloc_locals; + let multiplier = 2; + let (balance) = get_balance(); + let new_balance = balance * multiplier; + return (res=new_balance); + } ``` -# FULL CONTRACT EXAMPLE +### 12. Understanding Cairo's Punctuations + ++ `;` (semicolon). Used at the end of each instruction ++ `()` (parentheses). Used in a function declaration, if statements, and in a + tuple declaration ++ `{}` (curly braces). Used in a declaration of implicit arguments and to define + code blocks. ++ `[]` (square brackets). Standalone brackets represent the value at a + particular address location (such as the allocation pointer, `[ap]`). Brackets + following a pointer or a tuple act as a subscript operator, where `x[2]` + represents the element with index `2` in `x`. ++ `*` (single asterisk). Refers to the pointer of an expression. ++ `%` (percent sign). Appears at the start of a directive, such as `%builtins` + or `%lang`. ++ `%{` and `%}` represent Python hints. ++ `_` (underscore). A placeholder to handle values that are not used, such as an + unused function return value. + +## Full Contract Example Below is a simple automated market maker contract example that implements most of what we just learnt! Re-write, deploy, have fun! ```cairo - %lang starknet +%lang starknet - from starkware.cairo.common.cairo_builtins import HashBuiltin - from starkware.cairo.common.hash import hash2 - from starkware.cairo.common.alloc import alloc - from starkware.cairo.common.math import (assert_le, assert_nn_le, - unsigned_div_rem) - from starkware.starknet.common.syscalls import (get_caller_address, - storage_read, storage_write) +from starkware.cairo.common.cairo_builtins import HashBuiltin +from starkware.cairo.common.hash import hash2 +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.math import (assert_le, assert_nn_le, + unsigned_div_rem) +from starkware.starknet.common.syscalls import (get_caller_address, + storage_read, storage_write) - // - // CONSTANTS - // +// CONSTANTS +// +// @dev the maximum amount of each token that belongs to the AMM +const BALANCE_UPPER_BOUND = 2 ** 64; - // @dev the maximum amount of each token that belongs to the AMM - const BALANCE_UPPER_BOUND = 2 ** 64; +const TOKEN_TYPE_A = 1; +const TOKEN_TYPE_B = 2; - const TOKEN_TYPE_A = 1; - const TOKEN_TYPE_B = 2; +// @dev Ensure the user's balances are much smaller than the pool's balance +const POOL_UPPER_BOUND = 2 ** 30; +const ACCOUNT_BALANCE_BOUND = 1073741; // (2 ** 30 / 1000) - // @dev Ensure the user's balances are much smaller than the pool's balance - const POOL_UPPER_BOUND = 2 ** 30; - const ACCOUNT_BALANCE_BOUND = 1073741; // (2 ** 30 / 1000) - // - // STORAGE VARIABLES - // +// STORAGE VARIABLES +// +// @dev A map from account and token type to corresponding balance +@storage_var +func account_balance(account_id: felt, token_type: felt) -> (balance: felt){} - // @dev A map from account and token type to corresponding balance - @storage_var - func account_balance(account_id: felt, token_type: felt) -> (balance: felt){ - } +// @dev a map from token type to corresponding pool balance +@storage_var +func pool_balance(token_type: felt) -> (balance: felt) {} - // @dev a map from token type to corresponding pool balance - @storage_var - func pool_balance(token_type: felt) -> (balance: felt) { - } - // - // GETTERS - // - - // @dev returns account balance for a given token - // @param account_id Account to be queried - // @param token_type Token to be queried - @view - func get_account_token_balance{syscall_ptr: felt*, pedersen_ptr: - HashBuiltin*, range_check_ptr}( - account_id: felt, token_type: felt +// GETTERS +// +// @dev returns account balance for a given token +// @param account_id Account to be queried +// @param token_type Token to be queried +@view +func get_account_token_balance{syscall_ptr: felt*, pedersen_ptr: + HashBuiltin*, range_check_ptr}( + account_id: felt, token_type: felt ) -> (balance: felt) { - return account_balance.read(account_id, token_type); - } + return account_balance.read(account_id, token_type); + } - // @dev return the pool's balance - // @param token_type Token type to get pool balance - @view - func get_pool_token_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}( - token_type: felt +// @dev return the pool's balance +// @param token_type Token type to get pool balance +@view +func get_pool_token_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}( + token_type: felt ) -> (balance: felt) { - return pool_balance.read(token_type); - } - - // - // EXTERNALS - // - - // @dev set pool balance for a given token - // @param token_type Token whose balance is to be set - // @param balance Amount to be set as balance - @external - func set_pool_token_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}( - token_type: felt, balance: felt - ) { - with_attr error_message("exceeds maximum allowed tokens!"){ - assert_nn_le(balance, BALANCE_UPPER_BOUND - 1); - } + return pool_balance.read(token_type); + } - pool_balance.write(token_type, balance); - return (); - } - - // @dev add demo token to the given account - // @param token_a_amount amount of token a to be added - // @param token_b_amount amount of token b to be added - @external - func add_demo_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}( - token_a_amount: felt, token_b_amount: felt - ) { - alloc_locals; - let (account_id) = get_caller_address(); - - modify_account_balance(account_id=account_id, token_type=TOKEN_TYPE_A, - amount=token_a_amount); - modify_account_balance(account_id=account_id, token_type=TOKEN_TYPE_B, - amount=token_b_amount); - - return (); - } - // @dev intialize AMM - // @param token_a amount of token a to be set in pool - // @param token_b amount of token b to be set in pool - @external - func init_pool{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}( - token_a: felt, token_b: felt +// EXTERNALS +// +// @dev set pool balance for a given token +// @param token_type Token whose balance is to be set +// @param balance Amount to be set as balance +@external +func set_pool_token_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}( + token_type: felt, balance: felt ) { - with_attr error_message("exceeds maximum allowed tokens!"){ - assert_nn_le(token_a, POOL_UPPER_BOUND - 1); - assert_nn_le(token_b, POOL_UPPER_BOUND - 1); - } - - set_pool_token_balance(token_type=TOKEN_TYPE_A, balance=token_a); - set_pool_token_balance(token_type=TOKEN_TYPE_B, balance=token_b); - - return (); + with_attr error_message("exceeds maximum allowed tokens!"){ + assert_nn_le(balance, BALANCE_UPPER_BOUND - 1); } + pool_balance.write(token_type, balance); + return (); + } - // @dev swaps token between the given account and the pool - // @param token_from token to be swapped - // @param amount_from amount of token to be swapped - // @return amount_to the token swapped to - @external - func swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - token_from: felt, amount_from: felt - ) -> (amount_to: felt) { - alloc_locals; - let (account_id) = get_caller_address(); - - // verify token_from is TOKEN_TYPE_A or TOKEN_TYPE_B - with_attr error_message("token not allowed in pool!"){ - assert (token_from - TOKEN_TYPE_A) * (token_from - TOKEN_TYPE_B) = 0; - } - - // check requested amount_from is valid - with_attr error_message("exceeds maximum allowed tokens!"){ - assert_nn_le(amount_from, BALANCE_UPPER_BOUND - 1); - } - - // check user has enough funds - let (account_from_balance) = - get_account_token_balance(account_id=account_id, token_type=token_from); - with_attr error_message("insufficient balance!"){ - assert_le(amount_from, account_from_balance); - } - - let (token_to) = get_opposite_token(token_type=token_from); - let (amount_to) = do_swap(account_id=account_id, token_from=token_from, - token_to=token_to, amount_from=amount_from); - - return (amount_to=amount_to); - } - - - // - // INTERNALS - // - - // @dev internal function that updates account balance for a given token - // @param account_id Account whose balance is to be modified - // @param token_type Token type to be modified - // @param amount Amount Amount to be added - func modify_account_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}( - account_id: felt, token_type: felt, amount: felt +// @dev add demo token to the given account +// @param token_a_amount amount of token a to be added +// @param token_b_amount amount of token b to be added +@external +func add_demo_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}( + token_a_amount: felt, token_b_amount: felt + ) { + alloc_locals; + let (account_id) = get_caller_address(); + + modify_account_balance(account_id=account_id, token_type=TOKEN_TYPE_A, + amount=token_a_amount); + modify_account_balance(account_id=account_id, token_type=TOKEN_TYPE_B, + amount=token_b_amount); + + return (); + } + +// @dev intialize AMM +// @param token_a amount of token a to be set in pool +// @param token_b amount of token b to be set in pool +@external +func init_pool{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}( + token_a: felt, token_b: felt ) { - let (current_balance) = account_balance.read(account_id, token_type); - tempvar new_balance = current_balance + amount; + with_attr error_message("exceeds maximum allowed tokens!"){ + assert_nn_le(token_a, POOL_UPPER_BOUND - 1); + assert_nn_le(token_b, POOL_UPPER_BOUND - 1); + } - with_attr error_message("exceeds maximum allowed tokens!"){ - assert_nn_le(new_balance, BALANCE_UPPER_BOUND - 1); - } + set_pool_token_balance(token_type=TOKEN_TYPE_A, balance=token_a); + set_pool_token_balance(token_type=TOKEN_TYPE_B, balance=token_b); - account_balance.write(account_id=account_id, token_type=token_type, - value=new_balance); - return (); - } + return (); + } - // @dev internal function that swaps tokens between the given account and - // the pool - // @param account_id Account whose tokens are to be swapped - // @param token_from Token type to be swapped from - // @param token_to Token type to be swapped to - // @param amount_from Amount to be swapped - func do_swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, - range_check_ptr}( - account_id: felt, token_from: felt, token_to: felt, amount_from: felt - ) -> (amount_to: felt) { - alloc_locals; - - // get pool balance - let (local amm_from_balance) = get_pool_token_balance(token_type = - token_from); - let (local amm_to_balance) = - get_pool_token_balance(token_type=token_to); - - // calculate swap amount - let (local amount_to, _) = unsigned_div_rem((amm_to_balance * - amount_from), (amm_from_balance + amount_from)); - - // update token_from balances - modify_account_balance(account_id=account_id, token_type=token_from, - amount=-amount_from); - set_pool_token_balance(token_type=token_from, balance=(amm_from_balance - + amount_from)); - - // update token_to balances - modify_account_balance(account_id=account_id, token_type=token_to, - amount=amount_to); - set_pool_token_balance(token_type=token_to, balance=(amm_to_balance - - amount_to)); - - return (amount_to=amount_to); - } - // @dev internal function to get the opposite token type - // @param token_type Token whose opposite pair needs to be gotten - func get_opposite_token(token_type: felt) -> (t: felt) { - if(token_type == TOKEN_TYPE_A) { - return (t=TOKEN_TYPE_B); - } else { - return (t=TOKEN_TYPE_A); - } +// @dev swaps token between the given account and the pool +// @param token_from token to be swapped +// @param amount_from amount of token to be swapped +// @return amount_to the token swapped to +@external +func swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + token_from: felt, amount_from: felt + ) -> (amount_to: felt) { + alloc_locals; + let (account_id) = get_caller_address(); + + // verify token_from is TOKEN_TYPE_A or TOKEN_TYPE_B + with_attr error_message("token not allowed in pool!"){ + assert (token_from - TOKEN_TYPE_A) * (token_from - TOKEN_TYPE_B) = 0; + } + + // check requested amount_from is valid + with_attr error_message("exceeds maximum allowed tokens!"){ + assert_nn_le(amount_from, BALANCE_UPPER_BOUND - 1); + } + + // check user has enough funds + let (account_from_balance) = + get_account_token_balance(account_id=account_id, token_type=token_from); + with_attr error_message("insufficient balance!"){ + assert_le(amount_from, account_from_balance); + } + + let (token_to) = get_opposite_token(token_type=token_from); + let (amount_to) = do_swap(account_id=account_id, token_from=token_from, + token_to=token_to, amount_from=amount_from); + + return (amount_to=amount_to); + } + + +// INTERNALS +// +// @dev internal function that updates account balance for a given token +// @param account_id Account whose balance is to be modified +// @param token_type Token type to be modified +// @param amount Amount Amount to be added +func modify_account_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, +range_check_ptr}( + account_id: felt, token_type: felt, amount: felt + ) { + let (current_balance) = account_balance.read(account_id, token_type); + tempvar new_balance = current_balance + amount; + + with_attr error_message("exceeds maximum allowed tokens!"){ + assert_nn_le(new_balance, BALANCE_UPPER_BOUND - 1); + } + + account_balance.write(account_id=account_id, token_type=token_type, + value=new_balance); + return (); + } + +// @dev internal function that swaps tokens between the given account and +// the pool +// @param account_id Account whose tokens are to be swapped +// @param token_from Token type to be swapped from +// @param token_to Token type to be swapped to +// @param amount_from Amount to be swapped +func do_swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, +range_check_ptr}( + account_id: felt, token_from: felt, token_to: felt, amount_from: felt + ) -> (amount_to: felt) { + alloc_locals; + + // get pool balance + let (local amm_from_balance) = get_pool_token_balance(token_type = + token_from); + let (local amm_to_balance) = get_pool_token_balance(token_type=token_to); + + // calculate swap amount + let (local amount_to, _) = unsigned_div_rem((amm_to_balance * + amount_from), (amm_from_balance + amount_from)); + + // update token_from balances + modify_account_balance(account_id=account_id, token_type=token_from, + amount=-amount_from); + set_pool_token_balance(token_type=token_from, balance=(amm_from_balance + + amount_from)); + + // update token_to balances + modify_account_balance(account_id=account_id, token_type=token_to, + amount=amount_to); + set_pool_token_balance(token_type=token_to, balance=(amm_to_balance - + amount_to)); + + return (amount_to=amount_to); + } + + + // @dev internal function to get the opposite token type + // @param token_type Token whose opposite pair needs to be gotten + func get_opposite_token(token_type: felt) -> (t: felt) { + if(token_type == TOKEN_TYPE_A) { + return (t=TOKEN_TYPE_B); + } else { + return (t=TOKEN_TYPE_A); } + } ``` -# Additional Resources +## Additional Resources -1. [Official documentation](https://www.cairo-lang.org/docs/) -2. [Starknet EDU](https://medium.com/starknet-edu) -3. [Journey through Cairo](https://medium.com/@darlingtonnnam/journey-through-cairo-i-setting-up-protostar-and-argentx-for-local-development-ba40ae6c5524) -4. [Demystifying Cairo whitepaper](https://medium.com/@pban/demystifying-cairo-white-paper-part-i-b71976ad0108) -5. [Learn about StarkNet with Argent](https://www.argent.xyz/learn/tag/starknet/) ++ [Official documentation](https://www.cairo-lang.org/docs/) ++ [Starknet EDU](https://medium.com/starknet-edu) ++ [Journey through Cairo](https://medium.com/@darlingtonnnam/journey-through-cairo-i-setting-up-protostar-and-argentx-for-local-development-ba40ae6c5524) ++ [Demystifying Cairo whitepaper](https://medium.com/@pban/demystifying-cairo-white-paper-part-i-b71976ad0108) ++ [Learn about StarkNet with Argent](https://www.argent.xyz/learn/tag/starknet/) -# Development Frameworks +## Development Frameworks -1. [Protostar](https://docs.swmansion.com/protostar/docs/tutorials/installation) -2. [Nile](https://github.com/OpenZeppelin/nile) -3. [StarkNet CLI](https://www.cairo-lang.org/docs/quickstart.html) ++ [Protostar](https://docs.swmansion.com/protostar/docs/tutorials/installation) ++ [Nile](https://github.com/OpenZeppelin/nile) ++ [StarkNet CLI](https://www.cairo-lang.org/docs/quickstart.html) -# Helpful Libraries +## Helpful Libraries -1. [Cairo-lang](https://github.com/starkware-libs/cairo-lang) -2. [Openzeppelin](https://github.com/OpenZeppelin/cairo-contracts) ++ [Cairo-lang](https://github.com/starkware-libs/cairo-lang) ++ [Openzeppelin](https://github.com/OpenZeppelin/cairo-contracts) -# Educational Repos +## Educational Repos -1. [StarkNet Cairo 101](https://github.com/starknet-edu/starknet-cairo-101) -2. [StarkNet ERC721](https://github.com/starknet-edu/starknet-erc721) -3. [StarkNet ERC20](https://github.com/starknet-edu/starknet-erc20) -4. [L1 -> L2 Messaging](https://github.com/starknet-edu/starknet-messaging-bridge) -5. [StarkNet Debug](https://github.com/starknet-edu/starknet-debug) -6. [StarkNet Accounts](https://github.com/starknet-edu/starknet-accounts) -7. [Min-Starknet](https://github.com/Darlington02/min-starknet) ++ [StarkNet Cairo 101](https://github.com/starknet-edu/starknet-cairo-101) ++ [StarkNet ERC721](https://github.com/starknet-edu/starknet-erc721) ++ [StarkNet ERC20](https://github.com/starknet-edu/starknet-erc20) ++ [L1 -> L2 Messaging](https://github.com/starknet-edu/starknet-messaging-bridge) ++ [StarkNet Debug](https://github.com/starknet-edu/starknet-debug) ++ [StarkNet Accounts](https://github.com/starknet-edu/starknet-accounts) ++ [Min-Starknet](https://github.com/Darlington02/min-starknet) -# Security +## Security -1. [Amarna static analysis for Cairo programs](https://blog.trailofbits.com/2022/04/20/amarna-static-analysis-for-cairo-programs/) -2. [Cairo and StarkNet security by Ctrl03](https://ctrlc03.github.io/) -3. [How to hack almost any Cairo smart contract](https://medium.com/ginger-security/how-to-hack-almost-any-starknet-cairo-smart-contract-67b4681ac0f6) -4. [Analyzing Cairo code using Armana](https://dic0de.substack.com/p/analyzing-cairo-code-using-amarna?sd=pf) ++ [Amarna static analysis for Cairo programs](https://blog.trailofbits.com/2022/04/20/amarna-static-analysis-for-cairo-programs/) ++ [Cairo and StarkNet security by Ctrl03](https://ctrlc03.github.io/) ++ [How to hack almost any Cairo smart contract](https://medium.com/ginger-security/how-to-hack-almost-any-starknet-cairo-smart-contract-67b4681ac0f6) ++ [Analyzing Cairo code using Armana](https://dic0de.substack.com/p/analyzing-cairo-code-using-amarna?sd=pf) -# Future TO-DOs +## Future TO-DOs Update tutorial to fit Cairo 1.0 |