diff options
Diffstat (limited to 'cairo.html.markdown')
| -rw-r--r-- | cairo.html.markdown | 864 | 
1 files changed, 864 insertions, 0 deletions
| diff --git a/cairo.html.markdown b/cairo.html.markdown new file mode 100644 index 00000000..dd3ca036 --- /dev/null +++ b/cairo.html.markdown @@ -0,0 +1,864 @@ +--- +language: Cairo +filename: learnCairo.sol +contributors: +    - ["Darlington Nnam", "https://github.com/Darlington02"] +--- + +# 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. + +## StarkNet + +StarkNet is a decentralized ZK-rollup that operates as an Ethereum layer 2 +chain. + +In this document, we are going to be going in-depth into understanding Cairo's +syntax and how you could create and deploy a Cairo smart contract on StarkNet. + +**NB: As at the time of this writing, StarkNet is still at v0.10.3, with Cairo +1.0 coming soon. The ecosystem is young and evolving very fast, so you might +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 + +Before we get started writing codes, we will need to setup a Cairo development +environment, for writing, compiling and deploying our contracts to StarkNet. +For the purpose of this tutorial we are going to be using the +[Protostar Framework](https://github.com/software-mansion/protostar). +Installation steps can be found in the docs +[here](https://docs.swmansion.com/protostar/docs/tutorials/installation). +Note that Protostar supports just Mac and Linux OS, Windows users might need to +use WSL, or go for other alternatives such as the Official +[StarkNet CLI](https://www.cairo-lang.org/docs/quickstart.html) or +[Nile from Openzeppelin](https://github.com/OpenZeppelin/nile) + +Once you're done with the installations, run the command `protostar -v` to +confirm your installation was successful. If successful, you should see your +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: + +``` +protostar init +``` + +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 + +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 + +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 +you've done that, open your terminal and run the command: + +``` +protostar build +``` + +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="./images/cairo/build.png" alt="building your contract"> + +### 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 +[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 +private key associated with the specified account address in a file, or in the +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 (for +visual clarity, the backslash sign symbolizes the continuing line): + +``` +protostar declare ./build/main.json \ +  --network testnet \ +  --account 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 \ +  --max-fee auto +``` + +where `network` specifies the network we are deploying to, `account` specifies +account whose private key we are using, `max-fee` specifies the maximum fee to +be paid for the transaction. You should get the class hash outputted as seen +below: +<img src="./images/cairo/declare.png" alt="declaring your contract"> + +### Deploying Contracts + +After obtaining our class hash from declaring, we can now deploy using the +command below: + +``` +protostar \ +  deploy 0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc \ +  --network testnet \ +  --account 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 \ +  --max-fee auto +``` + +where `0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc` is +the class hash of our contract. +<img src="./images/cairo/deploy.png" alt="deploying your contract"> + +### 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`, 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 in the following +sequence: + ++ click on the "connect wallet" button, +  <img src="./images/cairo/connect.png" alt="connect wallet"> ++ select `Argent X` and approve the connection +  <img src="./images/cairo/connect2.png" alt="connect to argentX"> ++ you can now make read and write calls easily. + +## Let's learn Cairo + +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. + +``` +// Language directive - instructs compiler its a StarkNet contract +%lang starknet + +// Library imports from the Cairo-lang library +from starkware.cairo.common.math import assert_nn +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. +@storage_var +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(); +} + +// @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. +@external +func increase_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, +range_check_ptr}(amount: felt){ +  with_attr error_message("Amount must be positive. Got: {amount}.") { +    assert_nn(amount); +  } + +  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. +@view +func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, +range_check_ptr}() -> (res: felt) { +  let (res) = balance.read(); +  return (res,); +} +``` + +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 + +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. + +### 2. Lang Directive and Imports + +To get started with writing a StarkNet contract, you must specify the directive: + +``` +%lang starknet +``` + +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. + +``` +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 + ++ 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) {} +  ``` + ++ Storage mappings: Unlike Solidity where mappings have a separate keyword, in +  Cairo you create mappings using storage variables. + +  ``` +  @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. + +  ``` +  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. It's proper practice to +  capitalize constant names. + +  ``` +  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: + +  ``` +  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 (); +  } +  ``` + ++ 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); +  ``` + ++ 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: + +  ``` +  @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. + +  ``` +  @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: + +  ``` +  @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. + +  ``` +  @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. + +  ``` +  %{ +    # 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: + +  ``` +  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`. + +``` +// 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; +} + +// using an assert statement throws if condition is not true, thus +// returning the specified error. +``` + +### 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: + +``` +@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 exclude the function body/logic and the implicit +arguments. + +### 9. Recursions + +Due to the unavailability of loops, Recursion is 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 overflows. + +### 10. Registers + +Registers holds values that may change over time. There are 3 major types of +registers: + ++ `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. + +### 11. Revoked References + +Revoked references occur 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. + +In simple cases you can resolve revoked references by adding the keyword +`alloc_locals` within function scopes. In more 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); +} +``` + +### 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! + +``` +%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) + + +// CONSTANTS +// +// @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; + +// @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 +// +// @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) {} + + +// 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); +} + +// @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); +  } + +  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 +  ) { +  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 (); +} + + +// @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 + ++ [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 + ++ [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 + ++ [Cairo-lang](https://github.com/starkware-libs/cairo-lang) ++ [Openzeppelin](https://github.com/OpenZeppelin/cairo-contracts) + +## Educational Repos + ++ [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 + ++ [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 + +Update tutorial to fit Cairo 1.0 | 
