├── 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 | --------------------------------------------------------------------------------