learnxinyminutes-docs/cairo.html.markdown

898 lines
31 KiB
Markdown
Raw Normal View History

2023-01-23 12:24:45 +03:00
---
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.
2023-01-23 12:24:45 +03:00
# 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.
2023-01-23 12:24:45 +03:00
**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!**
2023-01-23 12:24:45 +03:00
# 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.
2023-01-23 12:24:45 +03:00
## Initializing a new project
Protostar similar to Truffle for solidity development can be installed once and
used for multiple projects.
2023-01-23 12:24:45 +03:00
To initialize a new Protostar project, run the following command:
2023-01-23 12:24:45 +03:00
```
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.
2023-01-23 12:24:45 +03:00
# 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.
2023-01-23 12:24:45 +03:00
## 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:
2023-01-23 12:24:45 +03:00
```
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.
2023-01-23 12:24:45 +03:00
<img src="./cairo_assets/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:
2023-01-23 12:24:45 +03:00
```
export PROTOSTAR_ACCOUNT_PRIVATE_KEY=[YOUR PRIVATE KEY HERE]
```
Then to declare our contract using Protostar run the following command:
2023-01-23 12:24:45 +03:00
```
protostar declare ./build/main.json --network testnet --account
0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 --max-fee
auto
2023-01-23 12:24:45 +03:00
```
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:
2023-01-23 12:24:45 +03:00
<img src="./cairo_assets/declare.png" alt="declaring your contract">
## Deploying Contracts
After obtaining our class hash from declaring, we can now deploy using the
below command:
2023-01-23 12:24:45 +03:00
```
protostar deploy
0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc --network
testnet --account
0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 --max-fee
auto
2023-01-23 12:24:45 +03:00
```
where `0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc` is
the class hash of our contract.
2023-01-23 12:24:45 +03:00
<img src="./cairo_assets/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, check out 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:
2023-01-23 12:24:45 +03:00
1. Click on the "connect wallet" button
<img src="./cairo_assets/connect.png" alt="connect wallet">
2023-01-23 12:24:45 +03:00
2. Select Argent X and approve the connection
<img src="./cairo_assets/connect2.png" alt="connect to argentX">
2023-01-23 12:24:45 +03:00
3. 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
2023-01-23 12:24:45 +03:00
```cairo
// 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);
2023-01-23 12:24:45 +03:00
}
let (res) = balance.read();
balance.write(res + amount);
return ();
}
2023-01-23 12:24:45 +03:00
// @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,);
}
2023-01-23 12:24:45 +03:00
// before proceeding, try to build, deploy and interact with this contract!
// NB: Should be at main.cairo if you are using Protostar.
2023-01-23 12:24:45 +03:00
```
2023-01-23 12:24:45 +03:00
Now unto the main lessons
### 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.
2023-01-23 12:24:45 +03:00
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.
2023-01-23 12:24:45 +03:00
```
### 2. LANG DIRECTIVE AND IMPORTS
```cairo
// To get started with writing a StarkNet contract, you must specify the
// directive:
2023-01-23 12:24:45 +03:00
%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.
2023-01-23 12:24:45 +03:00
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
```cairo
2023-01-23 12:24:45 +03:00
// A. STORAGE VARIABLES
// Cairo's storage is a map with 2^251 slots, where each slot is a felt
// which is initialized to 0.
2023-01-23 12:24:45 +03:00
// 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.
2023-01-23 12:24:45 +03:00
@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.
2023-01-23 12:24:45 +03:00
// 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
2023-01-23 12:24:45 +03:00
// E. ARRAYS
// Arrays can be defined as a pointer(felt*) to the first element of the
//array.
2023-01-23 12:24:45 +03:00
// 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
2023-01-23 12:24:45 +03:00
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
2023-01-23 12:24:45 +03:00
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
2023-01-23 12:24:45 +03:00
// 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.
2023-01-23 12:24:45 +03:00
// To create an event:
@event
func name_stored(address, name) {
}
// To emit an event:
name_stored.emit(address, name);
```
### 4. CONSTRUCTORS, EXTERNAL AND VIEW FUNCTIONS
```cairo
2023-01-23 12:24:45 +03:00
// A. CONSTRUCTORS
// Constructors are a way to intialize state variables on contract
// deployment
2023-01-23 12:24:45 +03:00
// You create a constructor using the @constructor decorator
@constructor
func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(_name: felt) {
2023-01-23 12:24:45 +03:00
let (caller) = get_caller_address();
names.write(caller, _name);
return ();
}
2023-01-23 12:24:45 +03:00
// 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){
2023-01-23 12:24:45 +03:00
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){
2023-01-23 12:24:45 +03:00
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.
2023-01-23 12:24:45 +03:00
```
### 5. DECORATORS
```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.
2023-01-23 12:24:45 +03:00
// 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.
2023-01-23 12:24:45 +03:00
// 4. @event — used for specifying events
// 5. @view — used for specifying functions that reads from a state
// variable.
2023-01-23 12:24:45 +03:00
// 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.
2023-01-23 12:24:45 +03:00
```
### 6. BUILTINS, HINTS & IMPLICIT ARGUMENTS
```cairo
2023-01-23 12:24:45 +03:00
// A. BUILTINS
// Builtins are predefined optimized low-level execution units, which are
// added to Cairos 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.
2023-01-23 12:24:45 +03:00
// 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)
2023-01-23 12:24:45 +03:00
// 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
2023-01-23 12:24:45 +03:00
// B. HINTS
// Hints are pieces of Python codes, which contains instructions that only
// the prover sees and executes
2023-01-23 12:24:45 +03:00
// 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.
2023-01-23 12:24:45 +03:00
%{
# Python hint goes here
2023-01-23 12:24:45 +03:00
%}
// 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:
2023-01-23 12:24:45 +03:00
func store_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(_name: felt){
2023-01-23 12:24:45 +03:00
let (caller) = get_caller_address();
names.write(caller, _name);
stored_name.emit(caller, _name);
return ();
}
```
### 7. ERROR MESSAGES & ACCESS CONTROLS
```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.
2023-01-23 12:24:45 +03:00
// 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
2023-01-23 12:24:45 +03:00
// 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.
2023-01-23 12:24:45 +03:00
```
### 8. CONTRACT INTERFACES
```cairo
// Contract interfaces provide a means for one contract to invoke or call
// the external function of another contract.
2023-01-23 12:24:45 +03:00
// 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:
2023-01-23 12:24:45 +03:00
IENS.store_name(contract_address, _name);
// Note that Interfaces excludes the function body/logic and the implicit
// arguments.
2023-01-23 12:24:45 +03:00
```
### 9. RECURSIONS
```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:
2023-01-23 12:24:45 +03:00
@external
func fibonacci{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(n : felt) -> (result : felt){
2023-01-23 12:24:45 +03:00
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.
2023-01-23 12:24:45 +03:00
```
Some low-level stuffs
### 10. REGISTERS
```cairo
2023-01-23 12:24:45 +03:00
// 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
2023-01-23 12:24:45 +03:00
// 3. pc (program counter) points to the current instruction
```
### 11. REVOKED REFERENCES
```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).
2023-01-23 12:24:45 +03:00
// Here is an example to demonstrate what I mean:
@external
func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}() -> (res: felt) {
2023-01-23 12:24:45 +03:00
return (res=100);
}
@external
func double_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}() -> (res: felt) {
2023-01-23 12:24:45 +03:00
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.
2023-01-23 12:24:45 +03:00
// resolving the `double_balance` function:
@external
func double_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}() -> (res: felt) {
2023-01-23 12:24:45 +03:00
alloc_locals;
let multiplier = 2;
let (balance) = get_balance();
let new_balance = balance * multiplier;
return (res=new_balance);
}
```
Miscellaneous
### 12. Understanding Cairo's punctuations
```cairo
2023-01-23 12:24:45 +03:00
// ; (semicolon). Used at the end of each instruction
// ( ) (parentheses). Used in a function declaration, if statements, and in
// a tuple declaration
2023-01-23 12:24:45 +03:00
// { } (curly brackets). Used in a declaration of implicit arguments and to
// define code blocks.
2023-01-23 12:24:45 +03:00
// [ ] (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.
2023-01-23 12:24:45 +03:00
// * Single asterisk. Refers to the pointer of an expression.
// % Percent sign. Appears at the start of a directive, such as %builtins
// or %lang.
2023-01-23 12:24:45 +03:00
// %{ %} Represents Python hints.
// _ (underscore). A placeholder to handle values that are not used, such
// as an unused function return value.
2023-01-23 12:24:45 +03:00
```
# 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
2023-01-23 12:24:45 +03:00
%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)
2023-01-23 12:24:45 +03:00
//
2023-01-23 12:24:45 +03:00
// CONSTANTS
//
2023-01-23 12:24:45 +03:00
// @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)
//
2023-01-23 12:24:45 +03:00
// STORAGE VARIABLES
//
2023-01-23 12:24:45 +03:00
// @dev A map from account and token type to corresponding balance
@storage_var
func account_balance(account_id: felt, token_type: felt) -> (balance: felt){
2023-01-23 12:24:45 +03:00
}
// @dev a map from token type to corresponding pool balance
@storage_var
func pool_balance(token_type: felt) -> (balance: felt) {
}
//
2023-01-23 12:24:45 +03:00
// GETTERS
//
2023-01-23 12:24:45 +03:00
// @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}(
2023-01-23 12:24:45 +03:00
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}(
2023-01-23 12:24:45 +03:00
token_type: felt
) -> (balance: felt) {
return pool_balance.read(token_type);
}
//
2023-01-23 12:24:45 +03:00
// EXTERNALS
//
2023-01-23 12:24:45 +03:00
// @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}(
2023-01-23 12:24:45 +03:00
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}(
2023-01-23 12:24:45 +03:00
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);
2023-01-23 12:24:45 +03:00
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}(
2023-01-23 12:24:45 +03:00
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);
2023-01-23 12:24:45 +03:00
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);
2023-01-23 12:24:45 +03:00
return (amount_to=amount_to);
}
//
2023-01-23 12:24:45 +03:00
// INTERNALS
//
2023-01-23 12:24:45 +03:00
// @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}(
2023-01-23 12:24:45 +03:00
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);
2023-01-23 12:24:45 +03:00
return ();
}
// @dev internal function that swaps tokens between the given account and
// the pool
2023-01-23 12:24:45 +03:00
// @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}(
2023-01-23 12:24:45 +03:00
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);
2023-01-23 12:24:45 +03:00
// calculate swap amount
let (local amount_to, _) = unsigned_div_rem((amm_to_balance *
amount_from), (amm_from_balance + amount_from));
2023-01-23 12:24:45 +03:00
// 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));
2023-01-23 12:24:45 +03:00
// 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));
2023-01-23 12:24:45 +03:00
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
2023-01-23 12:24:45 +03:00
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/)
# Development Frameworks
2023-01-23 12:24:45 +03:00
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)
# Helpful Libraries
2023-01-23 12:24:45 +03:00
1. [Cairo-lang](https://github.com/starkware-libs/cairo-lang)
2. [Openzeppelin](https://github.com/OpenZeppelin/cairo-contracts)
# Educational Repos
2023-01-23 12:24:45 +03:00
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)
# Security
2023-01-23 12:24:45 +03:00
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)
# Future TO-DOs
Update tutorial to fit Cairo 1.0