├── tutorials
├── imgs
│ ├── erc721.png
│ ├── cairo-vscode.png
│ ├── cairo-builtins.png
│ ├── cairo-extensions.png
│ └── walking_with_starknet_banner.png
└── tutorials
│ ├── EN
│ ├── 1_installation.md
│ ├── 4_protostar.md
│ ├── 3_cairo_basics.md
│ └── 2_cairo_basics.md
│ ├── PT
│ ├── 1_installation.md
│ ├── 3_cairo_basics.md
│ └── 2_cairo_basics.md
│ └── ES
│ ├── 1_installation.md
│ ├── 4_protostar.md
│ └── 3_cairo_basics.md
├── .gitmodules
├── src
├── interfaces
│ ├── IRNGOracle.cairo
│ ├── IBreedAnimals.cairo
│ └── IERC721Random.cairo
├── division1.cairo
├── division2.cairo
├── serializeWord.cairo
├── felts1.cairo
├── asserts.cairo
├── utils
│ ├── array.cairo
│ ├── utils.py
│ ├── contract_address.py
│ ├── metadata_utils.cairo
│ └── accounts_utils.py
├── multiplication.cairo
├── vector.cairo
├── sum.cairo
├── balance.cairo
├── matrix.cairo
├── ERC721MintableBurnable.cairo
├── voting.cairo
├── randomERC721.cairo
├── ERC721Custom.cairo
└── oz_modified_account.cairo
├── protostar.toml
├── LICENSE
├── README.md
├── .gitignore
├── tests
└── test_ERC721_random_end_to_end.cairo
└── migrations
└── migration_voting.cairo
/tutorials/imgs/erc721.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starknet-edu/walking-with-starknet/HEAD/tutorials/imgs/erc721.png
--------------------------------------------------------------------------------
/tutorials/imgs/cairo-vscode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starknet-edu/walking-with-starknet/HEAD/tutorials/imgs/cairo-vscode.png
--------------------------------------------------------------------------------
/tutorials/imgs/cairo-builtins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starknet-edu/walking-with-starknet/HEAD/tutorials/imgs/cairo-builtins.png
--------------------------------------------------------------------------------
/tutorials/imgs/cairo-extensions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starknet-edu/walking-with-starknet/HEAD/tutorials/imgs/cairo-extensions.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "cairo_contracts_git"]
2 | url = https://github.com/OpenZeppelin/cairo-contracts.git
3 | path = lib/cairo_contracts_git
4 |
--------------------------------------------------------------------------------
/tutorials/imgs/walking_with_starknet_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starknet-edu/walking-with-starknet/HEAD/tutorials/imgs/walking_with_starknet_banner.png
--------------------------------------------------------------------------------
/src/interfaces/IRNGOracle.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | // Request random numbers: https://github.com/0xNonCents/VRF-StarkNet
4 | @contract_interface
5 | namespace IRNGOracle {
6 | func request_rng(beacon_address: felt) -> (requestId: felt) {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/division1.cairo:
--------------------------------------------------------------------------------
1 | %builtins output
2 |
3 | from starkware.cairo.common.serialize import serialize_word
4 |
5 | func main{output_ptr: felt*}() {
6 | tempvar x = 9 / 3;
7 | assert x = 3;
8 | serialize_word(x);
9 |
10 | return ();
11 | }
12 |
--------------------------------------------------------------------------------
/src/division2.cairo:
--------------------------------------------------------------------------------
1 | %builtins output
2 |
3 | from starkware.cairo.common.serialize import serialize_word
4 |
5 | func main{output_ptr: felt*}() {
6 | tempvar x = 10 / 3;
7 | assert x = 10 / 3;
8 | serialize_word(x);
9 |
10 | return ();
11 | }
12 |
--------------------------------------------------------------------------------
/src/serializeWord.cairo:
--------------------------------------------------------------------------------
1 | %builtins output
2 |
3 | func serialize_word{output_ptr: felt*}(word: felt) {
4 | assert [output_ptr] = word;
5 | let output_ptr = output_ptr + 1;
6 | // El nuevo valor de output_ptr es implícitamente añadido en return.
7 | return ();
8 | }
9 |
--------------------------------------------------------------------------------
/src/felts1.cairo:
--------------------------------------------------------------------------------
1 | %builtins output
2 |
3 | from starkware.cairo.common.serialize import serialize_word
4 |
5 | func main{output_ptr: felt*}() {
6 | tempvar x = 10 / 3;
7 |
8 | tempvar y = 3 * x;
9 | assert y = 10;
10 | serialize_word(y);
11 |
12 | tempvar z = 10 / x;
13 | assert z = 3;
14 | serialize_word(z);
15 |
16 | return ();
17 | }
18 |
--------------------------------------------------------------------------------
/src/asserts.cairo:
--------------------------------------------------------------------------------
1 | %builtins range_check
2 |
3 | from starkware.cairo.common.math import assert_not_zero, assert_not_equal, assert_nn, assert_le
4 |
5 | func main{range_check_ptr: felt}() {
6 | assert_not_zero(1); // not zero
7 | assert_not_equal(1, 2); // not equal
8 | assert_nn(1); // non-negative
9 | assert_le(1, 10); // less or equal
10 |
11 | return ();
12 | }
13 |
--------------------------------------------------------------------------------
/protostar.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | protostar-version = "0.7.0"
3 | lib-path = "lib"
4 | cairo-path = ["lib/cairo_contracts/src"]
5 |
6 | [contracts]
7 | vote = ["src/voting.cairo"]
8 | account = ["src/erase/1.cairo"]
9 |
10 | [profile.devnet.deploy]
11 | gateway-url = "http://127.0.0.1:5050/"
12 | chain-id = "1"
13 |
14 | [profile.testnet.deploy]
15 | network = "testnet"
16 |
17 | [profile.mainnet.deploy]
18 | network = "alpha-mainnet"
19 |
--------------------------------------------------------------------------------
/src/utils/array.cairo:
--------------------------------------------------------------------------------
1 | from starkware.cairo.common.memcpy import memcpy
2 | from starkware.cairo.common.alloc import alloc
3 |
4 | func concat_arr{range_check_ptr}(arr1_len: felt, arr1: felt*, arr2_len: felt, arr2: felt*) -> (
5 | res: felt*, res_len: felt
6 | ) {
7 | alloc_locals;
8 | let (local res: felt*) = alloc();
9 | memcpy(res, arr1, arr1_len);
10 | memcpy(res + arr1_len, arr2, arr2_len);
11 | return (res, arr1_len + arr2_len);
12 | }
13 |
--------------------------------------------------------------------------------
/src/multiplication.cairo:
--------------------------------------------------------------------------------
1 | %builtins output
2 |
3 | from starkware.cairo.common.serialize import serialize_word
4 |
5 | // @dev Multiply two numbers and return the result
6 | // @param num1 (felt): first number to multiply
7 | // @param num2 (felt): second number to multiply
8 | // @return prod (felt): value of the multiplication of the two numbers
9 | func mult_two_nums(num1, num2) -> (prod: felt) {
10 | return (prod=num1 * num2);
11 | }
12 |
13 | func main{output_ptr: felt*}() {
14 | let (prod) = mult_two_nums(2, 2);
15 | serialize_word(prod);
16 | return ();
17 | }
18 |
--------------------------------------------------------------------------------
/src/vector.cairo:
--------------------------------------------------------------------------------
1 | %builtins output
2 |
3 | from starkware.cairo.common.serialize import serialize_word
4 |
5 | struct Vector2d {
6 | x: felt,
7 | y: felt,
8 | }
9 |
10 | func add_2d(v1: Vector2d, v2: Vector2d) -> (r: Vector2d) {
11 | alloc_locals;
12 |
13 | local res: Vector2d;
14 | assert res.x = v1.x + v2.x;
15 | assert res.y = v1.y + v2.y;
16 |
17 | return (r=res);
18 | }
19 |
20 | func main{output_ptr: felt*}() {
21 | let v1 = Vector2d(x=1, y=2);
22 | let v2 = Vector2d(x=3, y=4);
23 |
24 | let (sum) = add_2d(v1, v2);
25 |
26 | serialize_word(sum.x);
27 | serialize_word(sum.y);
28 |
29 | return ();
30 | }
31 |
--------------------------------------------------------------------------------
/src/sum.cairo:
--------------------------------------------------------------------------------
1 | %builtins output
2 |
3 | from starkware.cairo.common.serialize import serialize_word
4 |
5 | // @dev Add two numbers and return the result
6 | // @param num1 (felt): first number to add
7 | // @param num2 (felt): second number to add
8 | // @return sum (felt): value of the sum of the two numbers
9 | func sum_two_nums(num1: felt, num2: felt) -> (sum: felt) {
10 | alloc_locals;
11 | local sum = num1 + num2;
12 | return (sum=sum);
13 | }
14 |
15 | func main{output_ptr: felt*}() {
16 | alloc_locals;
17 |
18 | const NUM1 = 1;
19 | const NUM2 = 10;
20 |
21 | let (sum) = sum_two_nums(num1=NUM1, num2=NUM2);
22 | serialize_word(sum);
23 | return ();
24 | }
25 |
--------------------------------------------------------------------------------
/src/balance.cairo:
--------------------------------------------------------------------------------
1 | // Declare this file as a StarkNet contract.
2 | %lang starknet
3 |
4 | from starkware.cairo.common.cairo_builtins import HashBuiltin
5 |
6 | // Define a storage variable.
7 | @storage_var
8 | func balance() -> (res: felt) {
9 | }
10 |
11 | // Increases the balance by the given amount.
12 | @external
13 | func increase_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
14 | amount: felt
15 | ) {
16 | let (res) = balance.read();
17 | balance.write(res + amount);
18 | return ();
19 | }
20 |
21 | // Returns the current balance.
22 | @view
23 | func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (res: felt) {
24 | let (res) = balance.read();
25 | return (res=res);
26 | }
27 |
--------------------------------------------------------------------------------
/src/interfaces/IBreedAnimals.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.uint256 import Uint256
4 |
5 | @contract_interface
6 | namespace IExerciseSolution {
7 | // Breeding function
8 | func is_breeder(account: felt) -> (is_approved: felt) {
9 | }
10 | func registration_price() -> (price: Uint256) {
11 | }
12 | func register_me_as_breeder() -> (is_added: felt) {
13 | }
14 | func declare_animal(sex: felt, legs: felt, wings: felt) -> (token_id: Uint256) {
15 | }
16 | func get_animal_characteristics(token_id: Uint256) -> (sex: felt, legs: felt, wings: felt) {
17 | }
18 | func token_of_owner_by_index(account: felt, index: felt) -> (token_id: Uint256) {
19 | }
20 | func declare_dead_animal(token_id: Uint256) {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/utils.py:
--------------------------------------------------------------------------------
1 | MAX_LEN_FELT = 31
2 |
3 | def str_to_felt(text):
4 | if len(text) > MAX_LEN_FELT:
5 | raise Exception("Text length too long to convert to felt.")
6 | b_text = bytes(text, "UTF-8")
7 | return int.from_bytes(b_text, "big")
8 |
9 | def felt_to_str(felt):
10 | length = (felt.bit_length() + 7) // 8
11 | return felt.to_bytes(length, byteorder="big").decode("utf-8")
12 |
13 | def str_to_felt_array(text):
14 | # Break string into array of strings that meet felt requirements
15 | chunks = []
16 | for i in range(0, len(text), MAX_LEN_FELT):
17 | str_chunk = text[i:i+MAX_LEN_FELT]
18 | chunks.append(str_to_felt(str_chunk))
19 | return chunks
20 |
21 | def uint256_to_int(uint256):
22 | return uint256[0] + uint256[1]*2**128
23 |
24 | def uint256(val):
25 | return (val & 2**128-1, (val & (2**256-2**128)) >> 128)
26 |
27 | def hex_to_felt(val):
28 | return int(val, 16)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Omar U. Espejel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/matrix.cairo:
--------------------------------------------------------------------------------
1 | %builtins output
2 |
3 | from starkware.cairo.common.serialize import serialize_word
4 | from starkware.cairo.common.alloc import alloc
5 |
6 | struct Vector {
7 | elements: felt*,
8 | }
9 |
10 | struct Matrix {
11 | x: Vector,
12 | y: Vector,
13 | }
14 |
15 | func main{output_ptr: felt*}() {
16 | // Defining an array, my_array, of felts.
17 | let (my_array: felt*) = alloc();
18 |
19 | // Assigning values to three elements of my_array.
20 | assert my_array[0] = 1;
21 | assert my_array[1] = 2;
22 | assert my_array[2] = 3;
23 |
24 | // Creating the vectors Vector, by
25 | // simplicity we use the same my_array for both.
26 | let v1 = Vector(elements=my_array);
27 | let v2 = Vector(elements=my_array);
28 |
29 | // Defining an array of Matrix matrices
30 | let (matrix_array: Matrix*) = alloc();
31 |
32 | // Filling matrix_array with Matrix instances.
33 | // Each instance of Matrix contains as members
34 | // Vector instances.
35 | assert matrix_array[0] = Matrix(x=v1, y=v2);
36 | assert matrix_array[1] = Matrix(x=v1, y=v2);
37 |
38 | // We use assert to test some values in
39 | // our matrix_array.
40 | assert matrix_array[0].x.elements[0] = 1;
41 | assert matrix_array[1].x.elements[1] = 2;
42 |
43 | // What value do you think it will print? Answer: 3
44 | serialize_word(matrix_array[1].x.elements[2]);
45 |
46 | return ();
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # Cairo and StarkNet Tutorials
8 |
9 | Learn your way to Ethereum scalability; step by step and in community.
10 |
11 | ## Tutorials
12 |
13 | | Language | Source | Contributors |
14 | |:---------:|:--------------------------------------------------------------------------------------------------------------:|-------------------------------------------------|
15 | | English | [ `tutorials/EN` ](https://github.com/starknet-edu/walking-with-starknet/tree/master/tutorials/tutorials/EN) | @[omarespejel](https://github.com/omarespejel) |
16 | | Spanish | [ `tutorials/ES` ](https://github.com/starknet-edu/walking-with-starknet/tree/master/tutorials/tutorials/ES) | @[omarespejel](https://github.com/omarespejel) |
17 | | Potuguese | [ `tutorials/PT` ](https://github.com/starknet-edu/walking-with-starknet/tree/master/tutorials/tutorials/PT) | @[omarespejel](https://github.com/omarespejel) |
18 |
19 | Our goal is to reach as many developers in Cairo as possible. Open a PR if you want to translate into your own language.
20 |
21 | ## Contribute
22 |
23 | Your contribution would be greatly appreciated. Cairo and StarkNet are evolving frameworks; therefore, any contribution to keep these tutorials up to date and ready would be greatly appreciated.
24 |
25 | Examples of contributions:
26 | * Translate into your language.
27 | * Update tutorials to new versions of Cairo and StarkNet.
28 | * Add examples or additional details to the tutorials.
29 | * Correct errors or typos.
30 | * Additional tutorials.
31 |
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .temp/
3 |
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | .Python
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | pip-wheel-metadata/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | .python-version
87 |
88 | # pipenv
89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
92 | # install all needed dependencies.
93 | #Pipfile.lock
94 |
95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
96 | __pypackages__/
97 |
98 | # Celery stuff
99 | celerybeat-schedule
100 | celerybeat.pid
101 |
102 | # SageMath parsed files
103 | *.sage.py
104 |
105 | # Environments
106 | .env
107 | .venv
108 | env/
109 | venv/
110 | ENV/
111 | env.bak/
112 | venv.bak/
113 |
114 | # Spyder project settings
115 | .spyderproject
116 | .spyproject
117 |
118 | # Rope project settings
119 | .ropeproject
120 |
121 | # mkdocs documentation
122 | /site
123 |
124 | # mypy
125 | .mypy_cache/
126 | .dmypy.json
127 | dmypy.json
128 |
129 | # Pyre type checker
130 | .pyre/
131 |
132 | # vscode project settings
133 | .vscode/
134 | .gitsigners
135 |
136 | # code not ready yet
137 | tutorials/tutorials/ES/6_openzeppelin.md
138 | tutorials/tutorials/ES/7_randomness.md
139 | tutorials/tutorials/ES/8_starknetpy.md
140 | tutorials/tutorials/ES/6_starknet_CLI.md
141 | src/DICEeliminar.cairo
142 | src/randomERC721.cairo
143 | scripts/
144 | scripts/deploy.py
145 | build/*
146 | migrations/*.json
147 | assets
148 | assets/*
149 | src/erase/
150 | src/first_account_contract.cairo
151 | starknet-accounts/
152 |
153 |
154 | # private key
155 | pkey
156 |
--------------------------------------------------------------------------------
/src/utils/contract_address.py:
--------------------------------------------------------------------------------
1 | # This a copy of the contract_address.py library from the Starkware library:
2 | # https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/core/os/contract_address/contract_address.py
3 | # We added print statements in calculate_contract_address() function to get the class hash, salt, and constructor calldata
4 | # If you wish to use it, replace your site-packages/starkware/starknet/core/os/contract_address/contract_address.py with this file.
5 |
6 | from typing import Callable, Sequence
7 |
8 | from starkware.cairo.common.hash_state import compute_hash_on_elements
9 | from starkware.cairo.lang.vm.crypto import pedersen_hash
10 | from starkware.python.utils import from_bytes
11 | from starkware.starknet.core.os.class_hash import compute_class_hash
12 | from starkware.starknet.definitions.constants import L2_ADDRESS_UPPER_BOUND
13 | from starkware.starknet.services.api.contract_class import ContractClass
14 |
15 | CONTRACT_ADDRESS_PREFIX = from_bytes(b"STARKNET_CONTRACT_ADDRESS")
16 |
17 |
18 | def calculate_contract_address(
19 | salt: int,
20 | contract_class: ContractClass,
21 | constructor_calldata: Sequence[int],
22 | deployer_address: int,
23 | hash_function: Callable[[int, int], int] = pedersen_hash,
24 | ) -> int:
25 | """
26 | Calculates the contract address in the starkNet network - a unique identifier of the contract.
27 | The contract address is a hash chain of the following information:
28 | 1. Prefix.
29 | 2. Deployer address.
30 | 3. Salt.
31 | 4. Class hash.
32 | To avoid exceeding the maximum address we take modulus L2_ADDRESS_UPPER_BOUND of the above
33 | result.
34 | """
35 | class_hash = compute_class_hash(contract_class=contract_class, hash_func=hash_function)
36 | print(f"Class Hash: {class_hash}")
37 | print(f"Salt: {salt}")
38 | print(f"Constructor calldata: {constructor_calldata}")
39 |
40 | return calculate_contract_address_from_hash(
41 | salt=salt,
42 | class_hash=class_hash,
43 | constructor_calldata=constructor_calldata,
44 | deployer_address=deployer_address,
45 | hash_function=hash_function,
46 | )
47 |
48 |
49 | def calculate_contract_address_from_hash(
50 | salt: int,
51 | class_hash: int,
52 | constructor_calldata: Sequence[int],
53 | deployer_address: int,
54 | hash_function: Callable[[int, int], int] = pedersen_hash,
55 | ) -> int:
56 | """
57 | Same as calculate_contract_address(), except that it gets class_hash instead of
58 | contract_class.
59 | """
60 | constructor_calldata_hash = compute_hash_on_elements(
61 | data=constructor_calldata, hash_func=hash_function
62 | )
63 | raw_address = compute_hash_on_elements(
64 | data=[
65 | CONTRACT_ADDRESS_PREFIX,
66 | deployer_address,
67 | salt,
68 | class_hash,
69 | constructor_calldata_hash,
70 | ],
71 | hash_func=hash_function,
72 | )
73 |
74 | return raw_address % L2_ADDRESS_UPPER_BOUND
75 |
--------------------------------------------------------------------------------
/src/utils/metadata_utils.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.alloc import alloc
4 | from starkware.cairo.common.cairo_builtins import HashBuiltin
5 | from starkware.cairo.common.uint256 import Uint256
6 |
7 | from openzeppelin.token.erc721.library import ERC721
8 |
9 | from src.utils.array import concat_arr
10 |
11 | // ------
12 | // Storage
13 | // ------
14 |
15 | @storage_var
16 | func erc721_baseURI(array_index: felt) -> (text: felt) {
17 | }
18 |
19 | @storage_var
20 | func erc721_baseURI_len() -> (len: felt) {
21 | }
22 |
23 | @storage_var
24 | func erc721_base_tokenURI_suffix() -> (suffix: felt) {
25 | }
26 |
27 | // ------
28 | // Non-Constant Functions: state-changing functions
29 | // ------
30 |
31 | func ERC721_Metadata_tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
32 | token_id: Uint256, random_number: felt
33 | ) -> (token_uri_len: felt, token_uri: felt*) {
34 | alloc_locals;
35 |
36 | let exists = ERC721._exists(token_id);
37 | assert exists = 1;
38 |
39 | // TODO XXX Might be missing to read the storage with the base_token_uri
40 | let (local base_token_uri) = alloc();
41 | let (local base_token_uri_len) = erc721_baseURI_len.read();
42 |
43 | // Save in storage the URI base
44 | _store_base_tokenURI(base_token_uri_len, base_token_uri);
45 |
46 | // let (token_id_ss_len, token_id_ss) = uint256_to_ss(token_id);
47 |
48 | // Concatenate in an array the URI's base len, and base, the token_id's len and body
49 | let (local number) = alloc();
50 | [number] = random_number;
51 |
52 | let (token_uri_temp, token_uri_len_temp) = concat_arr(
53 | base_token_uri_len, base_token_uri, 1, number
54 | );
55 |
56 | // Store in suffix the array containing the suffix
57 | let (ERC721_base_token_uri_suffix_local) = erc721_base_tokenURI_suffix.read();
58 | let (local suffix) = alloc();
59 | [suffix] = ERC721_base_token_uri_suffix_local;
60 |
61 | // Concatenate the previous array now with the suffix
62 | let (token_uri, token_uri_len) = concat_arr(token_uri_len_temp, token_uri_temp, 1, suffix);
63 |
64 | return (token_uri_len=token_uri_len, token_uri=token_uri);
65 | }
66 |
67 | // Save in storage all the base and suffix of the token URI
68 | func set_base_tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
69 | token_uri_len: felt, token_uri: felt*, token_uri_suffix: felt
70 | ) {
71 | // store the baseURI string into the erc721_baseURI storage variable
72 | _store_base_tokenURI(token_uri_len, token_uri);
73 |
74 | // store the baseURI string length the erc721_baseURI_len storage variable
75 | erc721_baseURI_len.write(token_uri_len);
76 |
77 | // store the tokenURI suffix (e.g. json) in the erc721_base_tokenURI_suffix storage variable
78 | erc721_base_tokenURI_suffix.write(token_uri_suffix);
79 | return ();
80 | }
81 |
82 | // Store the value of an array into an storage variable
83 | func _store_base_tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
84 | token_uri_len: felt, token_uri: felt*
85 | ) {
86 | if (token_uri_len == 0) {
87 | return ();
88 | }
89 | // At position "token_uri_len" of the array "token_uri" store [token_uri]
90 | erc721_baseURI.write(token_uri_len, [token_uri]);
91 | _store_base_tokenURI(token_uri_len=token_uri_len - 1, token_uri=token_uri + 1);
92 | return ();
93 | }
94 |
--------------------------------------------------------------------------------
/tests/test_ERC721_random_end_to_end.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.uint256 import Uint256
4 |
5 | from src.interfaces.IERC721Custom import IERC721Custom
6 |
7 | // ---------
8 | // CONSTANTS
9 | // ---------
10 |
11 | const oracle_addr = 111;
12 | const _beacon_address = 222;
13 |
14 | // ---------
15 | // INTERFACES
16 | // ---------
17 |
18 | @contract_interface
19 | namespace IEvaluator {
20 | func assigned_legs_number(player_address: felt) -> (legs: felt) {
21 | }
22 | }
23 |
24 | // ---------
25 | // TESTS
26 | // ---------
27 |
28 | @external
29 | func __setup__() {
30 | %{
31 | declared = declare("src/contracts/random721.cairo")
32 | prepared = prepare(declared, [ids.oracle_addr, ids._beacon_address])
33 | context.erc721_custom_address = deploy(prepared).contract_address
34 | %}
35 | return ();
36 | }
37 |
38 | @external
39 | func test_erc721_random_deploy{syscall_ptr: felt*, range_check_ptr}() {
40 | tempvar erc721_custom_address: felt;
41 |
42 | %{ ids.erc721_custom_address = context.erc721_custom_address %}
43 |
44 | let (name) = IERC721Custom.name(contract_address=erc721_custom_address);
45 | let (symbol) = IERC721Custom.symbol(contract_address=erc721_custom_address);
46 | let (owner) = IERC721Custom.owner(contract_address=erc721_custom_address);
47 |
48 | assert NAME = name;
49 | assert SYMBOL = symbol;
50 | assert OWNER = owner;
51 |
52 | return ();
53 | }
54 |
55 | // Test minting an animal with certain characteristics and get back their characteristics
56 | @external
57 | func test_declare_animals{syscall_ptr: felt*, range_check_ptr}() {
58 | alloc_locals;
59 | tempvar erc721_custom_address: felt;
60 |
61 | // Mock call to evaluator contract to get number of legs
62 | tempvar external_contract_address = 123;
63 |
64 | %{ stop_mock = mock_call(ids.external_contract_address, "assigned_legs_number", [10]) %}
65 | let (n_legs) = IEvaluator.assigned_legs_number(external_contract_address, OWNER);
66 | %{ stop_mock() %}
67 |
68 | assert 10 = n_legs;
69 |
70 | // Get ERC721 contract address
71 | %{ ids.erc721_custom_address = context.erc721_custom_address %}
72 |
73 | // Declare two animals
74 | %{ stop_prank_owner = start_prank(ids.OWNER, ids.erc721_custom_address) %}
75 | let first_animal_token: Uint256 = IERC721Custom.declare_animal(
76 | contract_address=erc721_custom_address, sex=SEX, legs=n_legs, wings=WINGS
77 | );
78 | let second_animal_token: Uint256 = IERC721Custom.declare_animal(
79 | contract_address=erc721_custom_address, sex=SEX + 1, legs=n_legs - 1, wings=WINGS + 1
80 | );
81 | %{ stop_prank_owner() %}
82 |
83 | // Assert that the last tokenID minted is 2 and equals "second_animal_token"
84 | let two_uint: Uint256 = Uint256(2, 0);
85 | let one_uint: Uint256 = Uint256(1, 0);
86 | let last_minted_tokenID: Uint256 = IERC721Custom.get_last_tokenID(
87 | contract_address=erc721_custom_address
88 | );
89 | assert two_uint = last_minted_tokenID;
90 | assert two_uint = second_animal_token;
91 |
92 | // Assert that the animal characteristics corresponds to the declared ones
93 | let (local sex, legs, wings) = IERC721Custom.get_animal_characteristics(
94 | contract_address=erc721_custom_address, token_id=one_uint
95 | );
96 | assert SEX = sex;
97 | assert n_legs = legs;
98 | assert WINGS = wings;
99 |
100 | let (local sex, legs, wings) = IERC721Custom.get_animal_characteristics(
101 | contract_address=erc721_custom_address, token_id=two_uint
102 | );
103 | assert SEX + 1 = sex;
104 | assert n_legs - 1 = legs;
105 | assert WINGS + 1 = wings;
106 |
107 | return ();
108 | }
109 |
--------------------------------------------------------------------------------
/src/utils/accounts_utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import Sequence, Optional, List
3 |
4 | from starkware.cairo.common.hash_state import compute_hash_on_elements
5 | from starkware.cairo.lang.vm.crypto import pedersen_hash
6 | from starkware.python.utils import from_bytes
7 | from starkware.starknet.core.os.class_hash import compute_class_hash
8 | from starkware.starknet.core.os.contract_address.contract_address import (
9 | calculate_contract_address,
10 | )
11 | from starkware.starknet.services.api.contract_class import ContractClass
12 | from starkware.starknet.services.api.gateway.transaction import InvokeFunction
13 |
14 | CONTRACT_ADDRESS_PREFIX = from_bytes(b"STARKNET_CONTRACT_ADDRESS")
15 |
16 |
17 | # Calculates the pedersen hash of a contract
18 | def calculate_contract_hash(
19 | salt: int,
20 | class_hash: int,
21 | constructor_calldata: Sequence[int],
22 | deployer_address: int,
23 | ) -> int:
24 |
25 | # Hash constructor calldata
26 | constructor_calldata_hash = compute_hash_on_elements(
27 | data=constructor_calldata, hash_func=pedersen_hash
28 | )
29 |
30 | return compute_hash_on_elements(
31 | data=[
32 | CONTRACT_ADDRESS_PREFIX,
33 | deployer_address,
34 | salt,
35 | class_hash,
36 | constructor_calldata_hash,
37 | ],
38 | hash_func=pedersen_hash,
39 | )
40 |
41 |
42 | # Gets the class of a contract using ContractClass.loads
43 | def get_contract_class(contract_name: str) -> ContractClass:
44 |
45 | with open(contract_name, "r") as fp:
46 | contract_class = ContractClass.loads(data=fp.read())
47 |
48 | return contract_class
49 |
50 |
51 | # Gets the address of the account contract
52 | def get_address(
53 | contract_path_and_name: str,
54 | salt: int,
55 | constructor_calldata: Sequence[int],
56 | deployer_address: int = 0,
57 | compiled: bool = False,
58 | ) -> int:
59 |
60 | # Compile the account contract: must be in cairo environment
61 | # `compiled_account_contract` looks similar to "build/contract_compiled.json"
62 | if compiled:
63 | compiled_account_contract: str = contract_path_and_name
64 | else:
65 | os.system(
66 | f"starknet-compile {contract_path_and_name}.cairo --output {contract_path_and_name}_compiled.json"
67 | )
68 | compiled_account_contract: str = f"{contract_path_and_name}_compiled.json"
69 |
70 | # Get contract class
71 | contract_class = get_contract_class(contract_name=compiled_account_contract)
72 |
73 | # Get contract class hash for information purposes
74 | class_hash = compute_class_hash(
75 | contract_class=contract_class, hash_func=pedersen_hash
76 | )
77 |
78 | contract_address: int = calculate_contract_address(
79 | salt=salt,
80 | contract_class=contract_class,
81 | constructor_calldata=constructor_calldata,
82 | deployer_address=deployer_address,
83 | )
84 |
85 | print(
86 | f"""\
87 | Account contract address: 0x{contract_address:064x}
88 | Class contract hash: 0x{class_hash:064x}
89 | Salt: 0x{salt:064x}
90 | Constructor call data: {constructor_calldata}
91 |
92 | Move the appropriate amount of funds to the account. Then deploy the account.
93 | """
94 | )
95 |
96 | return contract_address
97 |
98 |
99 | # def invoke_fn(
100 | # signer_address: int,
101 | # calldata: Sequence[int],
102 | # max_fee: int,
103 | # nonce: Optional[int],
104 | # version: int,
105 | # signature: List[int],
106 | # ):
107 | # return InvokeFunction(
108 | # contract_address=signer_address,
109 | # calldata=calldata,
110 | # max_fee=max_fee,
111 | # nonce=nonce,
112 | # signature=signature
113 | # version=version,
114 | # )
115 |
116 |
--------------------------------------------------------------------------------
/migrations/migration_voting.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.alloc import alloc
4 |
5 | // ---------
6 | // CONSTANTS
7 | // ---------
8 |
9 | const VOTING_ADMIN = 0x02cdAb749380950e7a7c0deFf5ea8eDD716fEb3a2952aDd4E5659655077B8510;
10 | const NUMBER_VOTING_ADDRESSES = 2;
11 | const VOTING_ADDRESS_1 = 0x02c7e70bbd22095ad396f13e265ef18fc78257957e3f4e7b897cd9b9e8da3e77;
12 | const VOTING_ADDRESS_2 = 0x02cdAb749380950e7a7c0deFf5ea8eDD716fEb3a2952aDd4E5659655077B8510;
13 |
14 | // ---------
15 | // MIGRATION
16 | // ---------
17 |
18 | // Deploy the following migration in the CLI with:
19 | // protostar migrate migrations/migration_voting.cairo --network testnet --private-key-path pkey --account-address 0x02c7e70bbd22095ad396f13e265ef18fc78257957e3f4e7b897cd9b9e8da3e77
20 | // protostar migrate migrations/migration_voting.cairo --gateway-url "http://127.0.0.1:5050/" --chain-id "1" --private-key-path pkey --account-address 0x02c7e70bbd22095ad396f13e265ef18fc78257957e3f4e7b897cd9b9e8da3e77
21 | @external
22 | func up() {
23 | tempvar before_voting_status: felt;
24 | tempvar after_voting_status: felt;
25 | tempvar votes_yes: felt;
26 | tempvar votes_no: felt;
27 |
28 | %{
29 | # Deploy voting contract. Wait for acceptance of in the testnet
30 | voting_contract_address = deploy_contract(
31 | contract="./build/vote.json",
32 | constructor_args=[ids.VOTING_ADMIN, ids.NUMBER_VOTING_ADDRESSES, ids.VOTING_ADDRESS_1, ids.VOTING_ADDRESS_2],
33 | config={"wait_for_acceptance": True}
34 | ).contract_address
35 |
36 | # Assert if calling address can vote. The result from the getter function is a Python dict
37 | voter_status = call(
38 | contract_address=voting_contract_address,
39 | function_name="get_voter_status",
40 | inputs={"user_address": ids.VOTING_ADDRESS_1},
41 | )
42 | ids.before_voting_status = voter_status.status["allowed"]
43 | %}
44 |
45 | // Assert voter is allowed to vote. Print this in next hint
46 | assert 1 = before_voting_status;
47 |
48 | %{
49 | from console import fg
50 |
51 | print(fg.green, fx.italic, f"Voter with address {ids.VOTING_ADDRESS_1} is allowed to vote.", fx.end, sep='')
52 |
53 | # Vote 1 with the address calling the voting contract (see migration code in CLI)
54 | invoke(
55 | contract_address=voting_contract_address,
56 | function_name="vote",
57 | inputs={"vote": 1},
58 | config={
59 | "wait_for_acceptance": True,
60 | "max_fee": "auto",
61 | }
62 | )
63 | print(f"Voter with address {ids.VOTING_ADDRESS_1} voted.")
64 |
65 | # Assert if calling address can no longer vote
66 | voter_status = call(
67 | contract_address=voting_contract_address,
68 | function_name="get_voter_status",
69 | inputs={"user_address": ids.VOTING_ADDRESS_1}
70 | )
71 | ids.after_voting_status = voter_status.status["allowed"]
72 | %}
73 |
74 | // Assert voter is no longer allowed to vote. Print this in next hint
75 | assert 0 = after_voting_status;
76 |
77 | %{
78 | print(f"Voter with address {ids.VOTING_ADDRESS_1} is not allowed to vote.")
79 |
80 | # Get the status of the vote after initial vote
81 | voting_status = call(
82 | contract_address=voting_contract_address,
83 | function_name="get_voting_status",
84 | )
85 | ids.votes_yes = voting_status.status["votes_yes"]
86 | ids.votes_no = voting_status.status["votes_no"]
87 | %}
88 |
89 | // Assert we have 1 YES and 0 NOs.
90 | assert 1 = votes_yes;
91 | assert 0 = votes_no;
92 |
93 | return ();
94 | }
95 |
96 | @external
97 | func down() {
98 | %{ assert False, "Not implemented" %}
99 | return ();
100 | }
101 |
--------------------------------------------------------------------------------
/src/ERC721MintableBurnable.cairo:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // OpenZeppelin Contracts for Cairo v0.4.0b (token/erc721/presets/ERC721MintableBurnable.cairo)
3 |
4 | %lang starknet
5 |
6 | from starkware.cairo.common.cairo_builtins import HashBuiltin
7 | from starkware.cairo.common.uint256 import Uint256
8 |
9 | from openzeppelin.access.ownable.library import Ownable
10 | from openzeppelin.introspection.erc165.library import ERC165
11 | from openzeppelin.token.erc721.library import ERC721
12 |
13 | //
14 | // Constructor
15 | //
16 |
17 | @constructor
18 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
19 | name: felt, symbol: felt, owner: felt
20 | ) {
21 | ERC721.initializer(name, symbol);
22 | Ownable.initializer(owner);
23 | return ();
24 | }
25 |
26 | //
27 | // Getters
28 | //
29 |
30 | @view
31 | func supportsInterface{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
32 | interfaceId: felt
33 | ) -> (success: felt) {
34 | return ERC165.supports_interface(interfaceId);
35 | }
36 |
37 | @view
38 | func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (name: felt) {
39 | return ERC721.name();
40 | }
41 |
42 | @view
43 | func symbol{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (symbol: felt) {
44 | return ERC721.symbol();
45 | }
46 |
47 | @view
48 | func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(owner: felt) -> (
49 | balance: Uint256
50 | ) {
51 | return ERC721.balance_of(owner);
52 | }
53 |
54 | @view
55 | func ownerOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(tokenId: Uint256) -> (
56 | owner: felt
57 | ) {
58 | return ERC721.owner_of(tokenId);
59 | }
60 |
61 | @view
62 | func getApproved{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
63 | tokenId: Uint256
64 | ) -> (approved: felt) {
65 | return ERC721.get_approved(tokenId);
66 | }
67 |
68 | @view
69 | func isApprovedForAll{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
70 | owner: felt, operator: felt
71 | ) -> (isApproved: felt) {
72 | let (isApproved: felt) = ERC721.is_approved_for_all(owner, operator);
73 | return (isApproved=isApproved);
74 | }
75 |
76 | @view
77 | func tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
78 | tokenId: Uint256
79 | ) -> (tokenURI: felt) {
80 | let (tokenURI: felt) = ERC721.token_uri(tokenId);
81 | return (tokenURI=tokenURI);
82 | }
83 |
84 | @view
85 | func owner{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (owner: felt) {
86 | return Ownable.owner();
87 | }
88 |
89 | //
90 | // Externals
91 | //
92 |
93 | @external
94 | func approve{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
95 | to: felt, tokenId: Uint256
96 | ) {
97 | ERC721.approve(to, tokenId);
98 | return ();
99 | }
100 |
101 | @external
102 | func setApprovalForAll{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
103 | operator: felt, approved: felt
104 | ) {
105 | ERC721.set_approval_for_all(operator, approved);
106 | return ();
107 | }
108 |
109 | @external
110 | func transferFrom{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
111 | from_: felt, to: felt, tokenId: Uint256
112 | ) {
113 | ERC721.transfer_from(from_, to, tokenId);
114 | return ();
115 | }
116 |
117 | @external
118 | func safeTransferFrom{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
119 | from_: felt, to: felt, tokenId: Uint256, data_len: felt, data: felt*
120 | ) {
121 | ERC721.safe_transfer_from(from_, to, tokenId, data_len, data);
122 | return ();
123 | }
124 |
125 | @external
126 | func mint{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
127 | to: felt, tokenId: Uint256
128 | ) {
129 | Ownable.assert_only_owner();
130 | ERC721._mint(to, tokenId);
131 | return ();
132 | }
133 |
134 | @external
135 | func burn{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(tokenId: Uint256) {
136 | ERC721.assert_only_token_owner(tokenId);
137 | ERC721._burn(tokenId);
138 | return ();
139 | }
140 |
141 | @external
142 | func setTokenURI{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
143 | tokenId: Uint256, tokenURI: felt
144 | ) {
145 | Ownable.assert_only_owner();
146 | ERC721._set_token_uri(tokenId, tokenURI);
147 | return ();
148 | }
149 |
150 | @external
151 | func transferOwnership{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
152 | newOwner: felt
153 | ) {
154 | Ownable.transfer_ownership(newOwner);
155 | return ();
156 | }
157 |
158 | @external
159 | func renounceOwnership{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
160 | Ownable.renounce_ownership();
161 | return ();
162 | }
163 |
--------------------------------------------------------------------------------
/tutorials/tutorials/EN/1_installation.md:
--------------------------------------------------------------------------------
1 | # Programming on Ethereum's L2 (pt. 1): Installing Cairo and StarkNet
2 |
3 | This is the first tutorial of a series focused on developing smart contracts with Cairo and StarkNet. Here we prepare our team to program in Cairo; in the second and third tutorial we review the basics of programming in Cairo.
4 |
5 | 🚀 The future of Ethereum is today and it's already here. And it's just the beginning.
6 |
7 | ---
8 |
9 | We are going to learn how to install Cairo on our machines and get everything ready to start creating ❤️. We will also learn basic commands to interact with Cairo from our terminal.
10 |
11 | The Cairo documentation gives us very clear instructions. However, there may be exceptions depending on your machine.
12 |
13 | ## 1. Installing Cairo
14 |
15 | The Cairo documentation says:
16 |
17 | We recommend working inside a virtual python environment, but you can also install the Cairo package directly. To create and enter the virtual environment, type:
18 |
19 | ```
20 | python3.7 -m venv ~/cairo_venv
21 | source ~/cairo_venv/bin/activate
22 | ```
23 |
24 | Make sure venv is enabled; you should see (cairo_venv) on the command line.
25 |
26 | Make sure you can install the following pip packages: ecdsa, fastecdsa, sympy (using pip3 install ecdsa fastecdsa sympy). On Ubuntu, for example, you'll first need to run: sudo apt install -y libgmp3-dev.
27 |
28 | On Mac, you can use brew:
29 | ```
30 | brew install gmp.
31 | ```
32 |
33 | Install the cairo-lang python package using:
34 |
35 | ```
36 | pip3 install cairo-lang
37 | ```
38 |
39 | If all went well with these instructions, great 🥳. It is very likely that this was not the case. I installed Cairo on Ubuntu and MacOS and neither installation came out right away 🙉. Don't worry. It is solve.
40 |
41 | ### 1.1. MacOS
42 |
43 | If you're using MacOS you'll probably have trouble installing `gmp` with `brew install gmp`.
44 |
45 | This answer to an issue in the Nile repository has four different ways to fix it:
46 |
47 | I used this code in my terminal and it worked.
48 |
49 | ```
50 | CFLAGS=-I`brew --prefix gmp`/include LDFLAGS=-L`brew --prefix gmp`/lib pip install ecdsa fastecdsa sympy
51 | ```
52 | Another very interesting article recommends installing using Docker:
53 |
54 | ```
55 | # install build tools
56 | xcode-select --install
57 |
58 | # install brew
59 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
60 |
61 | # install python3.7
62 | brew install python@3.7 git gmp
63 |
64 | # install cairo
65 | python3.7 -m venv ~/cairo_venv
66 | source ~/cairo_venv/bin/activate
67 | pip3 install ecdsa fastecdsa sympy cairo-lang
68 |
69 | # install docker: https://docs.docker.com/desktop/mac/install
70 | # pull containers
71 | docker pull shardlabs/starknet-devnet
72 | docker pull trufflesuite/ganache-cli
73 | docker pull eqlabs/pathfinder
74 |
75 | # start ganache
76 | # to see the ganache logs: docker logs -f $(docker ps | grep ganache-cli | awk '{print $1}')
77 | docker run -d --rm --network host trufflesuite/ganache-cli
78 |
79 | # start starknet-devnet
80 | # to tail ganache logs: docker logs -f $(docker ps | grep starknet-devnet | awk '{print $1}')
81 | docker run -d --rm --network host shardlabs/starknet-devnet
82 |
83 | # pathfinder
84 | # to see the pathfinder logs: docker logs -f $(docker ps | grep pathfinder | awk '{print $1}')
85 | git clone https://github.com/eqlabs/pathfinder.git
86 | cd pathfinder; docker build -t pathfinder .
87 | docker run -d --rm --network host -e RUST_LOG=info -e ETH_RPC_URL=https://mainnet.infura.io/v3/ pathfinder
88 |
89 | # cairo shortcuts
90 | # NOTE: it is assumed that you use zsh
91 | mkdir -p $HOME/cairo_libs
92 | git clone git@github.com:OpenZeppelin/cairo-contracts.git $HOME/cairo_libs
93 | ln -s $HOME/cairo_libs/cairo-contracts/src/openzeppelin $HOME/cairo_libs/openzeppelin
94 | echo 'alias cairodev="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-goerli; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.zshrc
95 | echo 'alias cairoprod="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-mainnet; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.zshrc
96 | source ~/.zshrc
97 | ```
98 |
99 | ### 1.2. Ubuntu
100 |
101 | The same article recommends the following installation for Ubuntu:
102 |
103 | ```
104 | # system setup
105 | sudo apt update && sudo apt upgrade
106 | sudo apt install -y software-properties-common git curl pkg-config build-essential libssl-dev libffi-dev libgmp3-dev
107 |
108 | # install python3.7
109 | sudo add-apt-repository ppa:deadsnakes/ppa -y
110 | sudo apt install -y python3.7 python3.7-dev python3.7-venv python3-pip
111 |
112 | # install cairo
113 | python3.7 -m venv ~/cairo_venv
114 | source ~/cairo_venv/bin/activate
115 | pip3 install ecdsa fastecdsa sympy cairo-lang
116 |
117 | # cairo shortcuts
118 | mkdir -p $HOME/cairo_libs
119 | git clone git@github.com:OpenZeppelin/cairo-contracts.git $HOME/cairo_libs
120 | ln -s $HOME/cairo_libs/cairo-contracts/src/openzeppelin $HOME/cairo_libs/openzeppelin
121 | echo 'alias cairodev="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-goerli; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.bashrc
122 | echo 'alias cairoprod="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-mainnet; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.bashrc
123 | source ~/.bashrc
124 | ```
125 |
126 | ### 1.3. Windows
127 |
128 | Since Cairo and StarkNet are only available for Ubuntu and MacOS, you would have to use the Windows Linux subsystem or a Docker.
129 |
130 | ## 2. VSCode para tu Cairo 𓀀
131 |
132 | If you type `cairo` in the VSCode plugin browser ([here](https://code.visualstudio.com/docs/editor/extension-marketplace#:~:text=You%20can%20browse%20and%20install,on%20the%20VS%20Code%20Marketplace.)) tutorial on how to install plugins) only two will appear. We are starting 🚀:
133 |
134 | Both extensions are useful.
135 |
136 | * The first, `Cairo`, was created by StarkWare.
137 | * The second, `Cairo language support for StarkNet`, was created by Eric Lau who is part of Open Zepellin.
138 |
139 | I recommend installing both in your VSCode.
140 |
141 | Now you will see that your code in Cairo looks much better, is easier to read and returns errors in real time. You don't have to wait to compile your code to see if it has errors .
142 |
143 | Excellent, your machine is ready to create with Cairo and StarkNet 🚀.
144 |
145 | ## 3. Conclusion
146 |
147 | In the next tutorial we will learn the basics of Cairo 🥳. We will use everything learned and prepared here. We're going to have even more fun.
148 |
149 | In the following tutorials we will learn more about pointers and memory management; the cairo common library; how the Cairo compiler works; and more!
150 |
151 | Any comments or improvements please comment with [@espejelomar](https://twitter.com/espejelomar) or make a PR 🌈.
--------------------------------------------------------------------------------
/tutorials/tutorials/PT/1_installation.md:
--------------------------------------------------------------------------------
1 | # Programação no Ethereum L2 (pt. 1): Instalação da Cairo e StarkNet
2 |
3 | Este é o primeiro tutorial de uma série centrada no desenvolvimento de contratos inteligentes com Cairo e a StarkNet. Aqui preparamos a nossa máquina para a programação em Cairo; no segundo e terceiro tutoriais revemos as noções básicas da programação do Cairo.
4 |
5 | 🚀 O futuro do Ethereum é hoje e já está aqui. E é apenas o começo.
6 |
7 | ---
8 |
9 | Aprenderemos a instalar Cairo nas nossas máquinas e a preparar tudo para começar a criar ❤️. Também aprenderemos comandos básicos para interagir com Cairo a partir do nosso terminal.
10 |
11 | A documentação do Cairo dá instruções muito claras. No entanto, pode haver excepções dependendo da sua máquina.
12 |
13 | ## 1. instalação do Cairo
14 |
15 | A documentação do Cairo afirma:
16 |
17 | Recomendamos trabalhar num ambiente virtual python, mas também se pode instalar directamente o pacote do Cairo. Para criar e entrar no ambiente virtual, digite:
18 |
19 | ```
20 | python3.7 -m venv ~/cairo_venv
21 | source ~/cairo_venv/bin/activate
22 | ```
23 |
24 | Certifique-se de que venv esteja ativado; você deve ver (`cairo_venv`) na linha de comando.
25 |
26 | Certifique-se de poder instalar os seguintes pacotes pip: `ecdsa`, `fastecdsa`, `sympy` (usando `pip3 install ecdsa fastecdsa sympy`). No Ubuntu, por exemplo, primeiro você precisa executar: sudo apt install -y libgmp3-dev.
27 |
28 | No Mac, você pode usar brew: `brew install gmp`.
29 |
30 | Instale o pacote python cairo-lang usando:
31 |
32 | ```
33 | pip3 install cairo-lang
34 | ```
35 |
36 | Se tudo correu bem com essas instruções, ótimo 🥳. É muito provável que não tenha sido assim. Instalei o Cairo no Ubuntu e no MacOS e nenhuma instalação funcionou na primeira vez 🙉. Não te preocupes. Se resolve.
37 |
38 | ### 1.1. MacOS
39 |
40 | Se você estiver usando MacOS, provavelmente terá problemas para instalar o `gmp` com o `brew install gmp`.
41 |
42 | Esta resposta a um issue no repositório do Nile tem quatro maneiras diferentes de corrigi-lo:
43 |
44 | Eu usei este código no meu terminal e funcionou.
45 |
46 | ```
47 | CFLAGS=-I`brew --prefix gmp`/include LDFLAGS=-L`brew --prefix gmp`/lib pip install ecdsa fastecdsa sympy
48 | ```
49 |
50 | Outro artigo muito interessante recomenda a instalação usando o Docker:
51 |
52 | ```
53 | # instale build tools
54 | xcode-select --install
55 |
56 | # instale brew
57 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
58 |
59 | # instale python3.7
60 | brew install python@3.7 git gmp
61 |
62 | # instale cairo
63 | python3.7 -m venv ~/cairo_venv
64 | source ~/cairo_venv/bin/activate
65 | pip3 install ecdsa fastecdsa sympy cairo-lang
66 |
67 | # instale docker: https://docs.docker.com/desktop/mac/install
68 | # pull containers
69 | docker pull shardlabs/starknet-devnet
70 | docker pull trufflesuite/ganache-cli
71 | docker pull eqlabs/pathfinder
72 |
73 | # comece ganache
74 | # para ver os logs do ganache: docker logs -f $(docker ps | grep ganache-cli | awk '{print $1}')
75 | docker run -d --rm --network host trufflesuite/ganache-cli
76 |
77 | # comece starknet-devnet
78 | # to tail ganache logs: docker logs -f $(docker ps | grep starknet-devnet | awk '{print $1}')
79 | docker run -d --rm --network host shardlabs/starknet-devnet
80 |
81 | # pathfinder
82 | # para ver os logs do pathfinder: docker logs -f $(docker ps | grep pathfinder | awk '{print $1}')
83 | git clone https://github.com/eqlabs/pathfinder.git
84 | cd pathfinder; docker build -t pathfinder .
85 | docker run -d --rm --network host -e RUST_LOG=info -e ETH_RPC_URL=https://mainnet.infura.io/v3/ pathfinder
86 |
87 | # cairo shortcuts
88 | # NOTA: Presume-se que você use zsh
89 | mkdir -p $HOME/cairo_libs
90 | git clone git@github.com:OpenZeppelin/cairo-contracts.git $HOME/cairo_libs
91 | ln -s $HOME/cairo_libs/cairo-contracts/src/openzeppelin $HOME/cairo_libs/openzeppelin
92 | echo 'alias cairodev="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-goerli; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.zshrc
93 | echo 'alias cairoprod="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-mainnet; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.zshrc
94 | source ~/.zshrc
95 | ```
96 |
97 | ### 1.2. Ubuntu
98 |
99 | O mesmo artigo recomenda a seguinte instalação para o Ubuntu:
100 |
101 | ```
102 | # system setup
103 | sudo apt update && sudo apt upgrade
104 | sudo apt install -y software-properties-common git curl pkg-config build-essential libssl-dev libffi-dev libgmp3-dev
105 |
106 | # instale python3.7
107 | sudo add-apt-repository ppa:deadsnakes/ppa -y
108 | sudo apt install -y python3.7 python3.7-dev python3.7-venv python3-pip
109 |
110 | # instale cairo
111 | python3.7 -m venv ~/cairo_venv
112 | source ~/cairo_venv/bin/activate
113 | pip3 install ecdsa fastecdsa sympy cairo-lang
114 |
115 | # cairo shortcuts
116 | mkdir -p $HOME/cairo_libs
117 | git clone git@github.com:OpenZeppelin/cairo-contracts.git $HOME/cairo_libs
118 | ln -s $HOME/cairo_libs/cairo-contracts/src/openzeppelin $HOME/cairo_libs/openzeppelin
119 | echo 'alias cairodev="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-goerli; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.bashrc
120 | echo 'alias cairoprod="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-mainnet; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.bashrc
121 | source ~/.bashrc
122 | ```
123 |
124 | ### 1.3. Windows
125 |
126 | Como Cairo e StarkNet estão disponíveis apenas para Ubuntu e MacOS, você teria que usar o subsistema Windows Linux ou um Docker.
127 |
128 | ## 2. VSCode para o seu Cairo 𓀀
129 |
130 | Se você digitar `cairo` no navegador do plug-in VSCode ([aqui](https://code.visualstudio.com/docs/editor/extension-marketplace#:~:text=You%20can%20browse%20and%20install , em %20the%20VS%20Code%20Marketplace.)) tutorial de instalação do plugin) apenas dois aparecerão. estamos começando🚀:
131 |
132 | Ambas as extensões são úteis.
133 |
134 | * O primeiro, 'Cairo', foi criado pela StarkWare.
135 | * O segundo, `Cairo language support for StarkNet`, foi criado por Eric Lau, que faz parte do Open Zepellin.
136 |
137 | Eu recomendo instalar ambos no seu VSCode.
138 |
139 | Agora você verá que seu código no Cairo parece muito melhor, é mais fácil de ler e retorna erros em tempo real. Você não precisa esperar para compilar seu código para ver se tem erros .
140 |
141 | Ótimo, sua equipe está pronta para criar com Cairo e StarkNet 🚀.
142 |
143 | ## 3. Conclusão
144 |
145 | Agora sim…
146 |
147 | No próximo tutorial vamos aprender o básico do Cairo . Usaremos tudo o que aprendemos e preparamos aqui. Vamos nos divertir ainda mais.
148 |
149 | Nos tutoriais a seguir, aprenderemos mais sobre pointers e gerenciamento de memória; a common library do Cairo; como funciona o compilador do Cairo; e mais!
150 |
151 | Quaisquer comentários ou melhorias, por favor, comente com [@espejelomar](https://twitter.com/espejelomar) ou faça um PR 🌈.
152 |
--------------------------------------------------------------------------------
/tutorials/tutorials/ES/1_installation.md:
--------------------------------------------------------------------------------
1 |
2 | # Programando en la L2 de Ethereum (pt. 1): Intalando Cairo y StarkNet
3 |
4 | Únete a la comunidad de habla hispana de StarkNet ([Linktree](https://linktr.ee/starknet_es). Este es el primer tutorial de una serie enfocada en el desarrollo de smart cotracts con Cairo y StarkNet. Aquí preparamos nuestro equipo para programar en Cairo; en el segundo y tercer tutorial revisamos los básicos de la programación en Cairo.
5 |
6 | 🚀 El futuro de Ethereum es hoy y ya está aquí. Y apenas es el comienzo.
7 |
8 | ---
9 |
10 | Vamos a aprender a cómo instalar Cairo en nuestras máquinas y a dejar todo listo para comenzar a crear ❤️. También aprenderemos comandos básicos para interactuar con Cairo desde nuestra terminal.
11 |
12 | La documentación de Cairo nos da instrucciones muy claras. Sin embargo, puede haber excepciones según tu máquina.
13 |
14 | ## 1. Instalando Cairo
15 |
16 | La documentación de Cairo dice:
17 |
18 | Recomendamos trabajar dentro de un entorno virtual de python, pero también puedes instalar el paquete de Cairo directamente. Para crear e ingresar al entorno virtual, escriba:
19 |
20 | python3.7 -m venv ~/cairo_venv
21 | source ~/cairo_venv/bin/activate
22 |
23 | Asegúrate de que venv esté activado; deberías ver (cairo_venv) en la línea de comando.
24 |
25 | Asegúrate de poder instalar los siguientes paquetes de pip: ecdsa, fastecdsa, sympy (usando pip3 install ecdsa fastecdsa sympy). En Ubuntu, por ejemplo, primero deberá ejecutar: sudo apt install -y libgmp3-dev.
26 |
27 | En Mac, puedes usar brew: brew install gmp.
28 |
29 | Instala el paquete de Python cairo-lang usando:
30 |
31 | pip3 install cairo-lang
32 |
33 | Si todo te salió bien con estas instrucciones, excelente 🥳. Es muy probable que no haya sido así. Yo instalé Cairo en Ubuntu y en MacOS y ninguna instalación salió a la primera 🙉. No te preocupes. Se resuelve.
34 |
35 | ### 1.1. MacOS
36 |
37 | Si estás usando MacOS es probable que tengas problemas para instalar `gmp` con `brew install gmp`. Esta respuesta a un issue en el repositorio de Nile tiene cuatro formas diferentes de solucionarlo:
38 |
39 | Yo usé este código en mi terminal y funcionó.
40 |
41 | ```
42 | CFLAGS=-I`brew --prefix gmp`/include LDFLAGS=-L`brew --prefix gmp`/lib pip install ecdsa fastecdsa sympy
43 | ```
44 |
45 | Otro artículo muy interesante recomienda instalar usando Docker:
46 |
47 | ```
48 | # instala build tools
49 | xcode-select --install
50 |
51 | # instala brew
52 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
53 |
54 | # instala python3.7
55 | brew install python@3.7 git gmp
56 |
57 | # instala cairo
58 | python3.7 -m venv ~/cairo_venv
59 | source ~/cairo_venv/bin/activate
60 | pip3 install ecdsa fastecdsa sympy cairo-lang
61 |
62 | # instala docker: https://docs.docker.com/desktop/mac/install
63 | # pull containers
64 | docker pull shardlabs/starknet-devnet
65 | docker pull trufflesuite/ganache-cli
66 | docker pull eqlabs/pathfinder
67 |
68 | # comienza ganache
69 | # para ver los logs de ganache: docker logs -f $(docker ps | grep ganache-cli | awk '{print $1}')
70 | docker run -d --rm --network host trufflesuite/ganache-cli
71 |
72 | # comienza starknet-devnet
73 | # to tail ganache logs: docker logs -f $(docker ps | grep starknet-devnet | awk '{print $1}')
74 | docker run -d --rm --network host shardlabs/starknet-devnet
75 |
76 | # pathfinder
77 | # para ver los logs de pathfinder: docker logs -f $(docker ps | grep pathfinder | awk '{print $1}')
78 | git clone https://github.com/eqlabs/pathfinder.git
79 | cd pathfinder; docker build -t pathfinder .
80 | docker run -d --rm --network host -e RUST_LOG=info -e ETH_RPC_URL=https://mainnet.infura.io/v3/ pathfinder
81 |
82 | # cairo shortcuts
83 | # NOTA: se asume que usas zsh
84 | mkdir -p $HOME/cairo_libs
85 | git clone git@github.com:OpenZeppelin/cairo-contracts.git $HOME/cairo_libs
86 | ln -s $HOME/cairo_libs/cairo-contracts/src/openzeppelin $HOME/cairo_libs/openzeppelin
87 | echo 'alias cairodev="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-goerli; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.zshrc
88 | echo 'alias cairoprod="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-mainnet; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.zshrc
89 | source ~/.zshrc
90 | ```
91 |
92 | ### 1.2. Ubuntu
93 |
94 | El mismo artículo recomienda la siguiente instalación para Ubuntu:
95 |
96 | ```
97 | # system setup
98 | sudo apt update && sudo apt upgrade
99 | sudo apt install -y software-properties-common git curl pkg-config build-essential libssl-dev libffi-dev libgmp3-dev
100 |
101 | # instala python3.7
102 | sudo add-apt-repository ppa:deadsnakes/ppa -y
103 | sudo apt install -y python3.7 python3.7-dev python3.7-venv python3-pip
104 |
105 | # instala cairo
106 | python3.7 -m venv ~/cairo_venv
107 | source ~/cairo_venv/bin/activate
108 | pip3 install ecdsa fastecdsa sympy cairo-lang
109 |
110 | # cairo shortcuts
111 | mkdir -p $HOME/cairo_libs
112 | git clone git@github.com:OpenZeppelin/cairo-contracts.git $HOME/cairo_libs
113 | ln -s $HOME/cairo_libs/cairo-contracts/src/openzeppelin $HOME/cairo_libs/openzeppelin
114 | echo 'alias cairodev="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-goerli; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.bashrc
115 | echo 'alias cairoprod="python3.7 -m venv ~/cairo_venv; source ~/cairo_venv/bin/activate; export STARKNET_NETWORK=alpha-mainnet; export CAIRO_PATH=~/cairo_libs; export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount"' >> ~/.bashrc
116 | source ~/.bashrc
117 | ```
118 |
119 | ### 1.3. Windows
120 |
121 | Debido a que Cairo y StarkNet solo se encuentran disponibles para Ubuntu y MacOS, tendrías que utilizar el subsistema Linux de Windows o un Docker.
122 |
123 | ## 2. VSCode para tu Cairo 𓀀
124 |
125 | Si escribes `cairo` en el buscador de plugins de VSCode ([aquí](https://code.visualstudio.com/docs/editor/extension-marketplace#:~:text=You%20can%20browse%20and%20install,on%20the%20VS%20Code%20Marketplace.)) tutorial sobre cómo instalar plugins) te aparecerán solo dos. Estamos comenzando 🚀:
126 |
127 | Ambas extensiones son útiles.
128 |
129 | * La primera, `Cairo`, fue creada por StarkWare.
130 |
131 | * La segunda, `Cairo language support for StarkNet`, fue creada por Eric Lau quien es parte de Open Zepellin.
132 |
133 | Recomiendo instalar ambas en tu VSCode.
134 |
135 | Ahora verás que tu código en Cairo se ve mucho mejor, es más fácil de leer y te retorna errores en tiempo real. No tienes que esperar a compilar tu código para observar si tiene errores 🥳.
136 |
137 | Excelente, tu equipo está listo para crear con Cairo y StarkNet 🚀.
138 |
139 |
140 | ## 3. Conclusión
141 |
142 | Ahora sí…
143 |
144 | En el siguiente tutorial aprenderemos los básicos de Cairo 🥳. Usaremos todo lo aprendido y preparado aquí. Nos vamos a divertir aún más.
145 |
146 | En los siguientes tutoriales aprenderemos más sobre los pointers y el manejo de la memoria; la common library de cairo; cómo funciona el compilador de Cairo; y más!
147 |
148 | Cualquier comentario o mejora por favor comentar con [@espejelomar](https://twitter.com/espejelomar) o haz un PR 🌈.
149 |
--------------------------------------------------------------------------------
/src/interfaces/IERC721Random.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.uint256 import Uint256
4 |
5 | @contract_interface
6 | namespace IExerciseSolution {
7 | // Breeding function
8 | func owner() -> (owner: felt) {
9 | }
10 | func get_last_tokenID() -> (tokenID: Uint256) {
11 | }
12 | func register_me_as_breeder() -> (is_added: felt) {
13 | }
14 | func declare_animal(sex: felt, legs: felt, wings: felt) -> (token_id: Uint256) {
15 | }
16 | func get_animal_characteristics(token_id: Uint256) -> (sex: felt, legs: felt, wings: felt) {
17 | }
18 | func token_of_owner_by_index(account: felt, index: felt) -> (token_id: Uint256) {
19 | }
20 | func declare_dead_animal(token_id: Uint256) {
21 | }
22 | }
23 |
24 |
25 |
26 | // ------
27 | // Getters
28 | // ------
29 |
30 | @view
31 | func owner{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (owner: felt) {
32 | return Ownable.owner();
33 | }
34 |
35 | @view
36 | func ownerOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(tokenId: Uint256) -> (
37 | owner: felt
38 | ) {
39 | return ERC721.owner_of(tokenId);
40 | }
41 |
42 | @view
43 | func get_last_tokenID{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (tokenID: Uint256) {
44 | let last_token : Uint256 = token_counter.read();
45 | return (tokenID=last_token);
46 | }
47 |
48 | @view
49 | func get_random_number{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(tokenID: Uint256) -> (number: felt) {
50 | let number : felt = tokenID_to_random_number.read(tokenID);
51 | return (number=number);
52 | }
53 |
54 | @view
55 | func tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
56 | token_id: Uint256
57 | ) -> (token_uri_len: felt, token_uri: felt*) {
58 | let random_number : felt = tokenID_to_random_number.read(token_id);
59 | let (token_uri_len, token_uri) = ERC721_Metadata_tokenURI(token_id, random_number);
60 | return (token_uri_len=token_uri_len, token_uri=token_uri);
61 | }
62 |
63 | // ------
64 | // Constant Functions: non state-changing functions
65 | // ------
66 |
67 | func ERC721_Metadata_tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
68 | token_id: Uint256, random_number: felt
69 | ) -> (token_uri_len: felt, token_uri: felt*) {
70 | alloc_locals;
71 |
72 | let exists = ERC721._exists(token_id);
73 | assert exists = 1;
74 |
75 | // TODO XXX Might be missing to read the storage with the base_token_uri
76 | let (local base_token_uri) = alloc();
77 | let (local base_token_uri_len) = erc721_baseURI_len.read();
78 |
79 | // Save in storage the URI base
80 | _store_base_tokenURI(base_token_uri_len, base_token_uri);
81 |
82 | // let (token_id_ss_len, token_id_ss) = uint256_to_ss(token_id);
83 |
84 | // Concatenate in an array the URI's base len, and base, the token_id's len and body
85 | let (local number) = alloc();
86 | [number] = random_number;
87 |
88 | let (token_uri_temp, token_uri_len_temp) = concat_arr(
89 | base_token_uri_len, base_token_uri, 1, number
90 | );
91 |
92 | // Store in suffix the array containing the suffix
93 | let (ERC721_base_token_uri_suffix_local) = erc721_base_tokenURI_suffix.read();
94 | let (local suffix) = alloc();
95 | [suffix] = ERC721_base_token_uri_suffix_local;
96 |
97 | // Concatenate the previous array now with the suffix
98 | let (token_uri, token_uri_len) = concat_arr(token_uri_len_temp, token_uri_temp, 1, suffix);
99 |
100 | return (token_uri_len=token_uri_len, token_uri=token_uri);
101 | }
102 |
103 |
104 | // set the random value corresponding to the NFT as URI
105 | // @external
106 | // func setTokenURI{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
107 | // tokenId: Uint256, tokenURI: felt
108 | // ) {
109 | // Ownable.assert_only_owner();
110 | // ERC721._set_token_uri(tokenId, tokenURI);
111 | // return ();
112 | // }
113 |
114 | func create_random_number{syscall_ptr: felt*, range_check_ptr}(rng: felt) -> (roll: felt) {
115 | // Take the lower 128 bits of the random string
116 | let (_, low) = split_felt(rng);
117 | let (_, number) = unsigned_div_rem(low, 3);
118 | return (number + 1,);
119 | }
120 |
121 | // ------
122 | // Non-Constant Functions: state-changing functions
123 | // ------
124 |
125 |
126 | // Save in storage all the base and suffix of the token URI
127 | func set_base_tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
128 | token_uri_len: felt, token_uri: felt*, token_uri_suffix: felt
129 | ) {
130 |
131 | // store the baseURI string into the erc721_baseURI storage variable
132 | _store_base_tokenURI(token_uri_len, token_uri);
133 |
134 | // store the baseURI string length the erc721_baseURI_len storage variable
135 | erc721_baseURI_len.write(token_uri_len);
136 |
137 | // store the tokenURI suffix (e.g. json) in the erc721_base_tokenURI_suffix storage variable
138 | erc721_base_tokenURI_suffix.write(token_uri_suffix);
139 | return ();
140 | }
141 |
142 | // Store the value of an array into an storage variable
143 | func _store_base_tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
144 | token_uri_len: felt, token_uri: felt*
145 | ) {
146 | if (token_uri_len == 0) {
147 | return ();
148 | }
149 | // At position "token_uri_len" of the array "token_uri" store [token_uri]
150 | erc721_baseURI.write(token_uri_len, [token_uri]);
151 | _store_base_tokenURI(token_uri_len=token_uri_len - 1, token_uri=token_uri + 1);
152 | return ();
153 | }
154 |
155 | @external
156 | func create_collectible{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
157 | // password: felt
158 | ) {
159 |
160 | // with_attr error_message("Create Collectible: Wrong password") {
161 | // assert PASSWORD = password;
162 | // }
163 |
164 | let (requestID: felt) = request_rng();
165 | let (caller_address: felt) = get_caller_address();
166 |
167 | request_id_to_sender.write(requestID, caller_address);
168 | // request_id_to_tokenURI.write(requestID, tokenURI);
169 | return();
170 | }
171 |
172 | // @external
173 | func request_rng{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
174 | request_id: felt
175 | ) {
176 | let (oracle) = oracle_address.read();
177 | let (_beacon_address) = beacon_address.read();
178 | let (request_id) = IRNGOracle.request_rng(
179 | contract_address=oracle, beacon_address=_beacon_address
180 | );
181 | rn_request_id.write(request_id);
182 | return (request_id,);
183 | }
184 |
185 | // Function called by the oracle
186 | @external
187 | func will_recieve_rng{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
188 | rng: BigInt3, request_id: felt
189 | ) {
190 | // Assert the caller is the oracle
191 | let (oracle) = oracle_address.read();
192 | let (caller_address) = get_caller_address();
193 | assert oracle = caller_address;
194 |
195 | // Get new nft owner address and the tokenURI for this random request
196 | // Each nft owner has a random request map to they
197 | let (nft_owner: felt) = request_id_to_sender.read(request_id);
198 | // let (tokenURI: felt) = request_id_to_tokenURI.read(request_id);
199 | // xxx replace, this is while we set URI
200 | let tokenURI: felt = 2;
201 |
202 | // Update new tokenID
203 | let last_token : Uint256 = token_counter.read();
204 | let one_uint : Uint256 = Uint256(1,0);
205 | let (new_tokenID, _ ) = uint256_add(a=last_token, b=one_uint);
206 | token_counter.write(new_tokenID);
207 |
208 | // Mint to nft owner and set the URI of the tokenID
209 | ERC721._mint(to=nft_owner, token_id=new_tokenID);
210 | ERC721._set_token_uri(token_id=new_tokenID, token_uri=tokenURI);
211 |
212 | // Set random number corresponding to the tokenID
213 | let (random_number) = create_random_number(rng.d0);
214 | tokenID_to_random_number.write(new_tokenID, random_number);
215 |
216 | // Save random number obtained
217 | // rn_number.write(random_number);
218 |
219 | // Map request id to tokenID
220 | request_id_to_tokenId.write(request_id, new_tokenID);
221 |
222 | // rng_request_resolved.emit(rng, request_id, roll);
223 | return ();
224 | }
--------------------------------------------------------------------------------
/src/voting.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.cairo_builtins import HashBuiltin
4 | from starkware.starknet.common.syscalls import get_caller_address
5 | from starkware.cairo.common.math import assert_not_zero
6 |
7 | from openzeppelin.access.ownable.library import Ownable
8 | from openzeppelin.security.pausable.library import Pausable
9 |
10 | // ------
11 | // Structs
12 | // ------
13 |
14 | // struct that carries the status of the vote
15 | struct VoteCounting {
16 | votes_yes: felt,
17 | votes_no: felt,
18 | }
19 |
20 | // struct indicating whether a voter is allowed to vote
21 | struct VoterInfo {
22 | allowed: felt,
23 | }
24 |
25 | // ------
26 | // Storage
27 | // ------
28 |
29 | // storage variable that takes no arguments and returns the current status of the vote
30 | @storage_var
31 | func voting_status() -> (res: VoteCounting) {
32 | }
33 |
34 | // storage variable that receives an address and returns the information of that voter
35 | @storage_var
36 | func voter_info(user_address: felt) -> (res: VoterInfo) {
37 | }
38 |
39 | // storage variable that receives an address and returns if an address is registered as a voter
40 | @storage_var
41 | func registered_voter(address: felt) -> (is_registered: felt) {
42 | }
43 |
44 | // ------
45 | // Constructor
46 | // ------
47 |
48 | // @dev Initialize contract
49 | // @implicit syscall_ptr (felt*)
50 | // @implicit pedersen_ptr (HashBuiltin*)
51 | // @implicit range_check_ptr
52 | // @param admin_address (felt): admin can pause and resume the voting
53 | // @param registered_voters_len (felt): number of registered voters
54 | // @param registered_addresses_len (felt*): array with the addresses of registered voters
55 | @constructor
56 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
57 | admin_address: felt, registered_addresses_len: felt, registered_addresses: felt*
58 | ) {
59 | alloc_locals;
60 | Ownable.initializer(admin_address);
61 | _register_voters(registered_addresses_len, registered_addresses);
62 | return ();
63 | }
64 |
65 | // ------
66 | // Getters
67 | // ------
68 |
69 | // @dev Return address of the admin of the vote
70 | // @implicit syscall_ptr (felt*)
71 | // @implicit pedersen_ptr (HashBuiltin*)
72 | // @implicit range_check_ptr
73 | // @return owner (felt): felt address of the admin
74 | @view
75 | func admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (owner: felt) {
76 | return Ownable.owner();
77 | }
78 |
79 | // @dev Return if the voting is paused
80 | // @implicit syscall_ptr (felt*)
81 | // @implicit pedersen_ptr (HashBuiltin*)
82 | // @implicit range_check_ptr
83 | // @return status (VoteCounting): 1 if paused; 0 if not
84 | @view
85 | func paused{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (paused: felt) {
86 | return Pausable.is_paused();
87 | }
88 |
89 | // @dev Return the voting status
90 | // @implicit syscall_ptr (felt*)
91 | // @implicit pedersen_ptr (HashBuiltin*)
92 | // @implicit range_check_ptr
93 | // @return status (VoteCounting): current status of the vote
94 | @view
95 | func get_voting_status{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
96 | status: VoteCounting
97 | ) {
98 | let (status) = voting_status.read();
99 | return (status=status);
100 | }
101 |
102 | // @dev Returns the status of a voter
103 | // @implicit syscall_ptr (felt*)
104 | // @implicit pedersen_ptr (HashBuiltin*)
105 | // @implicit range_check_ptr
106 | // @return status (VoterInfo): current status of a voter
107 | @view
108 | func get_voter_status{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
109 | user_address: felt
110 | ) -> (status: VoterInfo) {
111 | let (status) = voter_info.read(user_address);
112 | return (status=status);
113 | }
114 |
115 | // @dev Return if an address is a voter
116 | // @implicit syscall_ptr (felt*)
117 | // @implicit pedersen_ptr (HashBuiltin*)
118 | // @implicit range_check_ptr
119 | // @param address (felt): address of possible voter
120 | // @return is_voter (felt): 1 it is a voter; 0 if is not
121 | @view
122 | func is_voter_registered{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
123 | address: felt
124 | ) -> (is_voter_registered: felt) {
125 | let (registered) = registered_voter.read(address);
126 | return (is_voter_registered=registered);
127 | }
128 |
129 | // ------
130 | // Constant Functions: non state-changing functions
131 | // ------
132 |
133 | // @dev Check if the caller is allowed to vote
134 | // @implicit syscall_ptr (felt*)
135 | // @implicit pedersen_ptr (HashBuiltin*)
136 | // @implicit range_check_ptr
137 | // @param info (VoterInfo): struct indicating whether the voter is allowed to vote
138 | func _assert_allowed{syscall_ptr: felt*, range_check_ptr}(info: VoterInfo) {
139 | with_attr error_message("VoterInfo: Your address is not allowed to vote.") {
140 | assert_not_zero(info.allowed);
141 | }
142 |
143 | return ();
144 | }
145 |
146 | // ------
147 | // Non-Constant Functions: state-changing functions
148 | // ------
149 |
150 | // @dev Internal function to prepare the list of voters
151 | // @implicit syscall_ptr (felt*)
152 | // @implicit pedersen_ptr (HashBuiltin*)
153 | // @implicit range_check_ptr
154 | // @param registered_addresses_len (felt): number of registered voters
155 | // @param registered_addresses (felt*): array with the addresses of registered voters
156 | func _register_voters{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
157 | registered_addresses_len: felt, registered_addresses: felt*
158 | ) {
159 | // No more voters, recursion ends
160 | if (registered_addresses_len == 0) {
161 | return ();
162 | }
163 |
164 | // Assign the voter at address 'registered_addresses[registered_addresses_len - 1]' a VoterInfo struct
165 | // indicating that they have not yet voted and can do so
166 | let votante_info = VoterInfo(allowed=1);
167 | registered_voter.write(registered_addresses[registered_addresses_len - 1], 1);
168 | voter_info.write(registered_addresses[registered_addresses_len - 1], votante_info);
169 |
170 | // Go to next voter, we use recursion
171 | return _register_voters(registered_addresses_len - 1, registered_addresses);
172 | }
173 |
174 | // @dev Given a vote, it checks if the caller can vote and updates the status of the vote, and the status of the voter
175 | // @implicit syscall_ptr (felt*)
176 | // @implicit pedersen_ptr (HashBuiltin*)
177 | // @implicit range_check_ptr
178 | // @param vote (felt): vote 0 or 1
179 | @external
180 | func vote{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(vote: felt) -> () {
181 | alloc_locals;
182 | Pausable.assert_not_paused();
183 |
184 | // Know if a voter has already voted and continue if they have not voted
185 | let (caller) = get_caller_address();
186 | let (info) = voter_info.read(caller);
187 | _assert_allowed(info);
188 |
189 | // Mark that the voter has already voted and update in the storage
190 | let info_actualizada = VoterInfo(allowed=0);
191 | voter_info.write(caller, info_actualizada);
192 |
193 | // Update the vote count with the new vote
194 | let (status) = voting_status.read();
195 | local updated_voting_status: VoteCounting;
196 | if (vote == 0) {
197 | assert updated_voting_status.votes_no = status.votes_no + 1;
198 | assert updated_voting_status.votes_yes = status.votes_yes;
199 | }
200 | if (vote == 1) {
201 | assert updated_voting_status.votes_no = status.votes_no;
202 | assert updated_voting_status.votes_yes = status.votes_yes + 1;
203 | }
204 | voting_status.write(updated_voting_status);
205 | return ();
206 | }
207 |
208 | // @dev Pause voting for security reasons. Only the admin can pause the vote
209 | // @implicit syscall_ptr (felt*)
210 | // @implicit pedersen_ptr (HashBuiltin*)
211 | // @implicit range_check_ptr
212 | @external
213 | func pause{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
214 | Ownable.assert_only_owner();
215 | Pausable._pause();
216 | return ();
217 | }
218 |
219 | // @dev Resume voting. Only the admin can resume voting
220 | // @implicit syscall_ptr (felt*)
221 | // @implicit pedersen_ptr (HashBuiltin*)
222 | // @implicit range_check_ptr
223 | @external
224 | func unpause{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
225 | Ownable.assert_only_owner();
226 | Pausable._unpause();
227 | return ();
228 | }
229 |
--------------------------------------------------------------------------------
/src/randomERC721.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 |
4 | from starkware.cairo.common.uint256 import Uint256, uint256_add
5 | from starkware.starknet.common.syscalls import get_caller_address
6 | from starkware.cairo.common.cairo_builtins import HashBuiltin
7 | from starkware.cairo.common.math import unsigned_div_rem, split_felt
8 | from starkware.cairo.common.cairo_secp.bigint import BigInt3
9 | from starkware.cairo.common.alloc import alloc
10 |
11 |
12 | from openzeppelin.access.ownable.library import Ownable
13 | from openzeppelin.introspection.erc165.library import ERC165
14 | from openzeppelin.token.erc721.library import ERC721
15 |
16 | from src.interfaces.IRNGOracle import IRNGOracle
17 | from src.utils.array import concat_arr
18 | from src.utils.metadata_utils import set_base_tokenURI, ERC721_Metadata_tokenURI
19 |
20 | // ------
21 | // Constants
22 | // ------
23 |
24 | // const PASSWORD = 654;
25 |
26 | // ------
27 | // Storage
28 | // ------
29 |
30 | @storage_var
31 | func oracle_address() -> (addr: felt) {
32 | }
33 |
34 | @storage_var
35 | func beacon_address() -> (address: felt) {
36 | }
37 |
38 | @storage_var
39 | func rn_request_id() -> (id: felt) {
40 | }
41 |
42 | @storage_var
43 | func request_id_to_tokenId(rn_request_id: felt) -> (tokenId: Uint256) {
44 | }
45 |
46 | @storage_var
47 | func request_id_to_sender(rn_request_id: felt) -> (address: felt) {
48 | }
49 |
50 | @storage_var
51 | func request_id_to_tokenURI(rn_request_id: felt) -> (tokenURI: felt) {
52 | }
53 |
54 | @storage_var
55 | func token_counter() -> (number: Uint256) {
56 | }
57 |
58 | @storage_var
59 | func tokenID_to_random_number(tokenID: Uint256) -> (number: felt) {
60 | }
61 |
62 | // ------
63 | // Constructor
64 | // ------
65 |
66 | @constructor
67 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
68 | oracle_addr: felt, _beacon_address: felt
69 | ) {
70 | alloc_locals;
71 | // "L2 coleccion octubre"
72 | let name = 435001157271749608490168255869766614643032355429;
73 | // "BBB"
74 | let symbol = 4342338;
75 | let owner = 1268012686959018685956609106358567178896598707960497706446056576062850827536;
76 |
77 | let base_token_uri_len = 3;
78 | let (base_token_uri: felt*) = alloc();
79 | // "https://gateway.pinata.cloud/ip"
80 | assert base_token_uri[0] = 184555836509371486644298270517380613565396767415278678887948391494588524912;
81 | // "fs/QmZLkgkToULVeKdbMic3XsepXj2X"
82 | assert base_token_uri[1] = 181013377130050200990509839903581994934108262384437805722120074606286615128;
83 | // "xxMukhUAUEYzBEBDMV/"
84 | assert base_token_uri[2] = 2686569255955106314754156739605748156359071279;
85 |
86 | // .jpeg
87 | let token_uri_suffix = 1199354246503;
88 |
89 | token_counter.write(Uint256(0,0));
90 | ERC721.initializer(name, symbol);
91 | Ownable.initializer(owner);
92 | // set_base_tokenURI(base_token_uri_len, base_token_uri, token_uri_suffix);
93 |
94 | // Requirements for randomness
95 | oracle_address.write(oracle_addr);
96 | beacon_address.write(_beacon_address);
97 |
98 | return ();
99 | }
100 |
101 | // ------
102 | // Getters
103 | // ------
104 |
105 | @view
106 | func owner{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (owner: felt) {
107 | return Ownable.owner();
108 | }
109 |
110 | @view
111 | func ownerOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(tokenId: Uint256) -> (
112 | owner: felt
113 | ) {
114 | return ERC721.owner_of(tokenId);
115 | }
116 |
117 | @view
118 | func get_last_tokenID{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (tokenID: Uint256) {
119 | let last_token : Uint256 = token_counter.read();
120 | return (tokenID=last_token);
121 | }
122 |
123 | @view
124 | func get_random_number{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(tokenID: Uint256) -> (number: felt) {
125 | let number : felt = tokenID_to_random_number.read(tokenID);
126 | return (number=number);
127 | }
128 |
129 | @view
130 | func tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
131 | token_id: Uint256
132 | ) -> (token_uri_len: felt, token_uri: felt*) {
133 | let random_number : felt = tokenID_to_random_number.read(token_id);
134 | let (token_uri_len, token_uri) = ERC721_Metadata_tokenURI(token_id, random_number);
135 | return (token_uri_len=token_uri_len, token_uri=token_uri);
136 | }
137 |
138 | @view
139 | func get_current_random_requestID{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (request_id: felt) {
140 | let request_id : felt = rn_request_id.read();
141 | return (request_id=request_id);
142 | }
143 |
144 | @view
145 | func get_sender_random_requestID{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (caller: felt) {
146 | let request_id : felt = rn_request_id.read();
147 | let caller : felt = request_id_to_sender.read(request_id);
148 | return (caller=caller);
149 | }
150 |
151 |
152 |
153 |
154 | // ------
155 | // Constant Functions: non state-changing functions
156 | // ------
157 |
158 |
159 |
160 | // set the random value corresponding to the NFT as URI
161 | // @external
162 | // func setTokenURI{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
163 | // tokenId: Uint256, tokenURI: felt
164 | // ) {
165 | // Ownable.assert_only_owner();
166 | // ERC721._set_token_uri(tokenId, tokenURI);
167 | // return ();
168 | // }
169 |
170 | func create_random_number{syscall_ptr: felt*, range_check_ptr}(rng: felt) -> (roll: felt) {
171 | // Take the lower 128 bits of the random string
172 | let (_, low) = split_felt(rng);
173 | let (_, number) = unsigned_div_rem(low, 10);
174 | return (number + 1,);
175 | }
176 |
177 | // ------
178 | // Non-Constant Functions: state-changing functions
179 | // ------
180 |
181 | @external
182 | func create_collectible{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
183 | // password: felt
184 | ) {
185 |
186 | // with_attr error_message("Create Collectible: Wrong password") {
187 | // assert PASSWORD = password;
188 | // }
189 |
190 | let (requestID: felt) = request_rng();
191 | let (caller_address: felt) = get_caller_address();
192 |
193 | request_id_to_sender.write(requestID, caller_address);
194 | // request_id_to_tokenURI.write(requestID, tokenURI);
195 | return();
196 | }
197 |
198 | // @external
199 | func request_rng{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
200 | request_id: felt
201 | ) {
202 | let (oracle) = oracle_address.read();
203 | let (_beacon_address) = beacon_address.read();
204 | let (request_id) = IRNGOracle.request_rng(
205 | contract_address=oracle, beacon_address=_beacon_address
206 | );
207 | rn_request_id.write(request_id);
208 | return (request_id,);
209 | }
210 |
211 | // Function called by the oracle
212 | @external
213 | func will_recieve_rng{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
214 | rng: BigInt3, request_id: felt
215 | ) {
216 | // Assert the caller is the oracle
217 | let (oracle) = oracle_address.read();
218 | let (caller_address) = get_caller_address();
219 | assert oracle = caller_address;
220 |
221 | // Get new nft owner address and the tokenURI for this random request
222 | // Each nft owner has a random request map to they
223 | let (nft_owner: felt) = request_id_to_sender.read(request_id);
224 | // let (tokenURI: felt) = request_id_to_tokenURI.read(request_id);
225 | // xxx replace, this is while we set URI
226 | // let tokenURI: felt = 2;
227 |
228 | // Update new tokenID
229 | let last_token : Uint256 = token_counter.read();
230 | let one_uint : Uint256 = Uint256(1,0);
231 | let (new_tokenID, _ ) = uint256_add(a=last_token, b=one_uint);
232 | token_counter.write(new_tokenID);
233 |
234 | // Mint to nft owner and set the URI of the tokenID
235 | ERC721._mint(to=nft_owner, token_id=new_tokenID);
236 | // ERC721._set_token_uri(token_id=new_tokenID, token_uri=tokenURI);
237 |
238 | // Set random number corresponding to the tokenID
239 | let (random_number) = create_random_number(rng.d0);
240 | tokenID_to_random_number.write(new_tokenID, random_number);
241 |
242 | // Save random number obtained
243 | // rn_number.write(random_number);
244 |
245 | // Map request id to tokenID
246 | request_id_to_tokenId.write(request_id, new_tokenID);
247 |
248 | // rng_request_resolved.emit(rng, request_id, roll);
249 | return ();
250 | }
--------------------------------------------------------------------------------
/src/ERC721Custom.cairo:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // OpenZeppelin Contracts for Cairo v0.4.0b (token/erc721/presets/ERC721MintableBurnable.cairo)
3 |
4 | %lang starknet
5 |
6 | from starkware.cairo.common.cairo_builtins import HashBuiltin
7 | from starkware.cairo.common.uint256 import Uint256, uint256_add
8 | from starkware.starknet.common.syscalls import get_caller_address
9 | from starkware.cairo.common.math import assert_not_zero
10 |
11 | from openzeppelin.access.ownable.library import Ownable
12 | from openzeppelin.introspection.erc165.library import ERC165
13 | from openzeppelin.token.erc721.library import ERC721
14 |
15 | //
16 | // Structs
17 | //
18 | struct Animal {
19 | sex: felt,
20 | legs: felt,
21 | wings: felt,
22 | }
23 |
24 | //
25 | // Storage
26 | //
27 | @storage_var
28 | func animal_characteristics(token_id: Uint256) -> (animal: Animal) {
29 | }
30 |
31 | @storage_var
32 | func last_tokenID() -> (token_id: Uint256) {
33 | }
34 |
35 | @storage_var
36 | func breeders(address: felt) -> (is_breeder: felt) {
37 | }
38 |
39 | //
40 | // Constructor
41 | //
42 |
43 | @constructor
44 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
45 | name: felt, symbol: felt, owner: felt
46 | ) {
47 | ERC721.initializer(name, symbol);
48 | Ownable.initializer(owner);
49 | // declare last token equals 0
50 | last_tokenID.write(Uint256(0, 0));
51 | // stablish owner as a breeder
52 | breeders.write(owner, 1);
53 | return ();
54 | }
55 |
56 | //
57 | // Getters
58 | //
59 |
60 | @view
61 | func is_breeder{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(account: felt) -> (
62 | is_approved: felt
63 | ) {
64 | let is_approved: felt = breeders.read(account);
65 | return (is_approved=is_approved);
66 | }
67 |
68 | @view
69 | func get_animal_characteristics{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
70 | token_id: Uint256
71 | ) -> (sex: felt, legs: felt, wings: felt) {
72 | tempvar sex: felt;
73 | tempvar legs: felt;
74 | tempvar wings: felt;
75 |
76 | let (animal: Animal) = animal_characteristics.read(token_id=token_id);
77 |
78 | return (sex=animal.sex, legs=animal.legs, wings=animal.wings);
79 | }
80 |
81 | @view
82 | func get_last_tokenID{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
83 | tokenID: Uint256
84 | ) {
85 | let last_token: Uint256 = last_tokenID.read();
86 | return (tokenID=last_token);
87 | }
88 |
89 | @view
90 | func supportsInterface{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
91 | interfaceId: felt
92 | ) -> (success: felt) {
93 | return ERC165.supports_interface(interfaceId);
94 | }
95 |
96 | @view
97 | func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (name: felt) {
98 | return ERC721.name();
99 | }
100 |
101 | @view
102 | func symbol{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (symbol: felt) {
103 | return ERC721.symbol();
104 | }
105 |
106 | @view
107 | func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(owner: felt) -> (
108 | balance: Uint256
109 | ) {
110 | return ERC721.balance_of(owner);
111 | }
112 |
113 | @view
114 | func ownerOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(tokenId: Uint256) -> (
115 | owner: felt
116 | ) {
117 | return ERC721.owner_of(tokenId);
118 | }
119 |
120 | @view
121 | func getApproved{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
122 | tokenId: Uint256
123 | ) -> (approved: felt) {
124 | return ERC721.get_approved(tokenId);
125 | }
126 |
127 | @view
128 | func isApprovedForAll{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
129 | owner: felt, operator: felt
130 | ) -> (isApproved: felt) {
131 | let (isApproved: felt) = ERC721.is_approved_for_all(owner, operator);
132 | return (isApproved=isApproved);
133 | }
134 |
135 | @view
136 | func tokenURI{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
137 | tokenId: Uint256
138 | ) -> (tokenURI: felt) {
139 | let (tokenURI: felt) = ERC721.token_uri(tokenId);
140 | return (tokenURI=tokenURI);
141 | }
142 |
143 | @view
144 | func owner{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (owner: felt) {
145 | return Ownable.owner();
146 | }
147 |
148 | //
149 | // Externals
150 | //
151 |
152 | @external
153 | func register_me_as_breeder{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
154 | is_added: felt
155 | ) {
156 | let (new_breeder_address) = get_caller_address();
157 | breeders.write(address=new_breeder_address, value=1);
158 | return (is_added=1);
159 | }
160 |
161 | // Store in animal_characteristics characteristics of new animal
162 | // Updates last_tokenID to new ID
163 |
164 | // @dev Mints a new animal with defined characteristics. Limitation: The caller will be owner
165 | // @implicit range_check_ptr (felt)
166 | // @implicit syscall_ptr (felt*)
167 | // @implicit pedersen_ptr (HashBuiltin*)
168 | // @param sex (felt): Sex of the declared animal
169 | // @param legs (felt): Number of legs of the declared animal
170 | // @param wings (felt): Number of wings of the declared animal
171 | // @return token_id (Uint256): Token assigned to the minted animal
172 | @external
173 | func declare_animal{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
174 | sex: felt, legs: felt, wings: felt
175 | ) -> (token_id: Uint256) {
176 | alloc_locals;
177 |
178 | _assert_only_breeder();
179 |
180 | // Define the token ID corresponding to the newly minted animal
181 | let last_token: Uint256 = last_tokenID.read();
182 | let one_uint: Uint256 = Uint256(1, 0);
183 | let (local new_tokenID, _) = uint256_add(a=last_token, b=one_uint);
184 | last_tokenID.write(new_tokenID);
185 |
186 | // Register characteristics of the new animal
187 | let animal: Animal = Animal(sex=sex, legs=legs, wings=wings);
188 | animal_characteristics.write(new_tokenID, animal);
189 |
190 | // The address that declares the new animal will be owner
191 | // TODO: allow for other accounts to also declare animals (breeders)
192 | let (sender_address) = get_caller_address();
193 | ERC721._mint(to=sender_address, token_id=new_tokenID);
194 | return (token_id=new_tokenID);
195 | }
196 |
197 | @external
198 | func approve{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
199 | to: felt, tokenId: Uint256
200 | ) {
201 | ERC721.approve(to, tokenId);
202 | return ();
203 | }
204 |
205 | @external
206 | func setApprovalForAll{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
207 | operator: felt, approved: felt
208 | ) {
209 | ERC721.set_approval_for_all(operator, approved);
210 | return ();
211 | }
212 |
213 | @external
214 | func transferFrom{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
215 | from_: felt, to: felt, tokenId: Uint256
216 | ) {
217 | ERC721.transfer_from(from_, to, tokenId);
218 | return ();
219 | }
220 |
221 | @external
222 | func safeTransferFrom{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
223 | from_: felt, to: felt, tokenId: Uint256, data_len: felt, data: felt*
224 | ) {
225 | ERC721.safe_transfer_from(from_, to, tokenId, data_len, data);
226 | return ();
227 | }
228 |
229 | // @external
230 | // func mint{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
231 | // to: felt, tokenId: Uint256
232 | // ) {
233 | // Ownable.assert_only_owner();
234 | // ERC721._mint(to, tokenId);
235 | // return ();
236 | // }
237 |
238 | @external
239 | func burn{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(tokenId: Uint256) {
240 | ERC721.assert_only_token_owner(tokenId);
241 | ERC721._burn(tokenId);
242 | return ();
243 | }
244 |
245 | @external
246 | func setTokenURI{pedersen_ptr: HashBuiltin*, syscall_ptr: felt*, range_check_ptr}(
247 | tokenId: Uint256, tokenURI: felt
248 | ) {
249 | Ownable.assert_only_owner();
250 | ERC721._set_token_uri(tokenId, tokenURI);
251 | return ();
252 | }
253 |
254 | @external
255 | func transferOwnership{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
256 | newOwner: felt
257 | ) {
258 | Ownable.transfer_ownership(newOwner);
259 | return ();
260 | }
261 |
262 | @external
263 | func renounceOwnership{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
264 | Ownable.renounce_ownership();
265 | return ();
266 | }
267 |
268 | //
269 | // Internals
270 | //
271 |
272 | func _assert_only_breeder{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
273 | // let (owner) = Ownable.owner();
274 | let (caller) = get_caller_address();
275 | with_attr error_message("Breeder: caller is the zero address") {
276 | assert_not_zero(caller);
277 | }
278 |
279 | let breeder: felt = is_breeder(account=caller);
280 |
281 | with_attr error_message("Breeder: caller is not a registered breeder") {
282 | assert 1 = breeder;
283 | }
284 | return ();
285 | }
286 |
--------------------------------------------------------------------------------
/src/oz_modified_account.cairo:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // OpenZeppelin Cairo Contracts v0.1.0 (account/Account.cairo)
3 |
4 | %lang starknet
5 |
6 | from starkware.cairo.common.alloc import alloc
7 | from starkware.cairo.common.cairo_builtins import HashBuiltin, SignatureBuiltin
8 | from starkware.cairo.common.hash_state import (
9 | hash_finalize,
10 | hash_init,
11 | hash_update,
12 | hash_update_single,
13 | )
14 | from starkware.cairo.common.math import assert_not_equal
15 | from starkware.cairo.common.memcpy import memcpy
16 | from starkware.cairo.common.registers import get_fp_and_pc
17 | from starkware.cairo.common.signature import verify_ecdsa_signature
18 | from starkware.starknet.common.syscalls import (
19 | call_contract,
20 | deploy,
21 | get_caller_address,
22 | get_contract_address,
23 | get_tx_info,
24 | )
25 | from starkware.starknet.common.constants import ORIGIN_ADDRESS
26 | // from starkware.starknet.third_party.open_zeppelin.utils.constants import PREFIX_TRANSACTION
27 |
28 | //
29 | // Structs
30 | //
31 |
32 | // Stores info on the call we are going to do
33 | struct MultiCall {
34 | account: felt,
35 | calls_len: felt,
36 | calls: Call*,
37 | max_fee: felt,
38 | version: felt,
39 | }
40 |
41 | // XXX Call allows you to store all the relevant information for making a call to a selector function with certain calldata (arguments)
42 | struct Call {
43 | to: felt,
44 | selector: felt,
45 | calldata_len: felt,
46 | calldata: felt*,
47 | }
48 |
49 | // Tmp struct introduced while we wait for Cairo
50 | // to support passing `[Call]` to __execute__
51 | // XXX CallArray is what we send to the __EXECUTE__ function, I do not know what data_offset is. Seems it is the calldata or the offset (space) that the data occupies?
52 | // XXX each CallArray represents a single call, just as if we were doing an array of Calls. It is confusing due to the name, but is not an array of calls it is a single call
53 | // XXX likely it represents a call with an array of calldata?
54 | struct CallArray {
55 | to: felt,
56 | selector: felt,
57 | data_offset: felt,
58 | data_len: felt,
59 | }
60 |
61 | //
62 | // Storage
63 | //
64 |
65 | @storage_var
66 | func public_key() -> (res: felt) {
67 | }
68 |
69 | //
70 | // Guards
71 | //
72 |
73 | @view
74 | func assert_only_self{syscall_ptr: felt*}() {
75 | let (self) = get_contract_address();
76 | let (caller) = get_caller_address();
77 | assert self = caller;
78 | return ();
79 | }
80 |
81 | //
82 | // Getters
83 | //
84 |
85 | @view
86 | func get_public_key{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
87 | res: felt
88 | ) {
89 | let (res) = public_key.read();
90 | return (res=res);
91 | }
92 |
93 | //
94 | // Setters
95 | //
96 |
97 | @external
98 | func set_public_key{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
99 | new_public_key: felt
100 | ) {
101 | assert_only_self();
102 | public_key.write(new_public_key);
103 | return ();
104 | }
105 |
106 | //
107 | // Constructor
108 | //
109 |
110 | @constructor
111 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
112 | _public_key: felt
113 | ) {
114 | public_key.write(_public_key);
115 | return ();
116 | }
117 |
118 | //
119 | // Business logic
120 | //
121 |
122 | @view
123 | func is_valid_signature{
124 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, ecdsa_ptr: SignatureBuiltin*
125 | }(hash: felt, signature_len: felt, signature: felt*) -> () {
126 | let (_public_key) = public_key.read();
127 |
128 | // This interface expects a signature pointer and length to make
129 | // no assumption about signature validation schemes.
130 | // But this implementation does, and it expects a (sig_r, sig_s) pair.
131 | with_attr error_message("INVALID_SIGNATURE_LENGTH") {
132 | assert signature_len = 2;
133 | }
134 |
135 | let sig_r = signature[0];
136 | let sig_s = signature[1];
137 |
138 | verify_ecdsa_signature(
139 | message=hash, public_key=_public_key, signature_r=sig_r, signature_s=sig_s
140 | );
141 |
142 | return ();
143 | }
144 |
145 | @external
146 | func __validate_declare__{
147 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, ecdsa_ptr: SignatureBuiltin*
148 | }(class_hash: felt) {
149 | let (tx_info) = get_tx_info();
150 | is_valid_signature(tx_info.transaction_hash, tx_info.signature_len, tx_info.signature);
151 | return ();
152 | }
153 |
154 | @external
155 | func __validate_deploy__{
156 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, ecdsa_ptr: SignatureBuiltin*
157 | }(class_hash: felt, contract_address_salt: felt, _public_key: felt) {
158 | let (tx_info) = get_tx_info();
159 | is_valid_signature(tx_info.transaction_hash, tx_info.signature_len, tx_info.signature);
160 | return ();
161 | }
162 |
163 | @external
164 | func __validate__{
165 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, ecdsa_ptr: SignatureBuiltin*
166 | }(call_array_len: felt, call_array: CallArray*, calldata_len: felt, calldata: felt*) {
167 | let (tx_info) = get_tx_info();
168 | is_valid_signature(tx_info.transaction_hash, tx_info.signature_len, tx_info.signature);
169 | return ();
170 | }
171 |
172 | @external
173 | @raw_output
174 | func __execute__{
175 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, ecdsa_ptr: SignatureBuiltin*
176 | }(call_array_len: felt, call_array: CallArray*, calldata_len: felt, calldata: felt*) -> (
177 | retdata_size: felt, retdata: felt*
178 | ) {
179 | alloc_locals;
180 |
181 | let (__fp__, _) = get_fp_and_pc();
182 | let (tx_info) = get_tx_info();
183 |
184 | // validate caller.
185 | let (caller) = get_caller_address();
186 | with_attr error_message(
187 | "Invalid caller. This function cannot be called from another contract.") {
188 | assert caller = ORIGIN_ADDRESS;
189 | }
190 |
191 | // TMP: Convert `CallArray` to 'Call'.
192 | // XXX Create an array of `Call`s named `calls`
193 | let (calls: Call*) = alloc();
194 | // XXX Converte the CallArray array into an array of Calls where we will have the calldata too
195 | from_call_array_to_call(call_array_len, call_array, calldata, calls);
196 | // XXX Get the number of calls which is equal to the number of "CallArray"s we had.
197 | let calls_len = call_array_len;
198 | // XXX Makes a new struct MultiCall with all the info on our call
199 | local multicall: MultiCall = MultiCall(
200 | tx_info.account_contract_address,
201 | calls_len,
202 | calls,
203 | tx_info.max_fee,
204 | tx_info.version
205 | );
206 |
207 | // execute call.
208 | // XXX Executes all the calls. We send an empty array of responses which will be filled with the
209 | // XXX responses generated by the call.
210 | // XXX I think that the MultiCall was not necessary and that we could added just the calls array directly
211 | let (response: felt*) = alloc();
212 | let (response_len) = execute_list(multicall.calls_len, multicall.calls, response);
213 |
214 | return (retdata_size=response_len, retdata=response);
215 | }
216 |
217 | @external
218 | func deploy_contract{syscall_ptr: felt*}(
219 | class_hash: felt,
220 | contract_address_salt: felt,
221 | constructor_calldata_len: felt,
222 | constructor_calldata: felt*,
223 | deploy_from_zero: felt,
224 | ) -> (contract_address: felt) {
225 | assert_only_self();
226 | let (contract_address) = deploy(
227 | class_hash=class_hash,
228 | contract_address_salt=contract_address_salt,
229 | constructor_calldata_size=constructor_calldata_len,
230 | constructor_calldata=constructor_calldata,
231 | deploy_from_zero=deploy_from_zero,
232 | );
233 | return (contract_address=contract_address);
234 | }
235 |
236 | // XXX Calls all the Calls that we had stored in calls
237 | func execute_list{syscall_ptr: felt*}(calls_len: felt, calls: Call*, response: felt*) -> (
238 | response_len: felt
239 | ) {
240 | alloc_locals;
241 |
242 | // if no more calls
243 | if (calls_len == 0) {
244 | return (response_len=0);
245 | }
246 |
247 | // do the current call
248 | let this_call: Call = [calls];
249 | let res = call_contract(
250 | contract_address=this_call.to,
251 | function_selector=this_call.selector,
252 | calldata_size=this_call.calldata_len,
253 | calldata=this_call.calldata,
254 | );
255 | // copy the result in response
256 | memcpy(response, res.retdata, res.retdata_size);
257 | // do the next calls recursively
258 | let (response_len) = execute_list(
259 | calls_len - 1, calls + Call.SIZE, response + res.retdata_size
260 | );
261 | return (response_len=response_len + res.retdata_size);
262 | }
263 |
264 | // XXX Helper recursive function that receives an array of `Call`s named `calls`, and an array of
265 | // XXX `CallArray`s with its respective length and a `felt`s array called `calldata`
266 | func from_call_array_to_call{syscall_ptr: felt*}(
267 | call_array_len: felt, call_array: CallArray*, calldata: felt*, calls: Call*
268 | ) {
269 | // if no more calls
270 | if (call_array_len == 0) {
271 | return ();
272 | }
273 |
274 | // parse the current call
275 | // XXX Create a Call stored in the calls pointer by extracting the information from CallArray, in calldata we store the calldata
276 | // XXX that we had coming from the invokationand add it the data_offset fo the CallArray
277 | assert [calls] = Call(
278 | to=[call_array].to,
279 | selector=[call_array].selector,
280 | calldata_len=[call_array].data_len,
281 | calldata=calldata + [call_array].data_offset
282 | );
283 |
284 | // parse the remaining calls recursively
285 | // XXX Then we go the next CallArray by adding to call_array pointer CallArray.SIZE, and do the same to the calls array
286 | // XXX notice the calldata remains the same.
287 | from_call_array_to_call(
288 | call_array_len - 1, call_array + CallArray.SIZE, calldata, calls + Call.SIZE
289 | );
290 | return ();
291 | }
--------------------------------------------------------------------------------
/tutorials/tutorials/EN/4_protostar.md:
--------------------------------------------------------------------------------
1 | # Programming on the Ethereum's L2 (pt. 4): Protostar and deploying contracts
2 |
3 | Before starting, I recommend that you prepare your machine to program in Cairo ❤️ with the [first tutorial](1_installation.md), and review the [Cairo Basics pt. 1](2_cairo_basics.md) and [pt. 2](3_cairo_basics.md).
4 |
5 | This is the fourth tutorial in a series focused on developing smart contracts with Cairo and StarkNet.
6 |
7 | 🚀 The future of Ethereum is today and it's already here. And it's just the beginning.
8 |
9 | ---
10 |
11 | > “StarkNet is a permissionless decentralized ZK-Rollup operating as an L2 network over Ethereum, where any dApp can achieve unlimited scale for its computation, without compromising Ethereum’s composability and security.” - [StarkNet Documentation](https://starknet.io/docs/hello_starknet/index.html#hello-starknet).
12 |
13 | Congratulations! 🚀 We already have an intermediate level from Cairo. Cairo is to StarkNet what Solidity is to Ethereum. It's time to deploy our contracts on StarkNet. We will also learn how to use [Protostar](https://github.com/software-mansion/protostar), tool inspired by [Foundry](https://github.com/foundry-rs/foundry) and key to compile, tests and deploy.
14 |
15 | We are currently able to operate with the StarkNet Alpha. The recommended steps for deploying contracts are:
16 |
17 | 1. Unit tests - Protostar.
18 | 2. Devnet - [Shard Lab’s](https://github.com/Shard-Labs/starknet-devnet) `starknet-devnet` (reviewed here).
19 | 3. Testnet - Alpha Goerli, `alpha-goerli` (reviewed here).
20 | 4. Mainnet - Alpha StarkNet, `alpha-mainnnet`.
21 |
22 | In this tutorial we will learn how to deploy contracts to the devnet and the testnet. In a following text we will learn how to create unit tests with Protostar; and to interact with the devnet and testnet.
23 |
24 | Let's get started!
25 |
26 | ---
27 |
28 | ## 1. Installing Protostar
29 |
30 | At this point we already have `cairo-lang` installed. If not, you can check [our tutorial](1_installation.md) about how to install it.
31 |
32 | On Ubuntu or MacOS (not available for Windows) run the following command:
33 |
34 | `curl -L https://raw.githubusercontent.com/software-mansion/protostar/master/install.sh | bash`
35 |
36 | Restart your terminal and run `protostar -v` to see the version of your `protostar` and `cairo-lang`.
37 |
38 | If you later want to upgrade your protostar use `protostar upgrade`. If you run into installation problems I recommend that you review the [Protostar documentation](https://docs.swmansion.com/protostar/docs/tutorials/installation).
39 |
40 | ## 2. First steps with Protostar
41 |
42 | What does it mean to initialize a project with Protostar?
43 |
44 | - **Git**. A new directory (folder) will be created which will be a git repository (it will have a `.git` file).
45 | - `protostar.toml`. Here we will have information necessary to configure our project. Do you know Python? Well you know where we're going with this.
46 | - **Three src directories will be created** (where will your code be), lib (for external dependencies), and tests (where the tests will be).
47 |
48 | You can initialize your project with the `protostar init` command, or you can indicate that an existing project will use Protostar with `protostar init --existing`. Basically, you just need your directory to be a git repository and have a `protostar.toml` file with the project settings. We could even create the `protostar.toml` file ourselves from our text editor.
49 |
50 | Let's run `protostar init` to initialize a Protostar project. It asks us to indicate two things:
51 |
52 | - `project directory name`: What is the name of the directory where your project is located?
53 | - `libraries directory name`: What is the name of the directory where external dependencies will be installed?
54 |
55 | This is what the structure of our project looks like:
56 |
57 | ```
58 | ❯ tree -L 2
59 | .
60 | ├── lib
61 | ├── protostar.toml
62 | ├── src
63 | │ └── main.cairo
64 | └── tests
65 | └── test_main.cairo
66 | ```
67 |
68 | - Initially, information about the version of protostar used is found here `[“protostar.config“]`, where the external libraries used will be found.
69 |
70 | ## 3. Installing external dependencies (libraries)
71 |
72 | Protostar uses git submodules to install external dependencies. Soon it will be done with a package manager. Let's install a couple of dependencies.
73 |
74 | Installing `cairo-contracts` we indicate the repository where they are, that is, [github.com/OpenZeppelin/cairo-contracts](http://github.com/OpenZeppelin/cairo-contracts). Let's use `protostar install`:
75 |
76 | `protostar install https://github.com/OpenZeppelin/cairo-contracts`
77 |
78 | Let's install one more dependency, `cairopen_contracts`:
79 |
80 | `protostar install https://github.com/CairOpen/cairopen-contracts`
81 |
82 | Our new dependencies are stored in the `lib` directory:
83 |
84 | ```
85 | ❯ tree -L 2
86 | .
87 | ├── lib
88 | │ ├── cairo_contracts
89 | │ └── cairopen_contracts
90 | ├── protostar.toml
91 | ├── src
92 | │ └── main.cairo
93 | └── tests
94 | └── test_main.cairo
95 | ```
96 |
97 | Finally, add the following section to `protostar.toml`:
98 |
99 | ```
100 | ["protostar.shared_command_configs"]
101 | cairo-path = ["lib/cairo_contracts/src", "lib/cairopen_contracts/src"]
102 | ```
103 |
104 | This allows Protostar to use those paths to find the libraries of interest. When you import, for example `from openzeppelin.access.ownable.library import Ownable`, Protostar will look for `Ownable` in the path `lib/cairo_contracts/src/openzeppelin/access/ownable/library`. If you change the name of the directory where you store your external dependencies then you would not use `lib` but the name of that directory.
105 |
106 | Wonderful!
107 |
108 | ## 4. Compiling
109 |
110 | In the past we have been compiling our contracts with `cairo-compile`. When we run `cairo-compile sum2Numbers.cairo --output x.json` to compile a Cairo `sum2Numbers.cairo` contract, the result is a new file in our working directory called `x.json`. The json file is used by `cairo-run` when we run our program.
111 |
112 | In Protostar we can compile all our StarkNet contracts at once with `protostar build`. But first we must indicate in the `[“protostar.contracts”]` section of `protostar.toml` the contracts we want to compile (or build). Imagine we have a `ERC721MintableBurnable.cairo` contract in our `src` folder (where the contracts are).
113 |
114 | > Note: You will not be able to compile pure Cairo code using `protostar build`. It is only for StarkNet contracts (they have the `%lang starknet` signal at the beginning). To compile and run pure Cairo applications you need to use `cairo-compile` and `cairo-run` ([see Cairo tutorial](3_cairo_basics.md). In following tutorials we will learn how to create StarkNet contracts.
115 |
116 | We put in `protostar.toml` that we want to compile the contract in `src/ERC721MintableBurnable.cairo` and that we want to call it `ERC721_original`:
117 |
118 | ```
119 | ["protostar.contracts"]
120 | ERC721_original = [
121 | "src/ERC721MintableBurnable.cairo",
122 | ]
123 | ```
124 | We run `protostar build`. We will see that we have a new `build` directory with an `ERC721MintableBurnable.json` file. This is what we were looking for!
125 |
126 | Moral: if your contract is not in the `[“protostar.contracts“]` section of `protostar.toml` it will not be compiled.
127 |
128 | At this point we can move on to deploying StarkNet contracts with Protostar.
129 |
130 | ## 5. The devnet: starknet-devnet
131 |
132 | Transactions on the testnet take time to complete so it's best to start developing and testing locally. We will use the [devnet developed by Shard Labs](https://github.com/Shard-Labs/starknet-devnet). We can think of this step as an equivalent of Ganache. That is, it emulates the testnet (alpha goerli) of StarkNet.
133 |
134 | Install using:
135 |
136 | `pip install starknet-devnet`
137 |
138 | Restart your terminal and run `starknet-devnet --version` to check that the installation was successful. Check that you have [the most up-to-date version](https://github.com/Shard-Labs/starknet-devnet/releases). If you don't have it then run `pip install --upgrade starknet-devnet`.
139 |
140 | Initialize the devnet in a separate shell (or tab) with `starknet-devnet --accounts 3 --gas-price 250 --seed 0`:
141 |
142 | ```
143 | ❯ starknet-devnet --accounts 3 --gas-price 250 --seed 0
144 | Account #0
145 | Address: 0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a
146 | Public key: 0x7e52885445756b313ea16849145363ccb73fb4ab0440dbac333cf9d13de82b9
147 | Private key: 0xe3e70682c2094cac629f6fbed82c07cd
148 |
149 | Account #1
150 | Address: 0x69b49c2cc8b16e80e86bfc5b0614a59aa8c9b601569c7b80dde04d3f3151b79
151 | Public key: 0x175666e92f540a19eb24fa299ce04c23f3b75cb2d2332e3ff2021bf6d615fa5
152 | Private key: 0xf728b4fa42485e3a0a5d2f346baa9455
153 |
154 | Account #2
155 | Address: 0x7447084f620ba316a42c72ca5b8eefb3fe9a05ca5fe6430c65a69ecc4349b3b
156 | Public key: 0x58100ffde2b924de16520921f6bfe13a8bdde9d296a338b9469dd7370ade6cb
157 | Private key: 0xeb1167b367a9c3787c65c1e582e2e662
158 |
159 | Initial balance of each account: 1000000000000000000000 WEI
160 | Seed to replicate this account sequence: 0
161 | WARNING: Use these accounts and their keys ONLY for local testing. DO NOT use them on mainnet or other live networks because you will LOSE FUNDS.
162 |
163 | * Listening on http://127.0.0.1:5050/ (Press CTRL+C to quit)
164 | ```
165 |
166 | You can run `curl http://127.0.0.1:5050/is_alive` to check if the devnet is active. If it is active you will receive `Alive!!!%` back.
167 |
168 | With this we are indicating that we will create three accounts and that the transactions will cost 250 wei per gas. We put a number in seed to have the same accounts every time we activate our devnet. These accounts are based on the code and standards developed by [Open Zepellin for Cairo](https://github.com/OpenZeppelin/cairo-contracts/tree/v0.2.1).
169 |
170 | It is key that we have at hand the address where our devnet is running. In the photo above it is: `http://127.0.0.1:5050/`. We will use it later.
171 |
172 | The interaction with the devnet and the testnet is very similar. If you want to see all the arguments available in the `starknet-devnet` call you can call `starknet-devnet --help`.
173 |
174 | ## 6. Deploying to the devnet and testnet
175 |
176 | Let's use a real example. When we initialize a Protostar project, a `main.cairo` contract is automatically created in the `src` directory. You can use it as an example of a contract to deploy to the devnet and then to the testnet. You just need to make sure that in `protostar.toml` you define what will be compiled. In this tutorial we are going to deploy a contract for an ERC721 (NFT) found in [this repository](../../../src/ERC721MintableBurnable.cairo). In `protostar.toml` we place:
177 |
178 | ```
179 | ["protostar.contracts"]
180 | ERC721_original = [
181 | "src/ERC721MintableBurnable.cairo",
182 | ]
183 | ```
184 | Run `protostar build` to compile and create the `build/ERC721_original.json` that we will use for deployment.
185 |
186 | To deploy our contracts on the devnet from Protostar we can create configuration profiles. In the `protostar.toml` we create a section `[profile.devnet.protostar.deploy]` where we put the url where we deploy our devnet locally: `gateway-url=”http://127.0.0.1:5050/”` and `chain-id="1"`. For the testnet the section would be `[profile.testnet.protostar.deploy]` and we put `network="testnet"`.
187 |
188 | ```
189 | [profile.devnet.protostar.deploy]
190 | gateway-url="http://127.0.0.1:5050/"
191 | chain-id="1"
192 |
193 | [profile.testnet.protostar.deploy]
194 | network="testnet"
195 | ```
196 |
197 | For the devnet we run:
198 |
199 | ```
200 | protostar -p devnet deploy ./build/ERC721.json --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536
201 | ```
202 |
203 | For the testnet we run:
204 |
205 | ```
206 | protostar -p testnet deploy build/ERC721_original.json --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536
207 | ```
208 |
209 | Everything is intuitive, except perhaps:
210 | - `-p` refers to the profiles we created in `protostar.toml`
211 | - `--inputs` are the constructor arguments for the contract we are deploying. Don't worry in the next tutorial we learn what a constructor is. In this case the ERC721 constructor asks us for three felts (`name`, `symbol` and `owner`).
212 |
213 | We could also deploy without the help of the profiles in `protostar.toml`. In the case of the testnet it can be efficient because we just add `--network testnet`:
214 |
215 | ```
216 | protostar deploy ./build/ERC721.json --network testnet --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536
217 | ```
218 |
219 | But in the case of the devnet we would have to add two arguments so perhaps the profiles are more convenient:
220 |
221 | ```
222 | protostar deploy ./build/ERC721.json --gateway-url "http://127.0.0.1:5050/" --chain-id "1" --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536
223 | ```
224 |
225 | When we deploy to the devnet or testnet we get the contract address and the transaction hash:
226 |
227 | ```
228 | Contract address: 0x002454c7b1f60f52a383963633dff767fd5372c43aad53028a5a8a2b9e04646d
229 | Transaction hash: 0x05a2f78261444b97b155417a5734210abe2ee1081b7f12f39f660321fd10e670
230 | ```
231 |
232 | It is important to save the contract address as we will interact with it in following functions. We will review this in other tutorials.
233 |
234 | If you deployed to the testnet you can use the contract address to interact with your contract in an block explorer: [Voyager](https://goerli.voyager.online/) or [StarkScan](https://testnet.starkscan.co/). These block explorers are equivalent to [Etherscan](https://goerli.voyager.online/) for L1.
235 |
236 | The advantage of deploying to the devnet first is that we can interact more quickly with our contracts. For the testnet we will have to wait about a few minutes.
237 |
238 | ## 7. Deploying with the `starknet` CLI
239 |
240 | Below, Protostar is using the `starknet` CLI to deploy. There are times when we don't want to fully depend on Protostar, for example when there is an update from StarkNet and it hasn't been applied to the Protostar library yet.
241 |
242 | We will explore the `starknet` CLI in more detail later. For now let's see how to deploy the exact same contract.
243 |
244 | For the devnet, once you turned it on at the gateway http://127.0.0.1:5050, it would be:
245 |
246 | ```
247 | starknet deploy --contract ./build/ERC721_original.json --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536 --gateway_url "http://127.0.0.1:5050" --no_wallet
248 | ```
249 |
250 | For the testnet:
251 |
252 | ```
253 | starknet deploy --contract ./build/ERC721_original.json --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536 --network alpha-goerli --no_wallet
254 | ```
255 |
256 | In both cases we get the contract address and the transaction hash; same as deploying with Protostar.
257 |
258 | ## 8. Conclusion
259 |
260 | Congratulations 🦾. You've just taken your first steps on StarkNet! You are taking advantage of everything learned in the past tutorials on Cairo.
261 |
262 | In the following tutorials we will learn more about creating smart contracts on StarkNet 🚀.
263 |
264 | Any comments or improvements please comment with [@espejelomar](https://twitter.com/espejelomar) 🌈.
--------------------------------------------------------------------------------
/tutorials/tutorials/PT/3_cairo_basics.md:
--------------------------------------------------------------------------------
1 | # Programação no Ethereum L2 (pt. 3): Básicas do Cairo 2
2 |
3 | Antes de começar, recomendo que você prepare sua equipe para programar no Cairo ❤️ com o [primeiro tutorial](1_installation.md), e revise o [Cairo basics pt. 1](2_cairo_basics.md).
4 |
5 | Este é o terceiro tutorial de uma série focada no desenvolvimento de contratos inteligentes com Cairo e StarkNet.
6 |
7 | ---
8 |
9 | Na terceira parte da série de tutoriais básicos do Cairo, vamos nos aprofundar nos conceitos introduzidos na [segunda sessão](2_cairo_basics.md) como `builtin`, `felt` e `assert` e suas variações. Além disso, vamos introduzir arrays. Com o que aprendemos nesta sessão poderemos criar contratos básicos no Cairo 🚀.
10 |
11 | ## 1. Builtins e sua relação com pointers
12 |
13 | No programa a seguir estamos multiplicando dois números. O código inteiro está disponível em [src/multiplication.cairo](../../../src/multiplication.cairo). Lá você encontrará o código corretamente comentado.
14 |
15 | ```python
16 | %builtins output
17 |
18 | from starkware.cairo.common.serialize import serialize_word
19 |
20 | func mult_two_nums(num1, num2) -> (prod : felt){
21 | return(prod = num1 * num2);
22 | }
23 |
24 | func main{output_ptr: felt*}(){
25 | let (prod) = mult_two_nums(2,2);
26 | serialize_word(prod);
27 | return ();
28 | }
29 | ```
30 |
31 | Lembra que introduzimos os `builtins` na última sessão junto com os argumentos implícitos?
32 |
33 | Cada `builtin` lhe dá o direito de usar um pointer que terá o nome do `builtin` + “`_ptr`”. Por exemplo, o builtin output, que definimos `%builtins output` no início do nosso contrato, nos dá o direito de usar o pointer `output_ptr`. O `range_check` embutido nos permite usar o pointer `range_check_ptr`. Esses pointers são frequentemente usados como argumentos implícitos que são atualizados automaticamente durante uma função.
34 |
35 | Na função para multiplicar dois números, usamos `%builtins output` e então usamos seu pointer ao definir main: `func main{output_ptr: felt*}():`.
36 |
37 | ## 2. Mais sobre como os felts são interessantes (raros?)
38 |
39 | > O felt é o único tipo de dado que existe no Cairo, você pode até omiti-lo [sua declaração explícita] (StarkNet Bootcamp - Amsterdam - min 1:14:36).
40 |
41 | Embora não seja necessário ser especialista nas qualidades matemáticas dos felts, é valioso saber como eles funcionam. No último tutorial, os apresentamos pela primeira vez, agora saberemos como eles afetam quando comparamos valores no Cairo.
42 |
43 | > A definição de felt, em termos terrestres (a exata está aqui): um inteiro que pode se tornar enorme (mas tem limites). Por exemplo: {...,-4,-3,-2,-1,0,+1,+2,+3,...}. Sim, inclui 0 e números negativos.
44 |
45 | Qualquer valor que não esteja dentro desse intervalo causará um "overflow": um erro que ocorre quando um programa recebe um número, valor ou variável fora do escopo de sua capacidade de manipulação ([Techopedia](https://www.techopedia . com/definition/663/overflow-error#:~:text=In%20computing%2C%20an%20overflow%20error,other%20numeric%20types%20of%20variables.)).
46 |
47 | Agora entendemos os limites do felt. Se o valor for 0,5, por exemplo, temos um overflow. Onde experimentaremos overflows com frequência? Nas divisões. O contrato a seguir (o código completo está em [src/division1.cairo](../../../src/division1.cairo)) divide 9/3, verifica com `assert` que o resultado é 3 e imprime o resultado.
48 |
49 | ```python
50 | %builtins output
51 |
52 | from starkware.cairo.common.serialize import serialize_word
53 |
54 | func main{output_ptr: felt*}(){
55 | tempvar x = 9/3;
56 | assert x = 3;
57 | serialize_word(x);
58 |
59 | return();
60 | }
61 |
62 | ```
63 |
64 | Até agora tudo faz sentido. Mas e se o resultado da divisão não for um inteiro como no contrato a seguir (o código está em [src/division2.cairo](../../../src/division2.cairo))?
65 |
66 | ```python
67 | %builtins output
68 |
69 | from starkware.cairo.common.serialize import serialize_word
70 |
71 | func main{output_ptr: felt*}(){
72 | tempvar x = 10/3;
73 | assert x = 10/3;
74 | serialize_word(x);
75 |
76 | return();
77 | }
78 |
79 | ```
80 |
81 | Para começar, imprime o belo número 🌈 no console: `1206167596222043737899107594365023368541035738443865566657697352045290673497`. O que é isso e por que ele retorna para nós em vez de um ponto decimal considerável?
82 |
83 | Na função acima `x` **não** é um `floating point`, 3.33, **ni** é um inteiro arredondado para o resultado, 3. É um inteiro que multiplicado por 3 nos dará 10 de volta ( veja como esta função `3 * x = 10`) ou também `x` pode ser um denominador que retorna 3 (`10 / x = 3`). Vamos ver isso com o seguinte contrato:
84 |
85 | ```python
86 | %builtins output
87 |
88 | from starkware.cairo.common.serialize import serialize_word
89 |
90 | func main{output_ptr: felt*}(){
91 | tempvar x = 10/3;
92 |
93 | tempvar y = 3 * x;
94 | assert y = 10;
95 | serialize_word(y);
96 |
97 | tempvar z = 10 / x;
98 | assert z = 3;
99 | serialize_word(z);
100 |
101 | return();
102 | }
103 |
104 | ```
105 |
106 | Ao compilar e executar este contrato, obtemos exatamente o que estávamos procurando:
107 |
108 | ```python
109 | Program output:
110 | 10
111 | 3
112 |
113 | ```
114 | Cairo consegue isso voltando transbordando novamente. Não vamos entrar em detalhes matemáticos. isso é pouco intuitivo, mas não se preocupe, podemos deixar aqui.
115 |
116 | > Uma vez que você está escrevendo contratos com Cairo, você não precisa ficar pensando constantemente sobre isso [as peculiaridades dos felts quando estão em divisões]. Mas é bom estar ciente de como eles funcionam (StarkNet Bootcamp - Amsterdam - min 1:31:00).
117 | >
118 |
119 | ## **3. Comparando felts 💪**
120 |
121 | Devido às particularidades dos felts, comparar entre felts não é como em outras linguagens de programação (como com `1 < 2`).
122 |
123 | Na biblioteca `starkware.cairo.common.math` encontramos funções que nos ajudarão a comparar felts ([link para o repositório GitHub](https://github.com/starkware-libs/cairo-lang/blob/master/src /starkware/cairo/common/math.cairo)). Por enquanto vamos usar `assert_not_zero`, `assert_not_equal`, `assert_nn` e `assert_le`. Existem mais recursos para comparar felts nesta biblioteca, recomendo que você veja o repositório do GitHub para explorá-los. O [seguinte código do StarkNet Bootcamp Amsterdam](https://github.com/lightshiftdev/starknet-bootcamp/blob/main/packages/contracts/samples/04-cairo-math.cairo) ajuda a entender o que cada um faz as funções que importamos (alterei um pouco). O código completo está em [src/asserts.cairo](../../../src/asserts.cairo).
124 |
125 | ```python
126 | %builtins range_check
127 |
128 | from starkware.cairo.common.math import assert_not_zero, assert_not_equal, assert_nn, assert_le
129 |
130 | func main{range_check_ptr : felt}(){
131 | assert_not_zero(1); // not zero
132 | assert_not_equal(1, 2); // not equal
133 | assert_nn(1); // non-negative
134 | assert_le(1, 10); // less or equal
135 |
136 | return ();
137 | }
138 |
139 | ```
140 |
141 | Simples, certo? São apenas maneiras diferentes de fazer asserts.
142 |
143 | Mas e se quisermos comparar `10/3 < 10`? Sabemos que isso é verdade, mas também sabemos que o resultado de `10/3` não é um número inteiro, por isso está fora do intervalo de valores possíveis que os felts podem assumir. Haverá overflow e será gerado o grande inteiro, que naturalmente será maior que 10 ou até mesmo se tornará fora dos possíveis inteiros que um felt pode levar (devido ao tamanho).
144 |
145 | De fato, a seguinte função que compara `10/3 < 10` retornará um erro: `AssertionError: a = 2412335192444087475798215188730046737082071476887731133315394704090581346994 is out of range.`
146 |
147 | ```python
148 | %builtins range_check
149 |
150 | from starkware.cairo.common.math import assert_lt
151 |
152 | func main{range_check_ptr : felt}(){
153 | assert_lt(10/3, 10); // less than
154 |
155 | return ();
156 | }
157 |
158 | ```
159 |
160 | Como então comparamos `10/3 < 10`? Temos que voltar para nossas aulas de ensino médio/faculdade. Vamos apenas remover o 3 do denominador multiplicando tudo por 3; compararíamos `3*10/3 < 3*10` que é o mesmo que `10 < 30`. Então, estamos apenas comparando números inteiros e esquecemos como os felts são excêntricos. A função a seguir é executada sem problemas.
161 |
162 | ```python
163 | %builtins range_check
164 |
165 | from starkware.cairo.common.math import assert_lt
166 |
167 | func main{range_check_ptr : felt}(){
168 | assert_lt(3*10/3, 3*10);
169 |
170 | return ();
171 | }
172 |
173 | ```
174 |
175 | ## 4. A natureza dual de assert
176 |
177 | Como vimos, `assert` é a chave para a programação no Cairo. Nos exemplos acima, usamos para confirmar uma declaração, `assert y = 10`. Este é um uso comum em outras linguagens de programação como Python. Mas no Cairo quando você tenta `assert` algo que ainda não foi atribuído, `assert` funciona para atribuir. Dê uma olhada neste exemplo adaptado de [StarkNet Bootcamp Amsterdam](https://github.com/lightshiftdev/starknet-bootcamp/blob/main/packages/contracts/samples/04-cairo-math.cairo) que também é útil para consolidar o que você aprendeu sobre structs no [tutorial anterior](2_basicos_cairo.md). O código completo está em [src/vector.cairo](../../../src/vector.cairo).
178 |
179 | ```python
180 | %builtins output
181 |
182 | from starkware.cairo.common.serialize import serialize_word
183 |
184 | struct Vector2d{
185 | x : felt,
186 | y : felt,
187 | }
188 |
189 | func add_2d(v1 : Vector2d, v2 : Vector2d) -> (r : Vector2d){
190 | alloc_locals;
191 |
192 | local res : Vector2d;
193 | assert res.x = v1.x + v2.x;
194 | assert res.y = v1.y + v2.y;
195 |
196 | return (r=res);
197 | }
198 |
199 | func main{output_ptr: felt*}(){
200 |
201 | let v1 = Vector2d(x = 1, y = 2);
202 | let v2 = Vector2d(x = 3, y = 4);
203 |
204 | let (sum) = add_2d(v1, v2);
205 |
206 | serialize_word(sum.x);
207 | serialize_word(sum.y);
208 |
209 | return();
210 | }
211 | ```
212 |
213 | Ao executar `assert res.x = v1.x + v2.x`, o prover do Cairo (mais sobre isso depois) detecta que `res.x` não existe e atribui o novo valor `v1.x + v2.x` . Se fôssemos executar `assert res.x = v1.x + v2.x` novamente, o prover realmente compararia o que encontra atribuído em `res.x` com o que tentamos atribuir. Ou seja, o uso que já sabíamos.
214 |
215 | ## 5. Arrays no Cairo
216 |
217 | Vamos fechar este tutorial com uma das estruturas de dados mais importantes. Arrays, contêm elementos ordenados. Eles são muito comuns na programação. Como eles funcionam no Cairo? Vamos aprender a **criar array de matrizes 🙉**. Sim, o escritor tem background em machine learning. O contrato abaixo está comentado (pode ser encontrado em [src/matrix.cairo](../../../src/matrix.cairo)) e examinaremos apenas a parte dos arrays, pois o leitor já sabe o resto.
218 |
219 | ```python
220 | %builtins output
221 |
222 | from starkware.cairo.common.serialize import serialize_word
223 | from starkware.cairo.common.alloc import alloc
224 |
225 | struct Vector{
226 | elements : felt*,
227 | }
228 |
229 | struct Matrix{
230 | x : Vector,
231 | y : Vector,
232 | }
233 |
234 | func main{output_ptr: felt*}(){
235 |
236 | // Defining an array, my_array, of felts.
237 | let (my_array : felt*) = alloc();
238 |
239 | // Assigning values to three elements of my_array.
240 | assert my_array[0] = 1;
241 | assert my_array[1] = 2;
242 | assert my_array[2] = 3;
243 |
244 | // Creating the vectors Vector, by
245 | // simplicity we use the same my_array for both.
246 | let v1 = Vector(elements = my_array);
247 | let v2 = Vector(elements = my_array);
248 |
249 | // Defining an array of Matrix matrices
250 | let (matrix_array : Matrix*) = alloc();
251 |
252 | // Filling matrix_array with Matrix instances.
253 | // Each instance of Matrix contains as members
254 | // Vector instances.
255 | assert matrix_array[0] = Matrix(x = v1, y = v2);
256 | assert matrix_array[1] = Matrix(x = v1, y = v2);
257 |
258 | // We use assert to test some values in
259 | // our matrix_array.
260 | assert matrix_array[0].x.elements[0] = 1;
261 | assert matrix_array[1].x.elements[1] = 2;
262 |
263 | // What value do you think it will print? Answer: 3
264 | serialize_word(matrix_array[1].x.elements[2]);
265 |
266 | return();
267 | }
268 | ```
269 |
270 | Criamos um array de feltros chamado `my_array`. Assim é definido:
271 |
272 | ```
273 | let (my_array : felt*) = alloc();
274 | ```
275 |
276 | é pouco intuitivo em comparação com o quão fácil é em Python e outras linguagens. `my_array : felt*` define uma variável chamada `my_array` que conterá um pointer (veja [tutorial anterior](2_basicos_cairo.md)) para um felt (ainda não definimos qual felt). Por quê? A documentação do Cairo nos ajuda:
277 |
278 | > “Arrays podem ser definidos como um pointer (felt*) para o primeiro elemento do array. À medida que array é preenchida, os elementos ocupam células de memória contíguas. A função alloc() é usada para definir um segmento de memória que se expande em tamanho cada vez que um novo elemento é gravado no array (documentação do Cairo)."
279 | >
280 |
281 | Então, no caso de `my_array`, colocando o `alloc()` estamos indicando o segmento de memória para o qual a expressão `my_array` aponta (lembre-se que `my_array` é apenas o nome de um pointer, `felt* ` , na memória) será expandido cada vez que um novo elemento for escrito em `my_array`.
282 |
283 | De fato, se formos [ao repositório](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/alloc.cairo) onde `alloc()` está localizado veremos que ele retorna `(ptr : felt*)`. Ou seja, ele retorna uma tupla de membro único que é um `felt*` (um ponteiro para um `felt`). Por ser uma tupla, nós a recebemos com um `let` e com `my_array : felt*` entre parênteses (veja [basicos de Cairo pt. 2](2_basicos_cairo.md y)). Tudo está fazendo sentido, certo 🙏?
284 |
285 | Vemos que a definição do nosso array de matrizes é exatamente a mesma, exceto que ao invés de querer um array de `felt` nós queremos um de `Matrix`:
286 |
287 | ```python
288 | let (matrix_array : Matrix*) = alloc();
289 | ```
290 |
291 | Já passamos o complicado 😴. Agora vamos ver como preencher nosso array com estruturas `Matrix`. Usamos `assert` e podemos indexar com `[]` a posição do array que queremos alterar ou revisar:
292 |
293 | ```
294 | assert matrix_array[0] = Matrix(x = v1, y = v2);
295 | ```
296 |
297 | O que fizemos foi criar um `Matrix(x = v1, y = v2)` e atribuí-lo à posição 0 do nosso `matrix_array`. Lembre-se que começamos a contar de 0. Preencher nosso array `felt` é ainda mais trivial: `assert my_array[0] = 1`.
298 |
299 | Então nós simplesmente chamamos os elementos dentro do `matrix_array` de diferentes maneiras. Por exemplo, com `matrix_array[1].x.elements[2]` indicamos estas etapas:
300 |
301 | 1. Chame o segundo, `[1]`, elemento de `matrix_array`. Ou seja, para `Matriz(x = v1, y = v2)`.
302 | 2. Chame o `membro` `x` de `Matrix`. Ou seja, para `v1 = Vector(elements = my_array)`.
303 | 3. Chame o `membro` `elementos` de `v1`. Ou seja, para `my_array`.
304 | 4. Chame o terceiro, `[2]`, elemento de `my_array`. Ou seja, para `3`.
305 |
306 | Não é tão complicado assim mas é satisfatório o suficiente 🤭.
307 |
308 | ## **6. Conclusão**
309 |
310 | Parabéns 🚀. Nós mergulhamos no básico do 🏖 Cairo. Com esse conhecimento você pode começar a fazer contratos simples no Cairo .
311 |
312 | Nos tutoriais a seguir, aprenderemos mais sobre gerenciamento de memória; a common library do Cairo; como funciona o compilador do Cairo; e mais!
313 |
314 | Quaisquer comentários ou melhorias, por favor, comente com [@espejelomar](https://twitter.com/espejelomar) ou faça um PR 🌈.
--------------------------------------------------------------------------------
/tutorials/tutorials/EN/3_cairo_basics.md:
--------------------------------------------------------------------------------
1 |
2 | # Programming on Ethereum L2 (pt. 3): Cairo basics 2
3 |
4 | Before starting, I recommend that you prepare your computer to code in Cairo ❤️ with the [first tutorial](1_installation.md), and review the [Cairo Basics pt. 1](2_cairo_basics.md).
5 |
6 | This is the third tutorial in a series focused on developing smart contracts with Cairo and StarkNet.
7 |
8 | 🚀 The future of Ethereum is today and it's already here. And it's just the beginning.
9 |
10 | ---
11 |
12 | In the third part of the series of basic Cairo tutorials we will delve into concepts introduced in the [second session](2_cairo_basics.md) such as `builtin`, `felt` and `assert` and their variations. In addition, we will introduce arrays. With what we have learned in this session we will be able to create basic Cairo 🚀 contracts.
13 |
14 | ## 1. Builtins and their relationship with pointers
15 |
16 | In the following program we are multiplying two numbers. The entire code is available at [src/multiplication.cairo](../../../src/multiplication.cairo). There you will find the code correctly commented.
17 |
18 | ```rust
19 | %builtins output
20 |
21 | from starkware.cairo.common.serialize import serialize_word
22 |
23 | func mult_two_nums(num1, num2) -> (prod : felt){
24 | return(prod = num1 * num2);
25 | }
26 |
27 | func main{output_ptr: felt*}(){
28 | let (prod) = mult_two_nums(2,2);
29 | serialize_word(prod);
30 | return ();
31 | }
32 | ```
33 |
34 | Remember that we introduced the `builtins` in the last session along with the implicit arguments?
35 |
36 | Each `builtin` gives you the right to use a pointer that will have the name of the `builtin` + “`_ptr`”. For example, the output builtin, which we define as `%builtins output` at the beginning of our contract, gives us the right to use the `output_ptr` pointer. The `range_check` `builtin` allows us to use the `range_check_ptr` pointer. These pointers are often used as implicit arguments that are automatically updated during a function.
37 |
38 | In the function to multiply two numbers we use `%builtins output` and then use its pointer when defining main: `func main{output_ptr: felt*}():`.
39 |
40 | ## 2. More about how interesting (rare?) felts are
41 |
42 | > The felt is the only data type that exists in Cairo, you can even omit it [its explicit declaration] (StarkNet Bootcamp - Amsterdam - min 1:14:36).
43 |
44 | Although it is not necessary to be an expert in the mathematical qualities of felts, it is valuable to know how they work. In the last tutorial we introduced them for the first time, now we will know how they affect when we compare values in Cairo.
45 |
46 | > The definition of a felt, in terrestrial terms (the exact one is here): an integer that can become huge (but has limits). For example: {...,-4,-3,-2,-1,0,+1,+2,+3,...}. Yes, it includes 0 and negative numbers.
47 |
48 | Any value that is not within this range will cause an “overflow”: an error that occurs when a program receives a number, value, or variable outside the scope of its ability to handle ([Techopedia](https://www.techopedia.com/definition/663/overflow-error#:~:text=In%20computing%2C%20an%20overflow%20error,other%20numerical%20types%20of%20variables.)).
49 |
50 | Now we understand the limits of the felt. If the value is 0.5, for example, we have an overflow. Where will we experience overflows frequently? In the divisions. The following contract (full code is in [src/division1.cairo](../../../src/division1.cairo)) divides 9/3, check with `assert` that the result is 3, and print the result.
51 |
52 | ```python
53 | %builtins output
54 |
55 | from starkware.cairo.common.serialize import serialize_word
56 |
57 | func main{output_ptr: felt*}(){
58 | tempvar x = 9/3;
59 | assert x = 3;
60 | serialize_word(x);
61 |
62 | return();
63 | }
64 |
65 | ```
66 |
67 | So far everything makes sense. But what if the result of the division is not an integer like in the following contract (the code is in [src/division2.cairo](../../../src/division2.cairo))?
68 |
69 | ```python
70 | %builtins output
71 |
72 | from starkware.cairo.common.serialize import serialize_word
73 |
74 | func main{output_ptr: felt*}(){
75 | tempvar x = 10/3;
76 | assert x = 10/3;
77 | serialize_word(x);
78 |
79 | return();
80 | }
81 |
82 | ```
83 |
84 | To begin with, it prints the beautiful number 🌈 on the console: `1206167596222043737899107594365023368541035738443865566657697352045290673497`. What is this and why does it return it to us instead of a sizable decimal point?
85 |
86 | In the function above `x` **not** is a `floating point`, 3.33, **ni** is an integer rounded to the result, 3. It is an integer that multiplied by 3 will give us 10 back (it looks like this function `3 * x = 10`) or `x` can also be a denominator that returns 3 (`10 / x = 3`). Let's see this with the following contract:
87 |
88 | ```python
89 | %builtins output
90 |
91 | from starkware.cairo.common.serialize import serialize_word
92 |
93 | func main{output_ptr: felt*}(){
94 | tempvar x = 10/3;
95 |
96 | tempvar y = 3 * x;
97 | assert y = 10;
98 | serialize_word(y);
99 |
100 | tempvar z = 10 / x;
101 | assert z = 3;
102 | serialize_word(z);
103 |
104 | return();
105 | }
106 | ```
107 |
108 | By compiling and running this contract we get exactly what we were looking for:
109 |
110 | ```python
111 | Program output:
112 | 10
113 | 3
114 | ```
115 |
116 | Cairo accomplishes this by coming back by overflowing again. Let's not go into mathematical details. This is somewhat unintuitive but don't worry, we can leave it here.
117 |
118 | > Once you're writing contracts with Cairo you don't need to be constantly thinking about this [the particularities of the felts when they are in divisions]. But it's good to be aware of how they work (StarkNet Bootcamp - Amsterdam - min 1:31:00).
119 | >
120 |
121 | ## **3. Comparing felts 💪**
122 |
123 | Due to the particularities of felts, comparing between felts is not like in other programming languages (like with `1 < 2`).
124 |
125 | In the `starkware.cairo.common.math` library we find functions that will help us compare felts ([link to GitHub repository](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/math.cairo)). For now we will use `assert_not_zero`, `assert_not_equal`, `assert_nn` and `assert_le`. There are more features to compare felts in this library, I recommend you to see the GitHub repository to explore them. The [following code from the StarkNet Bootcamp in Amsterdam](https://github.com/lightshiftdev/starknet-bootcamp/blob/main/packages/contracts/samples/04-cairo-math.cairo) it serves to understand what each of the functions that we import does (I altered it slightly). The complete code is in [src/asserts.cairo](../../../src/asserts.cairo).
126 |
127 | ```python
128 | %builtins range_check
129 |
130 | from starkware.cairo.common.math import assert_not_zero, assert_not_equal, assert_nn, assert_le
131 |
132 | func main{range_check_ptr : felt}(){
133 | assert_not_zero(1); // not zero
134 | assert_not_equal(1, 2); // not equal
135 | assert_nn(1); // non-negative
136 | assert_le(1, 10); // less or equal
137 |
138 | return ();
139 | }
140 | ```
141 |
142 | Simple, right? They're just different ways of doing asserts.
143 |
144 | But what if we want to compare `10/3 < 10`? We know this to be true, but we also know that the result of the division `10/3` is not an integer, so it falls outside the range of possible values that felts can take. There will be an overflow and a value will be generated that will turn out to be out of the possible integers that a felt can take (because of how big it is).
145 |
146 | In effect, the following function that compares `10/3 < 10` will return an error: `AssertionError: a = 2412335192444087475798215188730046737082071476887731133315394704090581346994 is out of range.`
147 |
148 | ```python
149 | %builtins range_check
150 |
151 | from starkware.cairo.common.math import assert_lt
152 |
153 | func main{range_check_ptr : felt}(){
154 | assert_lt(10/3, 10); // less than
155 |
156 | return ();
157 | }
158 | ```
159 |
160 | How then do we compare `10/3 < 10`? We have to go back to our high school/college classes. Let's just remove the 3 from the denominator by multiplying everything by 3; we would compare `3*10/3 < 3*10` which is the same as `10 < 30`. This way we are only comparing integers and forget about how eccentric the felt is. The following function runs without a problem.
161 |
162 | ```python
163 | %builtins range_check
164 |
165 | from starkware.cairo.common.math import assert_lt
166 |
167 | func main{range_check_ptr : felt}(){
168 | assert_lt(3*10/3, 3*10);
169 |
170 | return ();
171 | }
172 | ```
173 | ## 4. The dual nature of assert
174 |
175 | As we have seen, `assert` is key to programming in Cairo. In the examples above we use it to confirm a statement, `assert y = 10`. This is a common usage in other programming languages like Python. But in Cairo when you try to `assert` something that isn't assigned yet, `assert` works to assign. Check out this example adapted from [StarkNet Bootcamp Amsterdam](https://github.com/lightshiftdev/starknet-bootcamp/blob/main/packages/contracts/samples/04-cairo-math.cairo) which also helps us to consolidate what we learned about structs in the [past tutorial](2_cairo_basics.md). The complete code is in [src/vector.cairo](../../../src/vector.cairo).
176 |
177 | ```python
178 | %builtins output
179 |
180 | from starkware.cairo.common.serialize import serialize_word
181 |
182 | struct Vector2d{
183 | x : felt,
184 | y : felt,
185 | }
186 |
187 | func add_2d(v1 : Vector2d, v2 : Vector2d) -> (r : Vector2d){
188 | alloc_locals;
189 |
190 | local res : Vector2d;
191 | assert res.x = v1.x + v2.x;
192 | assert res.y = v1.y + v2.y;
193 |
194 | return (r=res);
195 | }
196 |
197 | func main{output_ptr: felt*}(){
198 |
199 | let v1 = Vector2d(x = 1, y = 2);
200 | let v2 = Vector2d(x = 3, y = 4);
201 |
202 | let (sum) = add_2d(v1, v2);
203 |
204 | serialize_word(sum.x);
205 | serialize_word(sum.y);
206 |
207 | return();
208 | }
209 | ```
210 |
211 | Running `assert res.x = v1.x + v2.x`, Cairo's prover (more on this later) detects that `res.x` does not exist, so it assigns the new value `v1.x + v2.x` to it. If we were to run `assert res.x = v1.x + v2.x` again, the prover would indeed compare what it finds assigned in `res.x` with what we tried to assign.If we were to run `assert res.x = v1.x + v2.x` again, the prover would indeed compare what it finds assigned in `res.x` with what we tried to assign. That is, the use that we already knew.
212 |
213 | ## 5. Arrays in Cairo
214 |
215 | Let's close this tutorial with one of the most important data structures. Arrays contain ordered elements. They are very common in programming. How do they work in Cairo? Let's learn **creating an array of matrices 🙉**. Yes, the write has a background in machine learning. The contract below is commented (it can be found in [src/matrix.cairo](../../../src/matrix.cairo)) and we will examine only the part of the arrays since the reader already knows the rest.
216 |
217 | ```python
218 | %builtins output
219 |
220 | from starkware.cairo.common.serialize import serialize_word
221 | from starkware.cairo.common.alloc import alloc
222 |
223 | struct Vector{
224 | elements : felt*,
225 | }
226 |
227 | struct Matrix{
228 | x : Vector,
229 | y : Vector,
230 | }
231 |
232 | func main{output_ptr: felt*}(){
233 |
234 | // Defining an array, my_array, of felts.
235 | let (my_array : felt*) = alloc();
236 |
237 | // Assigning values to three elements of my_array.
238 | assert my_array[0] = 1;
239 | assert my_array[1] = 2;
240 | assert my_array[2] = 3;
241 |
242 | // Creating the vectors Vector, by
243 | // simplicity we use the same my_array for both.
244 | let v1 = Vector(elements = my_array);
245 | let v2 = Vector(elements = my_array);
246 |
247 | // Defining an array of Matrix matrices
248 | let (matrix_array : Matrix*) = alloc();
249 |
250 | // Filling matrix_array with Matrix instances.
251 | // Each instance of Matrix contains as members
252 | // Vector instances.
253 | assert matrix_array[0] = Matrix(x = v1, y = v2);
254 | assert matrix_array[1] = Matrix(x = v1, y = v2);
255 |
256 | // We use assert to test some values in
257 | // our matrix_array.
258 | assert matrix_array[0].x.elements[0] = 1;
259 | assert matrix_array[1].x.elements[1] = 2;
260 |
261 | // What value do you think it will print? Answer: 3
262 | serialize_word(matrix_array[1].x.elements[2]);
263 |
264 | return();
265 | }
266 | ```
267 |
268 | We create an array of felts called `my_array`. This is how it is defined:
269 |
270 | ```
271 | let (my_array : felt*) = alloc();
272 | ```
273 |
274 | It's unintuitive compared to how easy it is in Python and other languages. `my_array : felt*` defines a variable called `my_array` which will contain a pointer (see [past tutorial](2_cairo_basics.md) to a felt (we haven't defined which felt yet). Why? The Cairo documentation helps us:
275 |
276 | > “Arrays can be defined as a pointer (felt*) to the first element of the array. As the array fills up, the elements occupy contiguous memory cells. The alloc() function is used to define a memory segment that expands in size each time a new element is written to the array (Cairo documentation)."
277 | >
278 |
279 | So, in the case of `my_array`, by placing the `alloc()` we are indicating that the memory segment pointed to by the `my_array` expression (remember that `my_array` is just the name of a pointer, `felt*`, in memory) will be expanded each time a new element is written to `my_array`.
280 |
281 | In fact, if we go [to the repo](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/alloc.cairo) where `alloc()` is located we will see that it returns `(ptr : felt*)`. That is, it returns a single-member tuple that is a `felt*` (a pointer to a `felt`). Because it is a tuple, we receive it with a `let` and with `my_array : felt*` in parentheses (see [Cairo basics pt. 2](2_cairo_basics.md)). Everything is making sense, right 🙏?
282 |
283 | We see that the definition of our array of matrices is exactly the same except that instead of wanting an array of `felt` we want one of `Matrix`:
284 |
285 | ```python
286 | let (matrix_array : Matrix*) = alloc();
287 |
288 | ```
289 | We already passed the complicated 😴. Now let's see how to fill our array with `Matrix` structures. We use `assert` and we can index with `[]` the position of the array that we want to alter or revise:
290 |
291 | ```
292 | assert matrix_array[0] = Matrix(x = v1, y = v2);
293 | ```
294 | What we did was create a `Matrix(x = v1, y = v2)` and assign it to position 0 of our `matrix_array`. Remember that we start counting from 0. Filling our `felt` array is even more trivial: `assert my_array[0] = 1`.
295 |
296 | Then we simply call elements inside the `matrix_array` in different ways. For example, with `matrix_array[1].x.elements[2]` we indicate these steps:
297 |
298 | 1. Call the second, `[1]`, element of `matrix_array`. That is, to `Matrix(x = v1, y = v2)`.
299 | 2. Call the `member` `x` of `Matrix`. That is, to `v1 = Vector(elements = my_array)`.
300 | 3. Call the `member` `elements` of `v1`. That is, to `my_array`.
301 | 4. Call the third, `[2]`, element of `my_array`. That is, to `3`.
302 |
303 | It's not that complicated but it's satisfying enough 🤭.
304 |
305 | ## **6. Conclusion**
306 |
307 | Congratulations 🚀. We've delved into the basics of 🏖 Cairo. With this knowledge you can start making simple contracts in Cairo 🥳.
308 |
309 | In the following tutorials we will learn more about memory management; the cairo common library; how the Cairo compiler works; and more!
310 |
311 | Any comments or improvements please comment with [@espejelomar](https://twitter.com/espejelomar) or make a PR 🌈.
--------------------------------------------------------------------------------
/tutorials/tutorials/ES/4_protostar.md:
--------------------------------------------------------------------------------
1 | # Programando en la L2 de Ethereum (pt. 4): Protostar y desplegando contratos
2 |
3 | Antes de comenzar, te recomiendo que prepares tu equipo para programar en Cairo ❤️ con el [primer tutorial](1_installation.md), y revises los [básicos de Cairo pt. 1](2_cairo_basics.md) y [pt. 2](3_cairo_basics.md).
4 |
5 | Únete a la comunidad de habla hispana de StarkNet ([Linktree](https://linktr.ee/starknet_es) con links a telegram, tutoriales, proyectos, etc.). Este es el cuarto tutorial de una serie enfocada en el desarrollo de smart cotracts con Cairo y StarkNet. Recomiendo que hagas los tutoriales pasados antes de pasar a este.
6 |
7 | 🚀 El futuro de Ethereum es hoy y ya está aquí. Y apenas es el comienzo. Aprende un poco más sobre el ecosistema de Starkware en [este texto corto](https://mirror.xyz/espejel.eth/PlDDEHJpp3Y0UhWVvGAnkk4JsBbJ8jr1oopGZFaRilI).
8 |
9 | ---
10 |
11 | > “StarkNet es un ZK-Rollup descentralizado sin permiso que funciona como una red L2 sobre Ethereum, donde cualquier dApp puede escalar ilimitadamente para su cálculo, sin comprometer la compatibilidad y la seguridad de Ethereum.” - [Documentación de StarkNet](https://starknet.io/docs/hello_starknet/index.html#hello-starknet).
12 |
13 | ¡Felicidades! 🚀 Ya tenemos un nivel intermedio de Cairo. Cairo es para StarkNet lo que Solidity es para Ethereum. Es hora de desplegar nuestros contratos en StarkNet. También aprenderemos a utilizar [Protostar](https://github.com/software-mansion/protostar), herramienta inspirada en [Foundry](https://github.com/foundry-rs/foundry) y clave para compilar, hacer tests y desplegar.
14 |
15 | Actualmente podemos operar con el Alpha de StarkNet. Los pasos recomendados para el despliegue de contratos son:
16 |
17 | 1. Unit tests - Protostar.
18 | 2. Devnet - [Shard Lab’s](https://github.com/Shard-Labs/starknet-devnet) `starknet-devnet` (revisado aquí).
19 | 3. Testnet - Alpha Goerli, `alpha-goerli` (revisado aquí).
20 | 4. Mainnet - Alpha StarkNet, `alpha-mainnnet`.
21 |
22 | En este tutorial aprenderemos a desplegar contratos a la devnet y la testnet. En un texto siguiente aprenderemos a crear unit tests con Protostar; y a interactuar con la devnet y testnet.
23 |
24 | ¡Comencemos!
25 |
26 | ---
27 |
28 | ## 1. Instalación de Protostar
29 |
30 | En este punto ya tenemos instalado `cairo-lang`. Si no, puedes revisar [nuestro tutorial](1_instalacion.md) sobre cómo instalarlo.
31 |
32 | En Ubuntu o MacOS (no está disponible para Windows) corre el siguiente comando:
33 |
34 | `curl -L https://raw.githubusercontent.com/software-mansion/protostar/master/install.sh | bash`
35 |
36 | Reinicia tu terminal y corre `protostar -v` para ver la versión de tu `protostar` y `cairo-lang`.
37 |
38 | Si más adelante quieres actualizar tu protostar usa `protostar upgrade`. Si te encuentras con problemas en las instalación te recomiendo que revises la [documentación de Protostar](https://docs.swmansion.com/protostar/docs/tutorials/installation).
39 |
40 | ## 2. Primeros pasos con Protostar
41 |
42 | ¿Qué significa inicializar un proyecto con Protostar?
43 |
44 | - **Git**. Se creará un nuevo directorio (carpeta) que será un repositorio de git (tendrá un archivo `.git`).
45 | - `protostar.toml`. Aquí tendremos información necesaria para configurar nuestro proyecto. ¿Conoces Python? Bueno ya sabes por donde vamos con esto.
46 | - **Se crearán tres directorios src** (donde estará tu código), lib (para dependencias externas), y tests (donde estarán los tests).
47 |
48 | Puedes inicializar tu proyecto con el comando `protostar init`, o puedes indicar que un proyecto existente utilizará Protostar con `protostar init --existing`. Básicamente, solo necesitas que tu directorio sea un repositorio de git y tenga un archivo `protostar.toml` con la configuración del proyecto. Incluso, podríamos crear nosotr@s mism@s el archivo `protostar.toml` desde nuestro editor de texto.
49 |
50 | Corramos `protostar init` para inicializar un proyecto de Protostar. Nos pide indicar dos cosas:
51 |
52 | - `project directory name`: ¿Cuál es el nombre del directorio donde se encuentra tu proyecto?
53 | - `libraries directory name`: ¿Cuál es el nombre del directorio donde se instalarán dependencias externas?
54 |
55 | Así luce la estructura de nuestro proyecto:
56 |
57 | ```
58 | ❯ tree -L 2
59 | .
60 | ├── lib
61 | ├── protostar.toml
62 | ├── src
63 | │ └── main.cairo
64 | └── tests
65 | └── test_main.cairo
66 | ```
67 |
68 | - Inicialmente, aquí se encuentra información sobre la versión de protostar utilizada `[“protostar.config“]`, dónde se encontrarán las librerías externas utilizadas
69 |
70 | ## 3. Instalando dependencias (bibliotecas) externas
71 |
72 | Protostar utiliza submodules de git para instalar dependencias externas. Próximamente se hará con un package manager. Instalemos un par de dependencias.
73 |
74 | Instalando `cairo-contracts` indicamos el repositorio donde se encuentran, es decir, [github.com/OpenZeppelin/cairo-contracts](http://github.com/OpenZeppelin/cairo-contracts). Usemos `protostar install`:
75 |
76 | `protostar install https://github.com/OpenZeppelin/cairo-contracts`
77 |
78 | Instalemos una dependencia más, `cairopen_contracts`:
79 |
80 | `protostar install https://github.com/CairOpen/cairopen-contracts`
81 |
82 | Nuestras nuevas dependencias se almacenan el directorio `lib`:
83 |
84 | ```
85 | ❯ tree -L 2
86 | .
87 | ├── lib
88 | │ ├── cairo_contracts
89 | │ └── cairopen_contracts
90 | ├── protostar.toml
91 | ├── src
92 | │ └── main.cairo
93 | └── tests
94 | └── test_main.cairo
95 | ```
96 |
97 | Por último, agrega en `protostar.toml` la siguiente sección:
98 |
99 | ```
100 | ["protostar.shared_command_configs"]
101 | cairo-path = ["lib/cairo_contracts/src", "lib/cairopen_contracts/src"]
102 | ```
103 |
104 | Esto nos permite que Protostar use esos paths para encontrar las librerías de interés. Cuando importes, por ejemplo `from openzeppelin.access.ownable.library import Ownable`, Protostar buscará `Ownable` en el path `lib/cairo_contracts/src/openzeppelin/access/ownable/library`. Si cambias el nombre del directorio donde almacenas tus dependencias externas entonces no usarías `lib` sino el nombre de ese directorio.
105 |
106 |
107 | ¡Maravilloso!
108 |
109 | ## 4. Compilando
110 |
111 | En el pasado hemos estado compilando nuestros contratos con `cairo-compile`. Cuando corremos `cairo-compile sum2Numbers.cairo --output x.json` para compilar un contrato `sum2Numbers.cairo` de Cairo, el resultado es un nuevo archivo en nuestro directorio de trabajo llamado `x.json`. El archivo json es utilizado por `cairo-run` cuando corremos nuestro programa.
112 |
113 | En Protostar podemos compilar todos nuestros contratos de StarkNet a la vez con `protostar build`. Pero antes debemos indicar en la sección `[“protostar.contracts”]` de `protostar.toml` los contratos que queremos compilar (o build). Imagina que tenemos un contrato `ERC721MintableBurnable.cairo` en nuestra carpeta `src` (donde están los contratos).
114 |
115 | > Nota: No lograrás compilar código de Cairo puro utilizando `protostar build`. Solamente es para contratos de StarkNet (tienen la señal `%lang starknet` al comienzo). Para compilar y correr aplicaciones de Cairo puro necesitas usar `cairo-compile` y `cairo-run` ([ver tutorial de Cairo](3_cairo_basics.md)). En siguientes tutoriales aprenderemos cómo crear contratos de StarkNet.
116 |
117 | Colocamos en `protostar.toml` que queremos compilar el contrato en `src/ERC721MintableBurnable.cairo` y que lo queremos llamar `ERC721_original`:
118 |
119 | ```
120 | ["protostar.contracts"]
121 | ERC721_original = [
122 | "src/ERC721MintableBurnable.cairo",
123 | ]
124 | ```
125 |
126 | Corremos `protostar build`. Veremos que tenemos un nuevo directorio `build` con un archivo `ERC721MintableBurnable.json`. ¡Esto es lo que buscábamos!
127 |
128 | Moraleja: si tu contrato no está en la sección `[“protostar.contracts“]` del `protostar.toml` no será compilado.
129 |
130 | En este punto podemos pasar a desplegar contratos de StarkNet con Protostar.
131 |
132 | ## 5. La devnet: starknet-devnet
133 |
134 | Las transacciones en la testnet toman tiempo para completarse por lo que es mejor comenzar desarrollando y probando localmente. Utilizaremos la [devnet desarollada por Shard Labs](https://github.com/Shard-Labs/starknet-devnet). Podemos pensar en este paso como un equivalente de Ganache. Es decir, emula la testnet (alpha goerli) de StarkNet.
135 |
136 | Instala usando:
137 |
138 | `pip install starknet-devnet`
139 |
140 | Reinicia tu terminal y corre `starknet-devnet --version` para revisar que la instalación fue correcta. Revisa que tengas [la versión más actualizada](https://github.com/Shard-Labs/starknet-devnet/releases). Si no la tienes entonces corre `pip install --upgrade starknet-devnet`.
141 |
142 | Inicializa la devnet en una shell separada (o una pestaña) con `starknet-devnet --accounts 3 --gas-price 250 --seed 0`:
143 |
144 | ```
145 | ❯ starknet-devnet --accounts 3 --gas-price 250 --seed 0
146 | Account #0
147 | Address: 0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a
148 | Public key: 0x7e52885445756b313ea16849145363ccb73fb4ab0440dbac333cf9d13de82b9
149 | Private key: 0xe3e70682c2094cac629f6fbed82c07cd
150 |
151 | Account #1
152 | Address: 0x69b49c2cc8b16e80e86bfc5b0614a59aa8c9b601569c7b80dde04d3f3151b79
153 | Public key: 0x175666e92f540a19eb24fa299ce04c23f3b75cb2d2332e3ff2021bf6d615fa5
154 | Private key: 0xf728b4fa42485e3a0a5d2f346baa9455
155 |
156 | Account #2
157 | Address: 0x7447084f620ba316a42c72ca5b8eefb3fe9a05ca5fe6430c65a69ecc4349b3b
158 | Public key: 0x58100ffde2b924de16520921f6bfe13a8bdde9d296a338b9469dd7370ade6cb
159 | Private key: 0xeb1167b367a9c3787c65c1e582e2e662
160 |
161 | Initial balance of each account: 1000000000000000000000 WEI
162 | Seed to replicate this account sequence: 0
163 | WARNING: Use these accounts and their keys ONLY for local testing. DO NOT use them on mainnet or other live networks because you will LOSE FUNDS.
164 |
165 | * Listening on http://127.0.0.1:5050/ (Press CTRL+C to quit)
166 | ```
167 |
168 | Puedes correr `curl http://127.0.0.1:5050/is_alive` para revisar si la devnet está activa. Si se encuentra activa recibirás `Alive!!!%` de vuelta.
169 |
170 | Con esto estamos indicando que crearemos tres cuentas y que las transacciones costarán 250 wei per gas. Colocamos un número en seed para tener las mismas cuentas cada vez que activemos nuestra devnet. Estas cuentas están basadas en el código y estándares desarrollados por [Open Zepellin para Cairo](https://github.com/OpenZeppelin/cairo-contracts/tree/v0.2.1).
171 |
172 | Es clave que tengamos a la mano la dirección en donde está corriendo nuestra devnet. En la foto arriba es: `http://127.0.0.1:5050/`. Más adelante la utilizaremos.
173 |
174 | La interacción con la devnet y la testnet es muy similar. Si quieres ver todos los argumentos disponibles en la llamada `starknet-devnet` puedes llamar `starknet-devnet --help`.
175 |
176 | ## 6. Desplegando en la devnet y testnet
177 |
178 | Utilicemos un ejemplo real. Cuando inicializamos un proyecto de Protostar, se crea automáticamente un contrato `main.cairo` en el directorio `src`. Puedes usarlo como ejemplo de un contrato para desplegar en la devnet y después en la testnet. Solo necesitas asegurarte de que en `protostar.toml` definas que será compilado. En este tutorial vamos a desplegar un contrato para un ERC721 (NFT) que se encuentra en [este repositorio](../../../src/ERC721MintableBurnable.cairo). En `protostar.toml` colocamos:
179 |
180 | ```
181 | ["protostar.contracts"]
182 | ERC721_original = [
183 | "src/ERC721MintableBurnable.cairo",
184 | ]
185 | ```
186 |
187 | Corre `protostar build` para compilar y crear el `build/ERC721_original.json` que usaremos para el despliegue.
188 |
189 | Para hacer deploy de nuestros contratos en la devnet desde Protostar podemos crear configuration profiles. En el `protostar.toml` creamos una sección `[profile.devnet.protostar.deploy]` donde colocamos el url donde desplegamos nuestra devnet localmente: `gateway-url=”http://127.0.0.1:5050/”` y `chain-id="1"`. Para la testnet la sección sería `[profile.testnet.protostar.deploy]` y colocamos `network="testnet"`.
190 |
191 |
192 | ```
193 | [profile.devnet.protostar.deploy]
194 | gateway-url="http://127.0.0.1:5050/"
195 | chain-id="1"
196 |
197 | [profile.testnet.protostar.deploy]
198 | network="testnet"
199 | ```
200 |
201 | Para la devnet corremos:
202 |
203 | ```
204 | protostar -p devnet deploy ./build/ERC721.json --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536
205 | ```
206 |
207 | Para la testnet corremos:
208 |
209 | ```
210 | protostar -p testnet deploy build/ERC721_original.json --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536
211 | ```
212 |
213 | Todo es intuitivo, excepto quizás:
214 | - `-p` hace referencia a los profiles que creamos en `protostar.toml`
215 | - `--inputs` son los argumentos del constructor para el contrato que estamos desplegando. No te preocupes en el siguiente tutorial aprendemos qué es un constructor. En este caso el constructor del ERC721 nos pide tres felts (`name`, `symbol` y `owner`)
216 |
217 | También podríamos desplegar sin ayuda de los profiles en el `protostar.toml`. En el caso de la testnet puede ser eficiente pues solo agregamos `--network testnet`:
218 |
219 | ```
220 | protostar deploy ./build/ERC721.json --network testnet --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536
221 | ```
222 |
223 | Pero en el caso de la devnet tendríamos que agregar dos argumentos por lo que quizás los profiles convienen más:
224 |
225 | ```
226 | protostar deploy ./build/ERC721.json --gateway-url "http://127.0.0.1:5050/" --chain-id "1" --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536
227 | ```
228 |
229 | Cuando desplegamos en la devnet o testnet obtenemos el contract address y el transaction hash:
230 |
231 | ```
232 | Contract address: 0x002454c7b1f60f52a383963633dff767fd5372c43aad53028a5a8a2b9e04646d
233 | Transaction hash: 0x05a2f78261444b97b155417a5734210abe2ee1081b7f12f39f660321fd10e670
234 | ```
235 |
236 | Es importante guardar el contract address pues interactuaremos con él en siguientes funciones. Esto lo revisaremos en otros tutoriales.
237 |
238 | Si desplegaste en la testnet puedes usar la contract address para interactuar con tu contrato en un block explorer: [Voyager](https://goerli.voyager.online/) o [StarkScan](https://testnet.starkscan.co/). Estos block explorers son equivalentes a [Etherscan](https://goerli.voyager.online/) para la L1.
239 |
240 | La ventaja de desplegar en la devnet primero es que podemos interactuar más rápidamente con nuestros contratos. Para la testnet tendremos que esperar cerca de unos minutos.
241 |
242 | ## 7. Desplegando con la CLI de `starknet`
243 |
244 | Por debajo, Protostar está utilizando el CLI de `starknet` para desplegar. Hay ocasiones en las que no queremos depender completamente de Protostar, por ejemplo cuando hay una actualización de StarkNet y aún no es aplicada en la biblioteca de Protostar.
245 |
246 | Más adelante exploraremos a fondo la CLI de `starknet`. Por ahora veamos cómo desplegar exactamente el mismo contrato.
247 |
248 | Para la devnet, una vez que la encendiste en el gateway http://127.0.0.1:5050, sería:
249 |
250 | ```
251 | starknet deploy --contract ./build/ERC721_original.json --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536 --gateway_url "http://127.0.0.1:5050" --no_wallet
252 | ```
253 |
254 | Para la testnet:
255 |
256 | ```
257 | starknet deploy --contract ./build/ERC721_original.json --inputs 27424471826656371 4279885 1268012686959018685956609106358567178896598707960497706446056576062850827536 --network alpha-goerli --no_wallet
258 | ```
259 |
260 | En ambos casos obtenemos el contract address y el transaction hash; igual que al desplegar con Protostar.
261 |
262 |
263 | ## 8. Conclusión
264 |
265 | Felicidades 🦾. ¡Acabas de dar tus primeros pasos en StarkNet! Estás aprovechando todo lo aprendido en los tutoriales pasados sobre Cairo.
266 |
267 | En los siguientes tutoriales aprenderemos más sobre la creación de smart contracts en StarkNet 🚀.
268 |
269 | Cualquier comentario o mejora por favor comentar con [@espejelomar](https://twitter.com/espejelomar) 🌈.
270 |
--------------------------------------------------------------------------------
/tutorials/tutorials/ES/3_cairo_basics.md:
--------------------------------------------------------------------------------
1 | # Programando en la L2 de Ethereum (pt. 3): Básicos de Cairo 2
2 |
3 | Antes de comenzar, te recomiendo que prepares tu equipo para programar en Cairo ❤️ con el [primer tutorial](1_installation.md), y revises los [básicos de Cairo pt. 1](2_cairo_basics.md).
4 |
5 | Únete a la comunidad de habla hispana de StarkNet ([Linktree](https://linktr.ee/starknet_es) con links a telegram, tutoriales, proyectos, etc.). Este es el tercer tutorial de una serie enfocada en el desarrollo de smart cotracts con Cairo y StarkNet.
6 |
7 | 🚀 El futuro de Ethereum es hoy y ya está aquí. Y apenas es el comienzo. Aprende un poco más sobre el ecosistema de Starkware en [este texto corto](https://mirror.xyz/espejel.eth/PlDDEHJpp3Y0UhWVvGAnkk4JsBbJ8jr1oopGZFaRilI).
8 |
9 | ---
10 |
11 | En la tercera parte de la serie de tutoriales básicos de Cairo profundizaremos en conceptos introducidos en la [segunda sesión](https://github.com/starknet-edu/walking-with-starknet/blob/master/tutorials/tutorials/ES/2_cairo_basics.md) como los `builtin`, los `felt` y `assert` y sus variaciones. Además, introduciremos los arrays. Con lo aprendido en esta sesión seremos capaces de crear contratos básicos en Cairo 🚀.
12 |
13 | ## 1. Los builtin y su relación con los pointers
14 |
15 | En el siguiente programa estamos multiplicando dos números. El código entero está disponible en [src/multiplication.cairo](../../../src/multiplication.cairo). Ahí encontrarás el código correctamente comentado.
16 |
17 | ```python
18 | %builtins output
19 |
20 | from starkware.cairo.common.serialize import serialize_word
21 |
22 | func mult_two_nums(num1, num2) -> (prod : felt){
23 | return(prod = num1 * num2);
24 | }
25 |
26 | func main{output_ptr: felt*}(){
27 | let (prod) = mult_two_nums(2,2);
28 | serialize_word(prod);
29 | return ();
30 | }
31 | ```
32 |
33 | ¿Recuerdas que introdujimos los `builtins` en la sesión pasada junto con los argumentos implícitos?
34 |
35 | Cada `builtin` te da el derecho a usar un pointer que tendrá el nombre del `builtin` + “`_ptr`”. Por ejemplo, el builtin output, que definimos `%builtins output` al inicio de nuestro contrato, nos da derecho a usar el pointer `output_ptr`. El `builtin` `range_check` nos permite usar el pointer `range_check_ptr`. Estos pointers suelen usarse como argumentos implícitos que se actualizan automáticamente durante una función.
36 |
37 | En la función para multiplicar dos números usamos `%builtins output` y, posteriormente, utilizamos su pointer al definir main: `func main{output_ptr: felt*}():`.
38 |
39 | ## 2. Más sobre lo interesante (raros?) que son los felts
40 |
41 | > El felt es el único tipo de datos que existe en Cairo, incluso puedes omitirlo [su declaración explícita] (StarkNet Bootcamp - Amsterdam - min 1:14:36).
42 |
43 | Si bien no es necesario ser un@ expert@ en las cualidades matemáticas de los felts, es valioso conocer cómo funcionan. En el tutorial pasado los introdujimos por primera vez, ahora conoceremos cómo afectan cuando comparamos valores en Cairo.
44 |
45 | > La definición de un felt, en términos terrestres (la exacta esta aquí): un número entero que puede llegar a ser enorme (pero tiene límites). Por ejemplo: {...,-4,-3,-2,-1,0,+1,+2,+3,...}. Sí, incluye 0 y números negativos.
46 |
47 | Cualquier valor que no se encuentre dentro de este rango causará un “overflow”: un error que ocurre cuando un programa recibe un número, valor o variable fuera del alcance de su capacidad para manejar ([Techopedia](https://www.techopedia.com/definition/663/overflow-error#:~:text=In%20computing%2C%20an%20overflow%20error,other%20numerical%20types%20of%20variables.)).
48 |
49 | Ahora entendemos los límites de los felt. Si el valor es 0.5, por ejemplo, tenemos un overflow. ¿Dónde experimentaremos overflows frecuentemente? En las divisiones. El siguiente contrato (el código completo está en [src/division1.cairo](../../../src/division1.cairo)) divide 9/3, revisa con `assert` que el resultado sea 3, e imprime el resultado.
50 |
51 | ```python
52 | %builtins output
53 |
54 | from starkware.cairo.common.serialize import serialize_word
55 |
56 | func main{output_ptr: felt*}(){
57 | tempvar x = 9/3;
58 | assert x = 3;
59 | serialize_word(x);
60 |
61 | return();
62 | }
63 |
64 | ```
65 |
66 | Hasta ahora todo hace sentido. ¿Pero qué pasa si el resultado de la división no es un entero como en el siguiente contrato (el código está en [src/division2.cairo](../../../src/division2.cairo))?
67 |
68 | ```python
69 | %builtins output
70 |
71 | from starkware.cairo.common.serialize import serialize_word
72 |
73 | func main{output_ptr: felt*}(){
74 | tempvar x = 10/3;
75 | assert x = 10/3;
76 | serialize_word(x);
77 |
78 | return();
79 | }
80 |
81 | ```
82 |
83 | Para empezar, nos imprime en consola el hermoso número 🌈: `1206167596222043737899107594365023368541035738443865566657697352045290673497`. ¿Qué es esto y por qué nos lo retorna en vez de un apreciable punto decimal?
84 |
85 | En la función arriba `x` **no** es un `floating point`, 3.33, **ni** es un entero redondeado con el resultado, 3. Es un entero que multiplicado por 3 nos dará 10 de vuelta (se ve como esta función `3 * x = 10`) o también `x` puede ser un denominador que nos devuelva 3 (`10 / x = 3`). Veamos esto con el siguiente contrato:
86 |
87 | ```python
88 | %builtins output
89 |
90 | from starkware.cairo.common.serialize import serialize_word
91 |
92 | func main{output_ptr: felt*}(){
93 | tempvar x = 10/3;
94 |
95 | tempvar y = 3 * x;
96 | assert y = 10;
97 | serialize_word(y);
98 |
99 | tempvar z = 10 / x;
100 | assert z = 3;
101 | serialize_word(z);
102 |
103 | return();
104 | }
105 |
106 | ```
107 |
108 | Al compilar y correr este contrato obtenemos exactamente lo que buscabamos:
109 |
110 | ```python
111 | Program output:
112 | 10
113 | 3
114 |
115 | ```
116 |
117 | Cairo logra esto al volver al realizar un overflowing de nuevo. No entremos en detalles matemáticos. Esto es algo poco intuitivo pero no te preocupes, hasta aquí lo podemos dejar.
118 |
119 | > Una vez que estás escribiendo contratos con Cairo no necesitas estar pensando constantemente en esto [las particularidades de los felts cuando están en divisiones]. Pero es bueno estar consciente de cómo funcionan (StarkNet Bootcamp - Amsterdam - min 1:31:00).
120 | >
121 |
122 | ## **3. Comparando felts 💪**
123 |
124 | Debido a las particularidades de los felts, comparar entre felts no es como en otros lenguajes de programación (como con `1 < 2`).
125 |
126 | En la librería `starkware.cairo.common.math` encontramos funciones que nos servirán para comparar felts ([link a repositorio en GitHub](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/math.cairo)). Por ahora usaremos `assert_not_zero`, `assert_not_equal`, `assert_nn` y `assert_le`. Hay más funciones para comparar felts en esta librería, te recomiendo que veas el repositorio de GitHub para explorarlas. El [siguiente código del Bootcamp de StarkNet en Amsterdam](https://github.com/lightshiftdev/starknet-bootcamp/blob/main/packages/contracts/samples/04-cairo-math.cairo) sirve para entender lo que hace cada una de las funciones que importamos (lo alteré ligeramente). El código completo está en [src/asserts.cairo](../../../src/asserts.cairo).
127 |
128 | ```python
129 | %builtins range_check
130 |
131 | from starkware.cairo.common.math import assert_not_zero, assert_not_equal, assert_nn, assert_le
132 |
133 | func main{range_check_ptr : felt}(){
134 | assert_not_zero(1); // not zero
135 | assert_not_equal(1, 2); // not equal
136 | assert_nn(1); // non-negative
137 | assert_le(1, 10); // less or equal
138 |
139 | return ();
140 | }
141 |
142 | ```
143 |
144 | ¿Sencillo, cierto? Solo son formas diferentes de hacer asserts.
145 |
146 | ¿Pero qué pasa si queremos comparar `10/3 < 10`? Sabemos que esto es cierto, pero también sabemos que el resultado de la división `10/3` no es un entero por lo que cae fuera del rango de posibles valores que pueden tomar los felts. Habrá overflow y se generará un valor que resultará estar fuera de los enteros posibles que un felt puede tomar (por lo grande que es).
147 |
148 | n efecto la siguiente función que compara `10/3 < 10` nos retornará un error: `AssertionError: a = 2412335192444087475798215188730046737082071476887731133315394704090581346994 is out of range.`
149 |
150 | ```python
151 | %builtins range_check
152 |
153 | from starkware.cairo.common.math import assert_lt
154 |
155 | func main{range_check_ptr : felt}(){
156 | assert_lt(10/3, 10); // less than
157 |
158 | return ();
159 | }
160 |
161 | ```
162 |
163 | ¿Cómo hacemos entonces para comparar `10/3 < 10`? Tenemos que volver a nuestras clases de secundaria/colegio. Simplemente eliminemos el 3 del denominador al multiplicar todo por 3; compararíamos `3*10/3 < 3*10` que es lo mismo que `10 < 30`. Así solo estamos comparando enteros y nos olvidamos de lo exéntricos que son los felt. La siguiente función corre sin problema.
164 |
165 | ```python
166 | %builtins range_check
167 |
168 | from starkware.cairo.common.math import assert_lt
169 |
170 | func main{range_check_ptr : felt}(){
171 | assert_lt(3*10/3, 3*10);
172 |
173 | return ();
174 | }
175 |
176 | ```
177 |
178 | ## 4. La doble naturaleza de assert
179 |
180 | Como hemos visto, `assert` es clave para la programación en Cairo. En los ejemplos arriba lo utilizamos para confirmar una declaración, `assert y = 10`. Este es un uso común en otros lenguajes de programación como Python. Pero en Cairo cuando tratas de `assert` algo que no está asignado aún, `assert` funciona para asignar. Mira esté ejemplo adaptado del [Bootcamp de StarkNet en Amsterdam](https://github.com/lightshiftdev/starknet-bootcamp/blob/main/packages/contracts/samples/04-cairo-math.cairo) que también nos sirve para afianzar lo aprendido sobre las structs en el [tutorial pasado](2_cairo_basics.md). El código completo está en [src/vector.cairo](../../../src/vector.cairo).
181 |
182 | ```python
183 | %builtins output
184 |
185 | from starkware.cairo.common.serialize import serialize_word
186 |
187 | struct Vector2d{
188 | x : felt,
189 | y : felt,
190 | }
191 |
192 | func add_2d(v1 : Vector2d, v2 : Vector2d) -> (r : Vector2d){
193 | alloc_locals;
194 |
195 | local res : Vector2d;
196 | assert res.x = v1.x + v2.x;
197 | assert res.y = v1.y + v2.y;
198 |
199 | return (r=res);
200 | }
201 |
202 | func main{output_ptr: felt*}(){
203 |
204 | let v1 = Vector2d(x = 1, y = 2);
205 | let v2 = Vector2d(x = 3, y = 4);
206 |
207 | let (sum) = add_2d(v1, v2);
208 |
209 | serialize_word(sum.x);
210 | serialize_word(sum.y);
211 |
212 | return();
213 | }
214 | ```
215 |
216 | Al correr `assert res.x = v1.x + v2.x`, el prover (más sobre esto más adelante) de Cairo detecta que `res.x` no existe por lo que le asigna el nuevo valor `v1.x + v2.x`. Si volvieramos a correr `assert res.x = v1.x + v2.x`, el prover sí compararía lo que encuentra asignado en `res.x` con lo que intentamos asignar. Es decir, el uso que ya conocíamos.
217 |
218 | ## 5. Arrays en Cairo
219 |
220 | Cerremos este tutorial con una de las estructura de datos más importantes. Los arrays, arreglos en español, contienen elementos ordenados. Son muy comunes en programación. ¿Cómo funcionan en Cairo? Aprendamos **creando un array de matrices 🙉**. Sí, el escrito tiene un background en machine learning. El contrato abajo está comentado (se encuentra en [src/matrix.cairo](../../../src/matrix.cairo)) y examinaremos unicamente la parte de los arrays pues el lector ya conoce el resto.
221 |
222 | ```python
223 | %builtins output
224 |
225 | from starkware.cairo.common.serialize import serialize_word
226 | from starkware.cairo.common.alloc import alloc
227 |
228 | struct Vector{
229 | elements : felt*,
230 | }
231 |
232 | struct Matrix{
233 | x : Vector,
234 | y : Vector,
235 | }
236 |
237 | func main{output_ptr: felt*}(){
238 |
239 | // Defining an array, my_array, of felts.
240 | let (my_array : felt*) = alloc();
241 |
242 | // Assigning values to three elements of my_array.
243 | assert my_array[0] = 1;
244 | assert my_array[1] = 2;
245 | assert my_array[2] = 3;
246 |
247 | // Creating the vectors Vector, by
248 | // simplicity we use the same my_array for both.
249 | let v1 = Vector(elements = my_array);
250 | let v2 = Vector(elements = my_array);
251 |
252 | // Defining an array of Matrix matrices
253 | let (matrix_array : Matrix*) = alloc();
254 |
255 | // Filling matrix_array with Matrix instances.
256 | // Each instance of Matrix contains as members
257 | // Vector instances.
258 | assert matrix_array[0] = Matrix(x = v1, y = v2);
259 | assert matrix_array[1] = Matrix(x = v1, y = v2);
260 |
261 | // We use assert to test some values in
262 | // our matrix_array.
263 | assert matrix_array[0].x.elements[0] = 1;
264 | assert matrix_array[1].x.elements[1] = 2;
265 |
266 | // What value do you think it will print? Answer: 3
267 | serialize_word(matrix_array[1].x.elements[2]);
268 |
269 | return();
270 | }
271 | ```
272 |
273 | Creamos un array de felts llamado `my_array`. Esta es la forma en que se define:
274 |
275 | ```
276 | let (my_array : felt*) = alloc();
277 | ```
278 |
279 | Es poco intuitivo en comparación con lo fácil que es en Python y otros lenguajes. `my_array : felt*` define una variable llamada `my_array` que contendrá un pointer (ver [tutorial pasado](2_cairo_basics.md)) a un felt (aún no definimos a qué felt). ¿Por qué? La documentación de Cairo nos ayuda:
280 |
281 | > “Los arrays se pueden definir como un pointer (felt*) al primer elemento del array. A medida que se llena el array, los elementos ocupan celdas de memoria contiguas. La función alloc() se usa para definir un segmento de memoria que expande su tamaño cada vez que se escribe un nuevo elemento en el array (documentación de Cairo)”.
282 | >
283 |
284 | Entonces, en el caso de `my_array`, al colocar el `alloc()` estamos indicando que el segmento de memoria al que la expresión `my_array` apunta (recuerda que `my_array` es solo el nombre de un pointer, `felt*`, en memoria) será expandido cada vez que se escriba un nuevo elemento en `my_array`.
285 |
286 | De hecho, si pasamos [al repo](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/alloc.cairo) donde se encuentra `alloc()` veremos que retorna `(ptr : felt*)`. Es decir, nos regresa una tupla de un solo miembro que es un `felt*` (un pointer a un `felt`). Por ser una tupla la recibimos con un `let` y con `my_array : felt*` entre paréntesis (ver [básicos de Cairo pt. 2](2_cairo_basics.md)). Todo va haciendo sentido, ¿cierto 🙏?
287 |
288 | Vemos que la definición de nuestro array de matrices es exactamente igual salvo que en vez de querer un array de `felt` queremos uno de `Matrix`:
289 |
290 | ```python
291 | let (matrix_array : Matrix*) = alloc();
292 | ```
293 |
294 | Ya pasamos lo complicado 😴. Ahora veamos cómo rellenar nuestro array con estructuras `Matrix`. Usamos `assert` y podemos indexar con `[]` la posición del array que queremos alterar o revisar:
295 |
296 | ```
297 | assert matrix_array[0] = Matrix(x = v1, y = v2);
298 | ```
299 |
300 | Lo que hicimos fue crear una `Matrix(x = v1, y = v2)` y asignarla a la posición 0 de nuestra `matrix_array`. Recuerda que empezamos a contar desde 0. Rellenar nuestro array de `felt` es aún más trivial: `assert my_array[0] = 1`.
301 |
302 | Después simplemente llamamos de diferentes maneras a elementos dentro de `matrix_array`. Por ejemplo, con `matrix_array[1].x.elements[2]` indicamos estos pasos:
303 |
304 | 1. Llama al segundo, `[1]`, elemento de `matrix_array`. Es decir, a `Matrix(x = v1, y = v2)`.
305 | 2. Llama al `member` `x` de `Matrix`. Es decir, a `v1 = Vector(elements = my_array)`.
306 | 3. Llama al `member` `elements` de `v1`. Es decir, a `my_array`.
307 | 4. Llama al tercer, `[2]`, elemento de `my_array`. Es decir, a `3`.
308 |
309 | No es tan complicado pero es lo suficientemente satisfactorio 🤭.
310 |
311 | ## **6. Conclusión**
312 |
313 | Felicidades 🚀. Hemos profundizado en los básicos de 🏖 Cairo. Con este conocimiento puedes comenzar a hacer contratos sencillos en Cairo 🥳.
314 |
315 | En los siguientes tutoriales aprenderemos más sobre los el manejo de la memoria; la common library de cairo; cómo funciona el compilador de Cairo; y más!
316 |
317 | Cualquier comentario o mejora por favor comentar con [@espejelomar](https://twitter.com/espejelomar) o haz un PR 🌈.
318 |
--------------------------------------------------------------------------------
/tutorials/tutorials/EN/2_cairo_basics.md:
--------------------------------------------------------------------------------
1 | # Programming on Ethereum L2 (pt. 2): Cairo Basics 1
2 |
3 | Before starting, I recommend that you prepare your machine to program in Cairo ❤️ with the [past tutorial](1_installation.md).
4 |
5 | This is the second tutorial in a series focused on developing smart contracts with Cairo and StarkNet. I recommend that you do the previous tutorial before moving on to this one.
6 |
7 | 🚀 The future of Ethereum is today and it's already here. And it's just the beginning.
8 |
9 | ---
10 |
11 | ## 1. Add two numbers
12 |
13 | To learn the basics of Cairo we will create together a function to add two numbers 🎓. The code is very simple but it will help us to better understand many concepts of Cairo. We will be strongly based on the [Cairo documentation](https://www.cairo-lang.org/docs/). The documentation is excellent, as of today it is not ready to serve as a structured tutorial for beginners. Here we seek to solve this 🦙.
14 |
15 | Here is our code to add two numbers. You can paste it directly into your code editor or IDE. In my case I am using VSCode with the Cairo extension.
16 |
17 | Don't worry if you don't understand everything that's going on at this point. But [@espejelomar](https://twitter.com/espejelomar) will worry if by the end of the tutorial you don't understand every line of this code. Let me know if so because we will improve 🧐. Cairo is a low-level language so it will be more difficult than learning Python, for example. But it will be worth it 🥅. Eyes on the goal.
18 |
19 | Let's see line by line and with additional examples what we are doing. The entire program to add the two numbers is available in [src/sum.cairo](../../../src/sum.cairo). There you will find the code correctly commented.
20 |
21 | ```python
22 | %builtins output
23 |
24 | from starkware.cairo.common.serialize import serialize_word
25 |
26 | // @dev Add two numbers and return the result
27 | // @param num1 (felt): first number to add
28 | // @param num2 (felt): second number to add
29 | // @return sum (felt): value of the sum of the two numbers
30 | func sum_two_nums(num1: felt, num2: felt) -> (sum: felt) {
31 | alloc_locals;
32 | local sum = num1+num2;
33 | return (sum=sum);
34 | }
35 |
36 | func main{output_ptr: felt*}(){
37 | alloc_locals;
38 |
39 | const NUM1 = 1;
40 | const NUM2 = 10;
41 |
42 | let (sum) = sum_two_nums(num1 = NUM1, num2 = NUM2);
43 | serialize_word(sum);
44 | return ();
45 | }
46 | ```
47 |
48 | ## 2. The builtins
49 |
50 | At the beginning of our program in Cairo we write `%builtins output`. Here we are telling the Cairo compiler that we will use the `builtin` called `output`. The definition of `builtin` is quite technical and beyond the scope of this first tutorial ([here it is](https://www.cairo-lang.org/docs/how_cairo_works/builtins.html#builtins) in the documentation). For now, it suffices for us to point out that we can summon Cairo's special abilities through the builtins. If you know C++ surely you already found the similarities.
51 |
52 | > The builtin output is what allows the program to communicate with the outside world. You can think of it as the equivalent of `print()` in Python or `std::cout` in C++ ([Cairo documentation](https://www.cairo-lang.org/docs/hello_cairo/intro.html#writing-a-main-function)).
53 | >
54 |
55 | The interaction between `builtin` `output` and the `serialize_word` function, which we previously imported, will allow us to print to the console. In this case with `serialize_word(sum)`. Don't worry, we'll take a closer look at it later.
56 |
57 | ## 3. Importing
58 |
59 | Cairo is built on top of Python so importing functions and variables is exactly the same. The `from starkware.cairo.common.serialize import serialize_word` line is importing the `serialize_word` function found in `starkware.cairo.common.serialize`. To see the source code of this function, just go to the Github repository of `cairo-lang` ([link](https://github.com/starkware-libs/cairo-lang)). For example, the serialize function is found [here](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/serialize.cairo) within the repository. This will be useful for finding bugs in the code or understanding Cairo more thoroughly.
60 |
61 | > Multiple functions from the same library can be separated by commas. Functions from different libraries are imported on different lines. Cairo looks for each module in a default directory path and any additional paths specified at compile moment (Cairo documentation).
62 |
63 | This is how several functions are imported from the same library: `from starkware.cairo.common.math import (assert_not_zero, assert_not_equal)`.
64 |
65 | ## 4. The field elements (felt)
66 |
67 | In Cairo, when the type of a variable or argument is not specified, it is automatically assigned the type `felt`. The [Cairo documentation](https://www.cairo-lang.org/docs/hello_cairo/intro.html#the-primitive-type-field-element-felt) goes into technical detail about what is a `felt`. For the purposes of this tutorial, suffice it to say that a `felt` works as an integer. In the divisions we can notice the difference between the `felt` and the integers. However, quoting the documentation:
68 |
69 | > In most of your code (unless you intend to write very algebraic code), you won't have to deal with the fact that the values in Cairo are felts and you can treat them as if they were normal integers.
70 |
71 | ## 5. The structs (Cairo dictionaries?)
72 |
73 | In addition to `felt`, we have other structures at our disposal (more details in the [documentation](https://www.cairo-lang.org/docs/reference/syntax.html#type-system)).
74 |
75 | We can create our own structure, Python dictionary style:
76 |
77 | ```python
78 | struct MyStruct{
79 | first_member : felt,
80 | second_member : felt,
81 | }
82 |
83 | ```
84 | We define a new data type called `MyStruct` with the properties `first_member` and `second_member`. We set the `type` of both properties to be `felt` but we may as well have put in other types. When we create a `struct` it is mandatory to add the `type`.
85 |
86 | We can create a variable of type `MyStruct`: `Name = (first_member=1, second_member=4)`. Now the variable `Name` has `type` `MyStruct`.
87 |
88 | With `Name.first_member` we can access the value of this argument, in this case it is 1.
89 |
90 | ## **6. Tuples
91 |
92 | Tuples in Cairo are pretty much the same as tuples in Python:
93 |
94 | > A tuple is a finite, ordered, unalterable list of elements. It is represented as a comma-separated list of elements enclosed in parentheses (for example, (3, x)). Its elements can be of any combination of valid types. A tuple containing only one element must be defined in one of two ways: the element is a named tuple or it has a trailing comma. When passing a tuple as an argument, the type of each element can be specified per element (for example, my_tuple : (felt, felt, MyStruct)). Tuple values can be accessed with a zero-based index in parentheses [index], including access to nested tuple elements as shown below (Cairo documentation).
95 |
96 | The Cairo documentation is very clear in its definition of tuples. Here your example:
97 |
98 | ```python
99 | # A tuple with three elements
100 | local tuple0 : (felt, felt, felt) = (7, 9, 13)
101 | local tuple1 : (felt) = (5,) # (5) is not a valid tuple.
102 |
103 | # A named tuple does not require a trailing comma
104 | local tuple2 : (a : felt) = (a=5)
105 |
106 | # Tuple that contains another tuple.
107 | local tuple3 : (felt, (felt, felt, felt), felt) = (1, tuple0, 5)
108 | local tuple4 : ((felt, (felt, felt, felt), felt), felt, felt) = (
109 | tuple3, 2, 11)
110 | let a = tuple0[2] # let a = 13.
111 | let b = tuple4[0][1][2] # let b = 13.
112 |
113 | ```
114 |
115 | ## 7. The structure of functions and comments
116 |
117 | The definition of a function in Cairo has the following format:
118 |
119 | ```python
120 | func function(arg1: felt, arg2) -> (retornado: felt){
121 | // Function body
122 | let (sum) = sum_two_nums(num1 = NUM1, num2 = NUM2);
123 | return(returned=sum);
124 | }
125 |
126 | ```
127 |
128 | - **Define the scope of the function**. We start the function with `func`. The scope of our function is defined with curly braces {}.
129 | - **Arguments and names**. We define the arguments that the function receives in parentheses next to the name that we define for our function, `function` in this case. The arguments can carry their type defined or not. In this case `arg1` must be of type `felt` and `arg2` can be of any type.
130 | - **Return**. We necessarily have to add `return()`. Although the function is not returning something. In this case we are returning a variable called `returned` so we put `return(returned=sum)` where sum is the value that the `returned` variable will take.
131 | - **Comments**. In Cairo we comment with `//`. This code will not be interpreted when running our program.
132 |
133 | As with other programming languages. We will need a `main()` function that orchestrates the use of our program in Cairo. It is defined exactly the same as a normal function only with the name `main()`. It can come before or after the other functions we create in our program.
134 |
135 | ## 8. Interacting with pointers: part 1
136 |
137 | > A pointer is used to indicate the address of the first felt of an element in memory. The pointer can be used to access the element efficiently. For example, a function can accept a pointer as an argument and then access the element at the pointer's address (Cairo documentation).
138 |
139 | Suppose we have a variable named `var`:
140 |
141 | - `var*` is a pointer to the memory address of the `var` object.
142 | - `[var]` is the value stored at address `var*`.
143 | - `&var` is the address to the `var` object.
144 | - `&[x]` is `x`. Can you see that `x` is a address?
145 |
146 | # 9. Implicit arguments
147 |
148 | Before explaining how implicit arguments work, a rule: If a `foo()` function calls a function with an implicit argument, `foo()` must also get and return the same implicit argument.
149 |
150 | With that said, let's see what a function with an implicit argument looks like. The function is serialize_word which is available in the `starkware.cairo.common.serialize` library and we use it in our initial function to add two numbers.
151 |
152 | ```python
153 | %builtins output
154 |
155 | // Appends a single word to the output pointer, and returns the pointer to the next output cell.
156 | func serialize_word{output_ptr: felt*}(word) {
157 | assert [output_ptr] = word;
158 | let output_ptr = output_ptr + 1;
159 | return ();
160 | }
161 | ```
162 |
163 | This will be a bit confusing, be prepared. In this and many other cases, it receives `output_ptr`, which is a pointer to a felt type. When we declare that a function receives an implicit argument, the function will automatically return the value of the implicit argument on termination of the function. If we didn't move the value of the implicit argument then it would automatically return the same value it started with. However, if during the function the value of the implicit argument is altered then the new value will be automatically returned.
164 |
165 | In the example with the `serialize_word` function we define that we are going to receive an implicit argument called `output_ptr`. In addition, we also receive an explicit argument called `value`. At the end of the function we will return the value that `output_ptr` has at that moment. During the function we see that `output_ptr`increases by 1: `let output_ptr = output_ptr + 1`. Then the function will implicitly return the new value of `output_ptr`.
166 |
167 | Following the rule defined at the beginning, any function that calls `serialize_word` will also have to receive the implicit `output_ptr`. For example, part of our function to add two numbers goes like this:
168 |
169 | ```python
170 | func main{output_ptr: felt*}():
171 | alloc_locals
172 |
173 | const NUM1 = 1
174 | const NUM2 = 10
175 |
176 | let (sum) = sum_two_nums(num1 = NUM1, num2 = NUM2)
177 | serialize_word(word=sum)
178 | return ()
179 | end
180 | ```
181 |
182 | We see that we call `serialize_word` so we necessarily have to also ask for the implicit argument `output_ptr` in our `main` function. This is where another property of implicit arguments comes into play, and perhaps why they are called that. We see that when calling `serialize_word` we only pass the explicit `word` argument. The implicit argument `output_ptr` is automatically passed 🤯! Be careful, we could also have made the implicit argument explicit like this: `serialize_word{output_ptr=output_ptr}(word=a)`. Do we already know how to program in Cairo? 🙉
183 |
184 | So the implicit argument is implicit because:
185 |
186 | 1. Inside the implicit function, the final value of the implicit argument is automatically returned.
187 | 2. When the implicit function is called, we do not need to indicate that we are going to pass the implicit argument. The default value is automatically included.
188 |
189 | ## 10. Locals
190 |
191 | We are almost ready to understand 100% what we did in our function that adds two numbers. I know, it's been a rocky road 🙉. But there is a rainbow at the end of the tutorial 🌈.
192 |
193 | Thus we define a local variable: `local a = 3`.
194 |
195 | > Any function that uses a local variable must have an `alloc_locals` declaration, usually at the beginning of the function. This statement is responsible for allocating memory cells used by local variables within the scope of the function (Cairo [documentation](https://www.cairo-lang.org/docs/reference/syntax.html#locals)).
196 | >
197 |
198 | As an example, look at this part of our function that adds two numbers:
199 |
200 | ```python
201 | func sum_two_nums(num1: felt, num2: felt) -> (sum):
202 | alloc_locals
203 | local sum = num1+num2
204 | return(sum)
205 | end
206 |
207 | ```
208 |
209 | It's very simple 💛.
210 |
211 | Since we don't want it to be so easy, let's talk from memory. Cairo stores the local variables relative to the frame pointer (`fp`) (we'll go into more detail about the `fp` in a later tutorial). So if we needed the address of a local variable, `&sum` would not suffice as it would give us this error: `using the value fp directly requires defining a variable __fp__`. We can get this value by importing `from starkware.cairo.common.registers import get_fp_and_pc`. `get_fp_and_pc` returns a tuple with the current values of `fp` and `pc`. In the most Python style, we will indicate that we are only interested in the value of `fp` and that we will store it in a variable `__fp__`: `let (__fp__, _) = get_fp_and_pc()`. Done now we could use `&sum`. In another tutorial we will see an example of this.
212 |
213 | ## **11. Constants
214 |
215 | Very simple. Just remember that they must give an integer (a field) when we compile our code. Create a constant:
216 |
217 | ```python
218 | const NUM1 = 1
219 |
220 | ```
221 |
222 | ## **12. References
223 |
224 | This is the format to define one:
225 |
226 | ```python
227 | let ref_name : ref_type = ref_expr
228 |
229 | ```
230 |
231 | Where `ref_type` is a type and `ref_expr` is a Cairo expression. Placing the `ref_type` is optional but it is recommended to do so.
232 |
233 | A reference can be reassigned (Cairo [documentation](https://www.cairo-lang.org/docs/reference/syntax.html#references)):
234 |
235 | ```python
236 | let a = 7 // a is initially bound to expression 7.
237 | let a = 8 // a is now bound to expression 8.
238 |
239 | ```
240 | In our addition of two numbers we create a reference called `sum`. We see that we assign to `sum` the `felt` that the function `sum_two_nums` returns.
241 |
242 | ```python
243 | let (sum) = sum_two_nums(num1 = NUM1, num2 = NUM2)
244 |
245 | ```
246 |
247 | ## 13. Compile and run 𓀀
248 |
249 | You already know how to do funtions in Cairo! Now let's run our first program.
250 |
251 | The tools that StarkNet offers to interact with the command line are many 🙉. We won't go into detail until later. For now, we will only show the commands with which we can run the application that we created in this tutorial 🧘♀️. But don't worry, the commands to run other applications will be very similar.
252 |
253 | `cairo-compile` allows us to compile our code and export a json that we will read in the next command. If ours is called `src/sun.cairo` (because it is located in the `src` directory as in this repository) and we want the json to be called `build/sum.json` (because it's in the `build` directory like this repository) then we would use the following code:
254 |
255 | ```
256 | cairo-compile src/sum.cairo --output build/sum.json`
257 | ```
258 |
259 | Simple, right? ❤️
260 |
261 | Ok now let's run our program with `cairo-run`.
262 |
263 | ```
264 | cairo-run --program=build/sum.json --print_output --layout=small
265 | ```
266 |
267 | The result should correctly print an 11 in our terminal.
268 |
269 | Here the details:
270 |
271 | We indicate in the `--program` argument that we want to run the `build/sum.json` that we generated earlier.
272 |
273 | With `--print_output` we indicate that we want to print something from our program to the terminal. For example, in the next tutorial we will use the builtin (we will study them later) output and the serialize_word function to print to the terminal.
274 |
275 | `--layout` allows us to indicate the layout to use. Depending on the builtins we use, it will be the layout to use. Later we will be using the output builtin and for this we need the small layout. If we will not use any builtin then we can leave this argument empty so we would use the default layout, the plain layout.
276 |
277 | ## **14. Conclusion**
278 |
279 | Congratulations 🚀. We have learned the basics of 🏖 Cairo. With this knowledge you could identify what is done in each line of our function that adds two integers 🥳. In the following tutorials we will learn more about pointers and memory management; the cairo common library; how the Cairo compiler works; and more!
280 |
281 | Any comments or improvements please comment with [@espejelomar](https://twitter.com/espejelomar) or make a PR 🌈.
--------------------------------------------------------------------------------
/tutorials/tutorials/PT/2_cairo_basics.md:
--------------------------------------------------------------------------------
1 | # Programação em Ethereum L2 (pt. 2): Básicas do Cairo 1
2 |
3 | Antes de começar, Eu recomendo que você prepare seu equipamento para programar no Cairo ❤️ com o [tutorial passado](1_installation.md).
4 |
5 | Este é o segundo tutorial de uma série focada no desenvolvimento de contratos inteligentes com Cairo e StarkNet. Recomendo que você faça o último tutorial antes de passar para este.
6 |
7 |
8 | ---
9 |
10 | ## 1. Somar dois números
11 |
12 | Para aprender o básico do Cairo vamos criar juntos uma função para somar dois números 🎓. O código é muito simples, mas nos ajudará a entender melhor muitos conceitos do Cairo. Dependeremos muito da [documentação do Cairo](https://www.cairo-lang.org/docs/). A documentação é excelente, até hoje não está pronta para servir como um tutorial estruturado para iniciantes. Aqui buscamos resolver isso.
13 |
14 | Aqui está o nosso código para somar dois números. Você pode colá-lo diretamente em seu editor de código ou IDE. No meu caso estou usando o VSCode com a extensão Cairo.
15 |
16 | Não se preocupe se você não entender tudo o que está acontecendo neste momento. Mas [@espejelomar](https://twitter.com/espejelomar) vai se preocupar se no final do tutorial você não entender cada linha deste código. Deixe-me saber se sim porque vamos melhorar 🧐. Cairo é uma linguagem low-level, então será mais difícil do que aprender Python, por exemplo. Mas vai valer a pena 🥅. Olhos no gol.
17 |
18 | Vamos ver linha por linha e com exemplos adicionais o que estamos fazendo. Todo o programa para somar os dois números está disponível em [src/sum.cairo](../../../src/sum.cairo). Lá você encontrará o código corretamente comentado.
19 |
20 | ```python
21 | %builtins output
22 |
23 | from starkware.cairo.common.serialize import serialize_word
24 |
25 | // @dev Add two numbers and return the result
26 | // @param num1 (felt): first number to add
27 | // @param num2 (felt): second number to add
28 | // @return sum (felt): value of the sum of the two numbers
29 | func sum_two_nums(num1: felt, num2: felt) -> (sum: felt) {
30 | alloc_locals;
31 | local sum = num1+num2;
32 | return (sum=sum);
33 | }
34 |
35 | func main{output_ptr: felt*}(){
36 | alloc_locals;
37 |
38 | const NUM1 = 1;
39 | const NUM2 = 10;
40 |
41 | let (sum) = sum_two_nums(num1 = NUM1, num2 = NUM2);
42 | serialize_word(sum);
43 | return ();
44 | }
45 |
46 | ```
47 |
48 | ## 2. Os builtins
49 |
50 | No início do nosso programa no Cairo escrevemos `%builtins output`. Aqui estamos dizendo ao compilador do Cairo que usaremos o `builtin` chamado `output`. A definição de `builtin` é bastante técnica e está além do escopo deste primeiro tutorial ([aqui está](https://www.cairo-lang.org/docs/how_cairo_works/builtins.html#builtins) na documentação) . Por enquanto, basta apontar que podemos convocar as habilidades especiais do Cairo através dos recursos internos. Se você conhece C++ certamente já encontrou as semelhanças.
51 |
52 | > Builtin output é o que permite que o programa se comunique com o mundo exterior. Você pode pensar nisso como o equivalente a `print()` em Python ou `std::cout` em C++ ([documentação do Cairo](https://www.cairo-lang.org/docs/hello_cairo/intro.html #escrevendo -a-função-principal)).
53 |
54 | A interação entre `builtin` `output` e a função `serialize_word`, que importamos anteriormente, nos permitirá imprimir no console. Neste caso com `serialize_word(sum)`. Não se preocupe, vamos dar uma olhada nisso mais tarde.
55 |
56 |
57 | ## 3. Importando
58 |
59 | Cairo é construído em cima do Python, então importar funções e variáveis é exatamente o mesmo. A linha `from starkware.cairo.common.serialize import serialize_word` está importando a função `serialize_word` encontrada em `starkware.cairo.common.serialize`. Para visualizar o código fonte desta função, basta acessar o repositório do Github `cairo-lang` ([link](https://github.com/starkware-libs/cairo-lang)). Por exemplo, a função serialize é encontrada [aqui](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/serialize.cairo) dentro do repositório. Isso será útil para encontrar bugs no código ou entender o Cairo mais profundamente.
60 |
61 | > Várias funções da mesma biblioteca podem ser separadas por vírgulas. Funções de diferentes bibliotecas são importadas em diferentes linhas. Cairo procura cada módulo em um caminho de diretório padrão e quaisquer caminhos adicionais especificados em tempo de compilação (documentação do Cairo).
62 | >
63 |
64 | É assim que várias funções são importadas da mesma biblioteca: `from starkware.cairo.common.math import (assert_not_zero, assert_not_equal)`.
65 |
66 | ## 4. Field elements (felt)
67 |
68 | No Cairo, quando o type de uma variável ou argumento não é especificado, é atribuído automaticamente o tipo `felt`. A [documentação do Cairo](https://www.cairo-lang.org/docs/hello_cairo/intro.html#the-primitive-type-field-element-felt) entra em detalhes técnicos sobre o que um `sentido`. Para os propósitos deste tutorial, basta dizer que um `felt` funciona como um inteiro. Nas divisões podemos notar a diferença entre o `felt` e os inteiros. No entanto, citando a documentação:
69 |
70 | > Na maior parte do seu código (a menos que você pretenda escrever código muito algébrico), você não terá que lidar com o fato de que os valores no Cairo são felts e você pode tratá-los como se fossem inteiros normais.
71 | >
72 |
73 | ## 5. Struct (dicionários do Cairo?)
74 |
75 | Além do `felt`, temos outras estruturas à nossa disposição (mais detalhes na [documentação](https://www.cairo-lang.org/docs/reference/syntax.html#type-system)).
76 |
77 | Podemos criar nossa própria estrutura, estilo dicionário Python:
78 |
79 | ```python
80 | struct MyStruct{
81 | first_member : felt,
82 | second_member : felt,
83 | }
84 |
85 | ```
86 |
87 | Então definimos um novo tipo de dados chamado `MyStruct` com as propriedades `first_member` e `second_member`. Definimos o `type` de ambas as propriedades como `felt`, mas também podemos ter colocado outros tipos. Quando criamos um `struct` é obrigatório adicionar o `type`.
88 |
89 | Podemos criar uma variável do tipo `MyStruct`: `name = (first_member=1, second_member=4)`. Agora a variável `name` tem `type` `MyStruct`.
90 |
91 | Com `name.first_member` podemos acessar o valor deste argumento, neste caso é 1.
92 |
93 |
94 | ## **6. As tuplas (tuples, em inglês)**
95 |
96 | Tuplas no Cairo são praticamente as mesmas tuplas em Python:
97 |
98 | > Uma tupla é uma lista finita, ordenada e inalterável de elementos. Ele é representado como uma lista de elementos separados por vírgulas entre parênteses (por exemplo, (3, x)). Seus elementos podem ser de qualquer combinação de tipos válidos. Uma tupla contendo apenas um elemento deve ser definida de duas maneiras: o elemento é uma tupla nomeada ou tem uma vírgula à direita. Ao passar uma tupla como argumento, o tipo de cada elemento pode ser especificado por elemento (por exemplo, my_tuple : (felt, felt, MyStruct)). Os valores de tupla podem ser acessados com um índice baseado em zero entre parênteses [índice], incluindo acesso a elementos de tupla aninhados conforme mostrado abaixo (documentação do Cairo).
99 | >
100 |
101 | A documentação do Cairo é muito clara em sua definição de tuplas. Aqui seu exemplo:
102 |
103 | ```python
104 | # A tuple with three elements
105 | local tuple0 : (felt, felt, felt) = (7, 9, 13)
106 | local tuple1 : (felt) = (5,) # (5) is not a valid tuple.
107 |
108 | # A named tuple does not require a trailing comma
109 | local tuple2 : (a : felt) = (a=5)
110 |
111 | # Tuple containing another tuple.
112 | local tuple3 : (felt, (felt, felt, felt), felt) = (1, tuple0, 5)
113 | local tuple4 : ((felt, (felt, felt, felt), felt), felt, felt) = (
114 | tuple3, 2, 11)
115 | let a = tuple0[2] # let a = 13.
116 | let b = tuple4[0][1][2] # let b = 13.
117 |
118 | ```
119 |
120 | ## 7. A estrutura de funções e comentários
121 |
122 | A definição de uma função no Cairo tem o seguinte formato:
123 |
124 | ```python
125 | func function(arg1: felt, arg2) -> (retornado: felt){
126 | // Function body
127 | let (sum) = sum_two_nums(num1 = NUM1, num2 = NUM2);
128 | return(returned=sum);
129 | }
130 |
131 | ```
132 |
133 | - **Definir o scope da função**. Iniciamos a função com `func`. O scope de nossa função é definido com chaves {}.
134 | - **Argumentos e nome**. Definimos os argumentos que a função recebe entre parênteses ao lado do nome que definimos para nossa função, `function` neste caso. Os argumentos podem levar seu type (tipo, em português) definido ou não. Neste caso, `arg1` deve ser do type `felt` e `arg2` pode ser de qualquer type.
135 | - **Retornar**. Nós necessariamente temos que adicionar `return()`. Embora a função não esteja retornando nada. Neste caso estamos retornando uma variável chamada `returned` então colocamos `return(returned=sum)` onde sum é o valor que a variável `returned` irá receber.
136 | - **Comentários**. No Cairo comentamos com `//`. Este código não será interpretado ao executar nosso programa.
137 |
138 | Assim como outras linguagens de programação. Precisaremos de uma função `main()` que orquestre o uso do nosso programa no Cairo. Ela é definida exatamente da mesma forma que uma função normal apenas com o nome `main()`. Pode vir antes ou depois das outras funções que criamos em nosso programa.
139 |
140 |
141 | ## 8. Interagindo com pointers (ponteiros, em português): parte 1
142 |
143 | > Um pointer é utilizado para indicar o endereço do primeiro felt de um elemento na memória. O pointer pode ser utilizado para aceder ao elemento de forma eficiente. Por exemplo, uma função pode aceitar um ponteiro como argumento e depois aceder ao elemento no endereço do ponteiro (documentação do Cairo).
144 | >
145 |
146 | Suponhamos que temos uma variável chamada `var`:
147 |
148 | - `var*` é um pointer para o endereço de memória do objeto `var`.
149 | - `[var]` é o valor armazenado no endereço `var*`.
150 | - `&var` é o endereço para o objeto `var`.
151 | - `&[x]` é `x`. Você pode ver que `x` é um endereço?
152 |
153 | ## 9. Argumentos implícitos
154 |
155 | Antes de explicar como os argumentos implícitos funcionam, uma regra: Se uma função `foo()` chama uma função com um argumento implícito, `foo()` também deve obter e retornar o mesmo argumento implícito.
156 |
157 | Com isso dito, vamos ver como é uma função com um argumento implícito. A função é serialize_word que está disponível na biblioteca `starkware.cairo.common.serialize` e nós a usamos em nossa função inicial para somar dois números.
158 |
159 | ```python
160 | %builtins output
161 |
162 | func serialize_word{output_ptr : felt*}(word : felt){
163 | assert [output_ptr] = word
164 | let output_ptr = output_ptr + 1
165 | # The new value of output_ptr is implicitly
166 | # added in return.
167 | return ()
168 | }
169 |
170 | ```
171 |
172 | Isso vai ser um pouco confuso, esteja preparado. Vou tentar deixar tudo bem claro 🤗. Para uma função receber argumentos implícitos, colocamos o argumento entre `{}`. Neste e em muitos outros casos, `output_ptr` é recebido, que é um pointer para um type felt. Quando declaramos que uma função recebe um argumento implícito, a função retornará automaticamente o valor do argumento implícito no término da função. Se não movermos o valor do argumento implícito, ele retornaria automaticamente o mesmo valor com o qual começou. No entanto, se durante a função o valor do argumento implícito for alterado, o novo valor será retornado automaticamente.
173 |
174 | No exemplo com a função `serialize_word` definimos que vamos receber um argumento implícito chamado `output_ptr`. Além disso, também recebemos um argumento explícito chamado `value`. Ao final da função retornaremos o valor que `output_ptr` possui naquele momento. Durante a função vemos que `output_ptr` aumenta em 1: `let output_ptr = output_ptr + 1`. Então a função retornará implicitamente o novo valor de `output_ptr`.
175 |
176 | Seguindo a regra definida no início, qualquer função que chame `serialize_word` também terá que receber o argumento implícito `output_ptr`. Por exemplo, parte da nossa função para somar dois números é assim:
177 |
178 | ```python
179 | func main{output_ptr: felt*}(){
180 | alloc_locals
181 |
182 | const NUM1 = 1
183 | const NUM2 = 10
184 |
185 | let (sum) = sum_two_nums(num1 = NUM1, num2 = NUM2)
186 | serialize_word(word=sum)
187 | return ()
188 | }
189 |
190 | ```
191 | Vemos que chamamos `serialize_word` então necessariamente temos que pedir também o argumento `output_ptr` implícito em nossa função `main`. É aqui que outra propriedade dos argumentos implícitos entra em jogo, e talvez por que eles são chamados assim. Vemos que ao chamar `serialize_word` passamos apenas o argumento `word` explícito. O argumento implícito `output_ptr` é passado automaticamente 🤯! Tenha cuidado, também poderíamos ter tornado o argumento implícito explícito assim: `serialize_word{output_ptr=output_ptr}(word=a)`. Já sabemos programar no Cairo? 🙉
192 |
193 | Portanto, o argumento implícito é implícito porque:
194 |
195 | 1. Dentro da função implícita, o valor final do argumento implícito é retornado automaticamente.
196 | 2. Quando a função implícita é chamada, não precisamos indicar que vamos passar o argumento implícito. O valor padrão é incluído automaticamente.
197 |
198 | ## 10. Locals (locais, em português)
199 |
200 | Estamos quase prontos para entender 100% o que fizemos em nossa função que soma dois números. Eu sei, tem sido uma estrada rochosa 🙉. Mas há um arco-íris no final do tutorial 🌈.
201 |
202 | Então definimos uma variável local: `local a = 3`.
203 |
204 | > Qualquer função que use uma variável local deve ter uma declaração alloc_locals, geralmente no início da função. Esta instrução é responsável por alocar células de memória usadas por variáveis locais dentro do escopo da função (documentação do Cairo).
205 | >
206 |
207 | Como exemplo, veja esta parte da nossa função que adiciona dois números:
208 |
209 | ```python
210 | func sum_two_nums(num1: felt, num2: felt) -> (sum){
211 | alloc_locals
212 | local sum = num1+num2
213 | return(sum)
214 | }
215 |
216 | ```
217 |
218 | É muito simples 💛.
219 |
220 | Já que não queremos que seja tão fácil, vamos falar de memória. O Cairo armazena variáveis locais relativas ao frame pointer (`fp`) (entraremos em mais detalhes sobre `fp` em um tutorial posterior). Portanto, se precisássemos do endereço de uma variável local, `&sum` não seria suficiente, pois nos daria este erro: `usar o valor fp diretamente requer a definição de uma variável __fp__`. Podemos obter este valor importando `from starkware.cairo.common.registers import get_fp_and_pc`. `get_fp_and_pc` retorna uma tupla com os valores atuais de `fp` e `pc`. No estilo mais Python, indicaremos que estamos interessados apenas no valor de `fp` e que iremos armazená-lo em uma variável `__fp__`: `let (__fp__, _) = get_fp_and_pc()`. Feito agora podemos usar `&sum`. Em outro tutorial veremos um exemplo disso.
221 |
222 | ## **11. Constants (constantes, em português)**.
223 |
224 | Muito simples. Apenas lembre-se que eles devem fornecer um inteiro (um field) quando compilamos nosso código. Crie uma constante:
225 |
226 | ```python
227 | const NUM1 = 1
228 |
229 | ```
230 |
231 | ## **12. References (referências, em português)**
232 |
233 | Este é o formato para definir um:
234 |
235 | ```python
236 | let ref_name : ref_type = ref_expr
237 |
238 | ```
239 |
240 | Onde `ref_type` é um type e `ref_expr` é uma expressão do Cairo. Colocar o `ref_type` é opcional, mas é recomendado fazê-lo.
241 |
242 | Uma referência pode ser reatribuída ([documentação](https://www.cairo-lang.org/docs/reference/syntax.html#references) do Cairo):
243 |
244 | ```python
245 | let a = 7 # a is initially bound to the expression 7.
246 | let a = 8 # a is now bound to the expression 8.
247 |
248 | ```
249 |
250 | Em nossa soma de dois números, criamos uma referência chamada `sum`. Vemos que atribuímos a `sum` o `felt` que a função `sum_two_nums` retorna.
251 |
252 | ```python
253 | let (sum) = sum_two_nums(num1 = NUM1, num2 = NUM2)
254 |
255 | ```
256 |
257 | ## 13. Compile e execute 𓀀
258 |
259 | Você já sabe como fazer funções no Cairo! Agora vamos executar nosso primeiro programa.
260 |
261 | As ferramentas que a StarkNet oferece para interagir com a linha de comando são muitas 🙉. Não entraremos em detalhes até mais tarde. Por enquanto, mostraremos apenas os comandos com os quais podemos executar o aplicativo que criamos neste tutorial 🧘♀️. Mas não se preocupe, os comandos para rodar outros aplicativos serão bem parecidos.
262 |
263 | `cairo-compile` nos permite compilar nosso código e exportar um json que leremos no próximo comando. Se o nosso é chamado `src/sum.cairo` (porque está no diretório `src` como neste repositório) e queremos que o json seja chamado `build/sum.json` (porque está no `build` como em este repositório), então usaríamos o seguinte código:
264 |
265 | ```
266 | cairo-compile src/sum.cairo --output build/sum.json`
267 | ```
268 |
269 | Simples, certo? ❤️
270 |
271 | Ok, agora vamos executar nosso programa com `cairo-run`.
272 |
273 | ```
274 | cairo-run --program=build/sum.json --print_output --layout=small
275 | ```
276 |
277 | O resultado deve imprimir corretamente um 11 em nosso terminal.
278 |
279 | Aqui os detalhes:
280 |
281 | Indicamos no argumento --program que queremos executar o build/sum.json que geramos anteriormente.
282 |
283 | Com --print_output indicamos que queremos imprimir algo do nosso programa no terminal. Por exemplo, no próximo tutorial usaremos builtin (vamos estudá-los mais tarde) output e a função serialize_word para imprimir no terminal.
284 |
285 | --layout nos permite indicar o layout a ser usado. Dependendo dos builtins que usamos, será o layout a ser usado. Mais tarde estaremos usando builtin output e para isso precisamos do layout small. Se não usarmos nenhum builtin, podemos deixar este argumento vazio para que possamos usar o layout default, o layout plain.
286 |
287 | ## **14. Conclusão**
288 |
289 | Parabéns 🚀. Aprendemos o básico do 🏖 Cairo. Com esse conhecimento você poderia identificar o que é feito em cada linha da nossa função que soma dois inteiros .
290 |
291 | Nos tutoriais a seguir, aprenderemos mais sobre pointers e gerenciamento de memória; a common library do Cairo; como funciona o compilador do Cairo; e mais!
292 |
293 | Quaisquer comentários ou melhorias, por favor, comente com [@espejelomar](https://twitter.com/espejelomar) ou faça um PR 🌈.
294 |
--------------------------------------------------------------------------------