├── .gitignore ├── README.md ├── ape ├── ape-config.yaml ├── contracts │ ├── array_contract.cairo │ └── mock_contract.cairo └── tests │ ├── conftest.py │ └── test_array_contract.py ├── hardhat ├── .npmignore ├── .prettierignore ├── .prettierrc ├── contracts │ ├── array_contract.cairo │ └── mock_contract.cairo ├── hardhat.config.ts ├── package.json ├── test │ └── test.spec.ts └── tsconfig.json ├── protostar ├── bigBrainDebug │ └── server.py ├── lib │ └── .gitkeep ├── protostar.toml ├── src │ ├── array_contract.cairo │ └── mock_contract.cairo └── tests │ ├── test_array_contract.cairo │ └── test_mock_contract.cairo └── python ├── contracts ├── array_contract.cairo └── mock_contract.cairo └── tests ├── test_array_contract.py └── test_mock_contract.py /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | cache 3 | starknet-artifacts 4 | __pycache__ 5 | 6 | */yarn.lock 7 | package-lock.json 8 | .build/ 9 | mock_logs.txt 10 | array_logs.txt 11 | .idea 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DISCLAIMER 2 | Hello Starknet community, this repository is not updated with the latest Cairo syntax as well as informaiton about the updated relevant tools and hence, we do not recommend to attempt this tutorial as of today. If you are interested in contributing to the repository to update the tutorial, please create a PR and tag me @gyan0890 on it and we will be happy to support you with the process. 3 | 4 | A great resource to get you up to speed with the new Cairo syntax in a Starknet context is [Chapter 2 of the Starknet Book](https://book.starknet.io/chapter_2/index.html). 5 | 6 | You can also ping me(@gyanlakshmi) on Telegram to help you assign the right tasks. 7 | 8 | # Starknet debugging 9 | 10 | Welcome! This tutorial will show you how to use StarkNet development tools to debug a smart contract using 3 different testing frameworks: 11 | 12 | - [Hardhat](https://github.com/Shard-Labs/starknet-hardhat-plugin) 13 | - [StarkNet's native framework](https://www.cairo-lang.org/docs/hello_starknet/unit_tests.html) 14 | - [Protostar](https://docs.swmansion.com/protostar/docs/tutorials/introduction) 15 | - [Ape](https://github.com/ApeWorX/ape-starknet) 16 | 17 | We'll use a functionality of the Cairo language called [hints](https://starknet.io/docs/how_cairo_works/hints.html), which allows you to inject python arbitrarily in your code. Hint usage is heavily restricted on StarkNet and is unapplicable in Smart Contracts. But it is extremely useful for debugging your contract. These debugging patterns are proposals we made, feel free to use any others you can think of and create a PR so everyone can know! 18 | 19 | ## Introduction 20 | 21 | ### How it works 22 | 23 | For each framework, there is a folder with broken or incomplete smart contracts that you need to fix. Each framework also includes a set of tests that the contract needs to pass. 24 | 25 | Your objective is to use the debugging features outlined in the readme to understand the bug, fix it, and have the tests pass. 26 | 27 | ### Where am I? 28 | 29 | This workshop is the fifth in a series aimed at teaching how to build on StarkNet. Checkout out the following: 30 | 31 | |Topic|GitHub repo| 32 | |---|---| 33 | |Learn how to read Cairo code |[Cairo 101](https://github.com/starknet-edu/starknet-cairo-101)| 34 | |Deploy and customize an ERC721 NFT |[StarkNet ERC721](https://github.com/starknet-edu/starknet-erc721)| 35 | |Deploy and customize an ERC20 token|[StarkNet ERC20](https://github.com/starknet-edu/starknet-erc20)| 36 | |Build a cross layer application|[StarkNet messaging bridge](https://github.com/starknet-edu/starknet-messaging-bridge)| 37 | |Debug your Cairo contracts easily (you are here)|[StarkNet debug](https://github.com/starknet-edu/starknet-debug)| 38 | |Design your own account contract|[StarkNet account abstraction](https://github.com/starknet-edu/starknet-accounts)| 39 | 40 | ### Providing feedback & getting help 41 | 42 | Once you are done working on this tutorial, your feedback would be greatly appreciated! 43 | 44 | **Please fill out [this form](https://forms.reform.app/starkware/untitled-form-4/kaes2e) to let us know what we can do to make it better.** 45 | 46 | ​ 47 | And if you struggle to move forward, do let us know! This workshop is meant to be as accessible as possible; we want to know if it's not the case. 48 | 49 | ​ 50 | Do you have a question? Join our [Discord server](https://starknet.io/discord), register, and join channel #tutorials-support 51 | ​ 52 | Are you interested in following online workshops about learning how to dev on StarkNet? [Subscribe here](http://eepurl.com/hFnpQ5) 53 | 54 | ### Contributing 55 | 56 | This project can be made better and will evolve as StarkNet matures. Your contributions are welcome! Here are things that you can do to help: 57 | 58 | - Create a branch with a translation to your language 59 | - Correct bugs if you find some 60 | - Add an explanation in the comments of the exercise if you feel it needs more explanation 61 | 62 | ​ 63 | ​ 64 | 65 | ## Getting started 66 | 67 | ### Smart contracts that need fixing 68 | 69 | The first smart contract [`array_contract.cairo`](python/contracts/array_contract.cairo) is a dummy smart contract which has a [view function](https://www.cairo-lang.org/docs/hello_starknet/intro.html) that needs to: 70 | 71 | - Compute the product of each array element. 72 | - Add the current contract’s address to it. 73 | 74 | The second smart contract [`mock_contract.cairo`](python/contracts/mock_contract.cairo) is also a dummy smart contract that will save a given array on initialization into a mapping and has a view function that needs: 75 | 76 | - Compute the product of each mapping element. 77 | 78 | For each of those contracts and each framework, your goal is for the tests to pass successfully. 79 | ​ 80 | ​ 81 | 82 | ## Debugging with Hardhat 83 | 84 | ### Installing 85 | 86 | Run the following command in the hardhat directory to install all dependencies, as well `cairo-lang` and the [`starknet-devnet`](https://github.com/Shard-Labs/starknet-devnet). 87 | 88 | ```bash 89 | npm i 90 | pip install "starknet-devnet>=0.2.1" cairo-lang 91 | ``` 92 | 93 | To setup a full `cairo` env you can take a look at [this article](https://medium.com/starknet-edu/the-ultimate-starknet-dev-environment-716724aef4a7). 94 | 95 | ### Including hints in your contract 96 | 97 | By default, StarkNet contracts can not use any [hints](https://starknet.io/docs/how_cairo_works/hints.html) in their code. `cairo-lang` refuses to compile contracts including hints. 98 | 99 | However, starknet-devnet lets you run hints if you compile your contracts so that they include them. 100 | 101 | Luckily, an flag has been added in hardhat to compile a contract that includes hints: `--disable-hint-validation`. 102 | 103 | ***To debug a smart contract using hardhat you may want to print variables, there is an example in the smart contract.*** 104 | 105 | ### Running tests 106 | 107 | The following command will run the hardhat tests that need to pass, and execute any hints included in the contract in the terminal 108 | 109 | ```bash 110 | npm run test 111 | ``` 112 | 113 | Your goal is to have this test pass. 114 | 115 | The contracts to fix are [here](hardhat/contracts). 116 | 117 | You can find the test file [here](hardhat/test/test.spec.ts). 118 | 119 | ​ 120 | ​ 121 | 122 | ## Python 123 | 124 | ### Installing 125 | 126 | To run the python unit test files you'll need pytest and asynctest rich 127 | 128 | ```bash 129 | pip install pytest asynctest cairo-lang 130 | ``` 131 | 132 | ### Running tests 133 | 134 | The Python testing framework doesn't need to interact with the `starknet-devnet` as it can natively use the testing functions from the `cairo-lang` package so you can also use any python hint you want. 135 | 136 | You can even add a `breakpoint` in a contract. How powerful is that? But wait that's not it you can also inspect the memory stack and the state variables! How cool is that? 137 | 138 | You can run your python unit tests with pytest and inspect whatever you want in your contract. 139 | 140 | The contracts to fix are [here](python/contracts). 141 | 142 | You can find the test script [here](python/test). 143 | 144 | Run the tests separately using: 145 | 146 | ```bash 147 | pytest tests/test_array_contract.py -s -W ignore::DeprecationWarning 148 | ``` 149 | 150 | ```bash 151 | pytest tests/test_mock_contract.py -s -W ignore::DeprecationWarning 152 | ``` 153 | 154 | Or all at once with: 155 | 156 | ```bash 157 | pytest -s -W ignore::DeprecationWarning 158 | ``` 159 | 160 | ​ 161 | 162 | ## Protostar 163 | 164 | ### Installing 165 | 166 | To run the Protostar unit test files you'll need to install `protostar`, `flask` and `requests`: 167 | 168 | ```bash 169 | pip install flask requests 170 | curl -L https://raw.githubusercontent.com/software-mansion/protostar/master/install.sh | bash 171 | ``` 172 | 173 | Check the [documentation](https://docs.swmansion.com/protostar/docs/tutorials/installation) for more details. 174 | 175 | ### Running tests 176 | 177 | As with the Python testing framework, Protostar doesn't need to interact with the `starknet-devnet` as it can natively use the testing functions from the `cairo-lang` package. When running the `protostar test` command, specify that you want to use unwhitelisted hints with the flag `--disable-hint-validation` (example below). Protostar doesn't give you full control over what's happening so you can't really use all the python code you wish. Nothing to worry about we've got for you a biiiiig :brain: solution. 178 | 179 | Adding a breakpoint and printing stuff is too mainstream right? 180 | 181 | [Here](protostar/bigBrainDebug/server.py) is a simple script that listens to your port 5000 and prints the body of all the post requests it gets. 182 | 183 | The contracts to fix are [here](protostar/src). 184 | 185 | The tests scripts are [here](protostar/tests). 186 | 187 | First, run the python server with (you could left it open in another terminal): 188 | 189 | ```bash 190 | python3 bigBrainDebug/server.py 191 | ``` 192 | 193 | Remember that your goal is for the tests to pass successfully. Run the tests separately using: 194 | 195 | ```bash 196 | protostar test tests/test_array_contract.cairo --disable-hint-validation 197 | ``` 198 | 199 | ```bash 200 | protostar test tests/test_mock_contract.cairo --disable-hint-validation 201 | ``` 202 | 203 | Or all at once with: 204 | 205 | ```bash 206 | protostar test --disable-hint-validation 207 | ``` 208 | 209 | If you are on MacOS and encountered `[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.` add `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` before protostar line, e.g.: 210 | 211 | ```bash 212 | OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES protostar test --disable-hint-validation 213 | ``` 214 | 215 | ## Ape 216 | 217 | ### Installing 218 | 219 | **The ape tutorial is not maintained. If you want it updated please open an issue** 220 | 221 | To run the ape unit test files you'll need ape installed and configured. This is how you can do it: 222 | 223 | ```bash 224 | pip install eth-ape 225 | ape plugins install cairo starknet 226 | ``` 227 | 228 | If you were starting from scratch you would have to init your project and update the [`ape-config.yaml`](ape/ape-config.yaml) file. Here it's already been done for your convenience. 229 | 230 | If you want to learn a little bit more about ape [here](https://www.youtube.com/watch?v=6nfUpYKLe6Q) is a video that you can watch which explains the basis of ape for StarkNet. 231 | 232 | Since ape hides everything printed by the devnet we can use `print` to debug the contract. I chose to save the logs in a file but we could also setup a server that would receive data from the smart-contract execution or whatever other technique you can think of. 233 | 234 | ### Running tests 235 | 236 | To run the tests run the following command: 237 | 238 | ```bash 239 | ape test 240 | ``` 241 | 242 | The contracts to fix are [here](ape/contracts). 243 | 244 | You can find the test script [here](ape/tests). 245 | -------------------------------------------------------------------------------- /ape/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: ape-debug 2 | plugins: 3 | - cairo 4 | - starknet 5 | default_ecosystem: starknet -------------------------------------------------------------------------------- /ape/contracts/array_contract.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.starknet.common.syscalls import get_contract_address 5 | 6 | struct BasicStruct { 7 | first_member: felt, 8 | second_member: felt, 9 | } 10 | 11 | @view 12 | func view_product{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 13 | arr_len: felt, arr: BasicStruct* 14 | ) -> (res: felt) { 15 | // This could be useful for debugging... 16 | let val = [arr]; 17 | %{ 18 | # this code will only be reached once so no need to append things to the file 19 | with open("array_logs.txt", "w") as f: 20 | f.write(f"Printing {ids.val.first_member=}\n") 21 | %} 22 | let (res) = array_product(arr_len, arr); 23 | let (add) = get_contract_address(); 24 | return (res + add,); 25 | } 26 | 27 | func array_product{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 28 | arr_len: felt, arr: BasicStruct* 29 | ) -> (res: felt) { 30 | if (arr_len == 0) { 31 | return (0,); 32 | } 33 | let temp = [arr].first_member * [arr].second_member; 34 | let (temp2) = array_product(arr_len - 1, arr); 35 | let res = temp * temp2; 36 | return (res,); 37 | } 38 | -------------------------------------------------------------------------------- /ape/contracts/mock_contract.cairo: -------------------------------------------------------------------------------- 1 | // Declare this file as a StarkNet contract. 2 | %lang starknet 3 | 4 | from starkware.cairo.common.cairo_builtins import HashBuiltin 5 | 6 | @storage_var 7 | func mapping(key) -> (value: felt) { 8 | } 9 | 10 | @storage_var 11 | func mapping_length() -> (value: felt) { 12 | } 13 | 14 | @constructor 15 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 16 | arr_len: felt, arr: felt* 17 | ) { 18 | mapping_length.write(arr_len); 19 | %{ 20 | # creating the file not to have the old logs 21 | with open("mock_logs.txt", "w") as f: 22 | f.write("") 23 | %} 24 | fill_mapping(arr_len, arr); 25 | 26 | return (); 27 | } 28 | 29 | func fill_mapping{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 30 | arr_len: felt, arr: felt* 31 | ) -> () { 32 | if (arr_len == 0) { 33 | return (); 34 | } 35 | 36 | // This could be useful for debugging... 37 | let val = [arr]; 38 | %{ 39 | # append the newly created file to have all the logs for this run 40 | with open("mock_logs.txt", "a+") as f: 41 | f.write(f"Still {ids.arr_len} values to save...\n") 42 | f.write(f"Saving {ids.val} to the storage...\n") 43 | %} 44 | mapping.write(arr_len, [arr]); 45 | fill_mapping(arr_len - 1, arr + 1); 46 | return (); 47 | } 48 | 49 | @view 50 | func product_mapping{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 51 | res: felt 52 | ) { 53 | let (length) = mapping_length.read(); 54 | let (res) = product_mapping_internal(length); 55 | return (res,); 56 | } 57 | 58 | func product_mapping_internal{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 59 | len: felt 60 | ) -> (res: felt) { 61 | if (len == 0) { 62 | let (res) = mapping.read(len); 63 | return (res,); 64 | } 65 | let (temp) = product_mapping_internal(len - 1); 66 | let (mapping_val) = mapping.read(len); 67 | let res = temp * mapping_val; 68 | return (res,); 69 | } 70 | -------------------------------------------------------------------------------- /ape/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from re import A 2 | import pytest 3 | 4 | ARRAY = [1, 2, 3, 4, 5, 6] 5 | 6 | 7 | @pytest.fixture 8 | def contracts_type(project): 9 | return project.array_contract, project.mock_contract 10 | 11 | 12 | @pytest.fixture 13 | def contracts(contracts_type): 14 | array_contract = contracts_type[0].deploy() 15 | mock_contract = contracts_type[1].deploy(len(ARRAY), ARRAY) 16 | return (array_contract, mock_contract) 17 | -------------------------------------------------------------------------------- /ape/tests/test_array_contract.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from conftest import ARRAY 3 | 4 | 5 | def test_array_contract(contracts): 6 | array_contract, _ = contracts 7 | calldata = [ 8 | {"first_member": 1, "second_member": 2}, 9 | {"first_member": 3, "second_member": 4}, 10 | ] 11 | 12 | assert array_contract.view_product(1, calldata) == int( 13 | str(array_contract), 16 14 | ) + reduce( 15 | lambda x, y: x * y, [x for arg in calldata for x in arg.values()] 16 | ), "Contract is still not correct" 17 | 18 | 19 | def test_mock_contract(contracts): 20 | _, mock_contract = contracts 21 | assert mock_contract.product_mapping() == reduce( 22 | lambda x, y: x * y, ARRAY 23 | ), "Contract is still not correct" 24 | -------------------------------------------------------------------------------- /hardhat/.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.ts 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /hardhat/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /hardhat/.prettierrc: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /hardhat/contracts/array_contract.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.starknet.common.syscalls import get_contract_address 5 | 6 | struct BasicStruct { 7 | first_member: felt, 8 | second_member: felt, 9 | } 10 | 11 | @view 12 | func view_product{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 13 | array_len: felt, array: BasicStruct* 14 | ) -> (res: felt) { 15 | // This could be useful for debugging... 16 | let val = [array]; 17 | // This will print first member of the first value of the array 18 | %{ print(f"Printing {ids.val.first_member=}") %} 19 | // You can use tempvars to print values further down the array 20 | tempvar last_val = array[array_len - 1]; 21 | %{ print(f"Printing {ids.last_val.first_member=}") %} 22 | let (res) = array_product(array_len, array); 23 | let (add) = get_contract_address(); 24 | return (res + add,); 25 | } 26 | 27 | // TODO: Fix the contract so that it returns the product of all the tuples in 28 | // the array. Example: 29 | // For the array [{2,3}, {4,5}] the result should be: (2*3)*(4*5) etc. 30 | func array_product{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 31 | array_len: felt, array: BasicStruct* 32 | ) -> (res: felt) { 33 | if (array_len == 0) { 34 | return (0,); 35 | } 36 | let temp = [array].first_member * [array].second_member; 37 | let (temp2) = array_product(array_len - 1, array); 38 | let res = temp * temp2; 39 | return (res,); 40 | } 41 | -------------------------------------------------------------------------------- /hardhat/contracts/mock_contract.cairo: -------------------------------------------------------------------------------- 1 | // Declare this file as a StarkNet contract. 2 | %lang starknet 3 | 4 | from starkware.cairo.common.cairo_builtins import HashBuiltin 5 | 6 | @storage_var 7 | func mapping(key) -> (value: felt) { 8 | } 9 | 10 | @storage_var 11 | func mapping_length() -> (value: felt) { 12 | } 13 | 14 | @constructor 15 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 16 | array_len: felt, array: felt* 17 | ) { 18 | mapping_length.write(array_len); 19 | fill_mapping(array_len, array); 20 | return (); 21 | } 22 | 23 | func fill_mapping{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 24 | array_len: felt, array: felt* 25 | ) -> () { 26 | if (array_len == 0) { 27 | return (); 28 | } 29 | 30 | // This could be useful for debugging... 31 | let val = [array]; 32 | %{ 33 | print(f"Still {ids.array_len} values to save...") 34 | print(f"Saving {ids.val} to the storage...") 35 | %} 36 | mapping.write(array_len, [array]); 37 | fill_mapping(array_len - 1, array + 1); 38 | return (); 39 | } 40 | 41 | @view 42 | func product_mapping{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 43 | res: felt 44 | ) { 45 | let (length) = mapping_length.read(); 46 | let (res) = product_mapping_internal(length); 47 | return (res,); 48 | } 49 | 50 | // TODO: Fix the contract so that it returns the product of all the values in 51 | // the stored array. Example: 52 | // For the array [7,8,10], the result should be (7*8*10)=560 53 | func product_mapping_internal{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 54 | len: felt 55 | ) -> (res: felt) { 56 | if (len == 0) { 57 | let (res) = mapping.read(len); 58 | return (res,); 59 | } 60 | let (temp) = product_mapping_internal(len - 1); 61 | let (mapping_val) = mapping.read(len); 62 | let res = temp * mapping_val; 63 | return (res,); 64 | } 65 | -------------------------------------------------------------------------------- /hardhat/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/types"; 2 | import "@shardlabs/starknet-hardhat-plugin"; 3 | 4 | /** 5 | * @type import('hardhat/config').HardhatUserConfig 6 | */ 7 | const config: HardhatUserConfig = { 8 | starknet: { 9 | venv: "active", 10 | network: "integratedDevnet", 11 | }, 12 | networks: { 13 | integratedDevnet: { 14 | url: "http://127.0.0.1:5050", 15 | args: ["--lite-mode"], 16 | 17 | stdout: "STDOUT", 18 | }, 19 | testnet: { 20 | url: "http://127.0.0.1:5050", 21 | }, 22 | }, 23 | }; 24 | 25 | export default config; 26 | -------------------------------------------------------------------------------- /hardhat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "scripts": { 4 | "build": "npm run clean && npm run kawaii && npm run compile && npm run prettier", 5 | "clean": "rm -rf starknet-artifacts", 6 | "compile": "npx hardhat starknet-compile --disable-hint-validation", 7 | "kawaii": "cairo-format -i contracts/*.cairo", 8 | "prettier": "prettier --write .", 9 | "test": "npm run compile && npx hardhat test" 10 | }, 11 | "devDependencies": { 12 | "@shardlabs/starknet-hardhat-plugin": "^0.6.6-alpha.0", 13 | "@types/chai": "^4.3.3", 14 | "@types/node": "^12.20.55", 15 | "@typescript-eslint/eslint-plugin": "^4.33.0", 16 | "@typescript-eslint/parser": "^4.33.0", 17 | "chai": "^4.3.6", 18 | "dotenv": "^10.0.0", 19 | "hardhat": "^2.11.1", 20 | "prettier": "^2.7.1", 21 | "ts-node": "^10.9.1", 22 | "typescript": "^4.8.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hardhat/test/test.spec.ts: -------------------------------------------------------------------------------- 1 | // Testing your code using the hard hat StarkNet plugin 2 | // You will find more information on testing in Hard Hat by following this link https://github.com/Shard-Labs/starknet-hardhat-plugin#testing 3 | import { expect } from "chai"; 4 | import { starknet } from "hardhat"; 5 | import { 6 | StarknetContract, 7 | StarknetContractFactory, 8 | } from "hardhat/types/runtime"; 9 | 10 | const toStruct = (first: number, second: number) => { 11 | return { first_member: first, second_member: second }; 12 | }; 13 | 14 | const ARRAY = [1, 2, 3, 4, 5, 6]; 15 | 16 | describe("Testing the contracts", async function () { 17 | this.timeout(6_000_000); 18 | 19 | let mockContractFactory: StarknetContractFactory; 20 | let mockContract: StarknetContract; 21 | 22 | let arrayContractFactory: StarknetContractFactory; 23 | let arrayContract: StarknetContract; 24 | 25 | before(async () => { 26 | arrayContractFactory = await starknet.getContractFactory("array_contract"); 27 | arrayContract = await arrayContractFactory.deploy(); 28 | 29 | mockContractFactory = await starknet.getContractFactory("mock_contract"); 30 | mockContract = await mockContractFactory.deploy({ 31 | array: ARRAY, 32 | }); 33 | }); 34 | 35 | describe("Test that you debugged the mock contract correctly", async () => { 36 | it("Let's see if you made it", async () => { 37 | expect( 38 | ( 39 | await arrayContract.call("view_product", { 40 | array: [toStruct(1, 2), toStruct(3, 4)], 41 | }) 42 | ).res 43 | ).to.deep.eq(BigInt(arrayContract.address) + BigInt(24)); 44 | }); 45 | }); 46 | 47 | describe("Test that you debugged the array contract correctly", async () => { 48 | it("Let's see if you made it", async () => { 49 | expect((await mockContract.call("product_mapping")).res).to.deep.eq( 50 | BigInt(ARRAY.reduce((a, b) => a * b, 1)) 51 | ); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /hardhat/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "declaration": true 9 | }, 10 | "include": ["./scripts", "./test", "./typechain"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /protostar/bigBrainDebug/server.py: -------------------------------------------------------------------------------- 1 | import flask 2 | from flask import request 3 | import logging 4 | import os 5 | 6 | log = logging.getLogger("werkzeug") 7 | log.disabled = True 8 | app = flask.Flask(__name__) 9 | app.config["DEBUG"] = True 10 | counter = 0 11 | 12 | 13 | @app.route("/", methods=["POST"]) 14 | def home(): 15 | global counter 16 | print(counter, request.json) 17 | counter += 1 18 | return "success" 19 | 20 | app.run(host=os.getenv('IP', '0.0.0.0'), 21 | port=int(os.getenv('PORT', 5000))) 22 | -------------------------------------------------------------------------------- /protostar/lib/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starknet-edu/starknet-debug/41366d086f2b743559aec68de19a3bd02547e19d/protostar/lib/.gitkeep -------------------------------------------------------------------------------- /protostar/protostar.toml: -------------------------------------------------------------------------------- 1 | ["protostar.config"] 2 | protostar_version = "0.4.1" 3 | 4 | ["protostar.project"] 5 | libs_path = "lib" 6 | 7 | ["protostar.contracts"] 8 | main = [ 9 | "src/main.cairo", 10 | ] 11 | -------------------------------------------------------------------------------- /protostar/src/array_contract.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.starknet.common.syscalls import get_contract_address 5 | 6 | struct BasicStruct { 7 | first_member: felt, 8 | second_member: felt, 9 | } 10 | 11 | @view 12 | func view_product{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 13 | arr_len: felt, arr: BasicStruct* 14 | ) -> (res: felt) { 15 | // This could be useful for debugging... 16 | let val = [arr]; 17 | %{ 18 | from requests import post 19 | json = { # creating the body of the post request so it's printed in the python script 20 | "first_member": ids.val.first_member, 21 | "second_member": ids.val.second_member 22 | } 23 | post(url="http://localhost:5000", json=json) # sending the request to our small "server" 24 | %} 25 | let (res) = array_product(arr_len, arr); 26 | let (add) = get_contract_address(); 27 | return (res + add,); 28 | } 29 | 30 | func array_product{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 31 | arr_len: felt, arr: BasicStruct* 32 | ) -> (res: felt) { 33 | if (arr_len == 0) { 34 | return (0,); 35 | } 36 | let temp = [arr].first_member * [arr].second_member; 37 | let (temp2) = array_product(arr_len - 1, arr); 38 | let res = temp * temp2; 39 | return (res,); 40 | } 41 | -------------------------------------------------------------------------------- /protostar/src/mock_contract.cairo: -------------------------------------------------------------------------------- 1 | // Declare this file as a StarkNet contract. 2 | %lang starknet 3 | 4 | from starkware.cairo.common.cairo_builtins import HashBuiltin 5 | 6 | @storage_var 7 | func mapping(key) -> (value: felt) { 8 | } 9 | 10 | @storage_var 11 | func mapping_length() -> (value: felt) { 12 | } 13 | 14 | @constructor 15 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 16 | arr_len: felt, arr: felt* 17 | ) { 18 | mapping_length.write(arr_len); 19 | fill_mapping(arr_len, arr); 20 | 21 | return (); 22 | } 23 | 24 | func fill_mapping{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 25 | arr_len: felt, arr: felt* 26 | ) -> () { 27 | if (arr_len == 0) { 28 | return (); 29 | } 30 | 31 | // This could be useful for debugging... 32 | let val = [arr]; 33 | %{ 34 | from requests import post 35 | json = { # creating the body of the post request so it's printed in the python script 36 | "1": f"Still {ids.arr_len} values to save...", 37 | "2": f"Saving {ids.val} to the storage..." 38 | } 39 | post(url="http://localhost:5000", json=json) # sending the request to our small "server" 40 | %} 41 | mapping.write(arr_len, [arr]); 42 | fill_mapping(arr_len - 1, arr + 1); 43 | return (); 44 | } 45 | 46 | @view 47 | func product_mapping{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 48 | res: felt 49 | ) { 50 | let (length) = mapping_length.read(); 51 | let (res) = product_mapping_internal(length); 52 | return (res,); 53 | } 54 | 55 | func product_mapping_internal{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 56 | len: felt 57 | ) -> (res: felt) { 58 | if (len == 0) { 59 | let (res) = mapping.read(len); 60 | return (res,); 61 | } 62 | let (temp) = product_mapping_internal(len - 1); 63 | let (mapping_val) = mapping.read(len); 64 | let res = temp * mapping_val; 65 | return (res,); 66 | } 67 | -------------------------------------------------------------------------------- /protostar/tests/test_array_contract.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.cairo.common.alloc import alloc 5 | from src.array_contract import BasicStruct 6 | 7 | @contract_interface 8 | namespace IContract { 9 | func view_product(arr_len: felt, arr: BasicStruct*) -> (res: felt) { 10 | } 11 | } 12 | 13 | @view 14 | func __setup__{syscall_ptr: felt*, range_check_ptr}() { 15 | alloc_locals; 16 | local contract_address: felt; 17 | %{ 18 | context.contract_address = deploy_contract("./src/array_contract.cairo",).contract_address 19 | ids.contract_address = context.contract_address 20 | %} 21 | return (); 22 | } 23 | 24 | @external 25 | func test_view_product{syscall_ptr: felt*, range_check_ptr, pedersen_ptr: HashBuiltin*}() { 26 | alloc_locals; 27 | local contract_address: felt; 28 | %{ ids.contract_address = context.contract_address %} 29 | let (local arr: BasicStruct*) = alloc(); 30 | assert arr[0] = BasicStruct(1, 2); 31 | assert arr[1] = BasicStruct(3, 4); 32 | assert arr[2] = BasicStruct(5, 6); 33 | let (res) = IContract.view_product(contract_address=contract_address, arr_len=3, arr=arr); 34 | assert res = 720 + contract_address; 35 | return (); 36 | } 37 | -------------------------------------------------------------------------------- /protostar/tests/test_mock_contract.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.cairo.common.alloc import alloc 5 | from src.array_contract import BasicStruct 6 | 7 | @contract_interface 8 | namespace IContract { 9 | func product_mapping() -> (res: felt) { 10 | } 11 | } 12 | 13 | @view 14 | func __setup__{syscall_ptr: felt*, range_check_ptr}() { 15 | alloc_locals; 16 | local contract_address: felt; 17 | %{ 18 | context.contract_address = deploy_contract( 19 | contract_path="./src/mock_contract.cairo", 20 | constructor_args=[6, 1, 2, 3, 4, 5, 6]).contract_address 21 | ids.contract_address = context.contract_address 22 | %} 23 | return (); 24 | } 25 | 26 | @external 27 | func test_view_product{syscall_ptr: felt*, range_check_ptr, pedersen_ptr: HashBuiltin*}() { 28 | alloc_locals; 29 | local contract_address: felt; 30 | %{ ids.contract_address = context.contract_address %} 31 | let (res) = IContract.product_mapping(contract_address=contract_address); 32 | assert res = 720; 33 | return (); 34 | } 35 | -------------------------------------------------------------------------------- /python/contracts/array_contract.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.starknet.common.syscalls import get_contract_address 5 | from starkware.cairo.common.registers import get_fp_and_pc 6 | 7 | struct BasicStruct { 8 | first_member: felt, 9 | second_member: felt, 10 | } 11 | 12 | @view 13 | func view_product{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 14 | array_len: felt, array: BasicStruct* 15 | ) -> (res: felt) { 16 | alloc_locals; 17 | let fp_and_pc = get_fp_and_pc(); 18 | tempvar __fp__ = fp_and_pc.fp_val; 19 | tempvar ptr = array; 20 | local dummy = 9; 21 | // This could be useful for debugging... 22 | tempvar val = [array]; 23 | %{ 24 | from rich.pretty import pprint 25 | from rich.console import Console 26 | from starkware.starknet.public.abi import starknet_keccak 27 | from starkware.cairo.lang.vm.crypto import pedersen_hash 28 | console = Console(markup=False, highlight=False) 29 | console.print("\nHey big boy look at me".upper(), style="bold underline #FFA500") 30 | console.print(f"Printing {ids.val.first_member=}", style="bold #FFA500") 31 | console.print("Now that you reached this why do you try exotic stuff ...?", style="bold #FFA500") 32 | console.print("We're gonna mess with the memory now so brace yourself ", style="bold #FFA500") 33 | console.print("Print the last filled memory cell with memory[ap-1]", style="bold green") # should print 1 34 | console.print("This is the 2nd member of the struct element guess what would output memory[ap-2] ??", style="bold green") # should print 1 35 | console.print("Pretty cool huh? Now we're going to access a pointer's value", style="bold #FFA500") 36 | console.print("Lets access a local that relies on fp's value", style="bold #FFA500") 37 | console.print("try memory[memory[fp+1]]", style="bold green") # should print array_len 38 | console.print("if you don't understand why it works like this you might want to read this https://www.cairo-lang.org/docs/how_cairo_works/consts.html#local-variables", style="bold green") 39 | breakpoint() 40 | %} 41 | let (res) = array_product(array_len, array); 42 | let (add) = get_contract_address(); 43 | return (res + add,); 44 | } 45 | 46 | func array_product{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 47 | array_len: felt, array: BasicStruct* 48 | ) -> (res: felt) { 49 | if (array_len == 0) { 50 | return (0,); 51 | } 52 | let temp = [array].first_member * [array].second_member; 53 | let (temp2) = array_product(array_len - 1, array); 54 | let res = temp * temp2; 55 | return (res,); 56 | } 57 | -------------------------------------------------------------------------------- /python/contracts/mock_contract.cairo: -------------------------------------------------------------------------------- 1 | // Declare this file as a StarkNet contract. 2 | %lang starknet 3 | 4 | from starkware.cairo.common.cairo_builtins import HashBuiltin 5 | from starkware.cairo.common.registers import get_fp_and_pc 6 | 7 | @storage_var 8 | func mapping(key) -> (value: felt) { 9 | } 10 | 11 | @storage_var 12 | func mapping_length() -> (value: felt) { 13 | } 14 | 15 | @constructor 16 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 17 | array_len: felt, array: felt* 18 | ) { 19 | mapping_length.write(array_len); 20 | fill_mapping(array_len, array); 21 | return (); 22 | } 23 | 24 | func fill_mapping{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 25 | array_len: felt, array: felt* 26 | ) -> () { 27 | if (array_len == 0) { 28 | return (); 29 | } 30 | alloc_locals; 31 | let fp_and_pc = get_fp_and_pc(); 32 | tempvar __fp__ = fp_and_pc.fp_val; 33 | 34 | // This could be useful for debugging... 35 | mapping.write(array_len, [array]); 36 | tempvar array_len_ptr = &array_len; 37 | tempvar val = [array]; 38 | %{ 39 | from rich.pretty import pprint 40 | from rich.console import Console 41 | from starkware.starknet.public.abi import starknet_keccak 42 | from starkware.cairo.lang.vm.crypto import pedersen_hash 43 | console = Console(markup=False, highlight=False) 44 | 45 | print(f"Still {ids.array_len} values to save...") 46 | print(f"Saving {ids.val} to the storage...") 47 | console.print("Hey big boy look at me".upper(), style="bold underline #FFA500") 48 | console.print("Now that you reached this why do you try exotic stuff ...?", style="bold #FFA500") 49 | console.print("We're gonna mess with the memory now so brace yourself ", style="bold #FFA500") 50 | console.print("Print the last filled memory cell with memory[ap-1]", style="bold green") # should print 1 51 | console.print("Pretty cool huh? Now we're going to access a pointer's value", style="bold #FFA500") 52 | console.print("try memory[memory[ap-2]]", style="bold green") # should print array_len 53 | console.print("now icing on the cake we're gonna inspect the storage", style="bold #FFA500") 54 | console.print("now try __storage.read(pedersen_hash(starknet_keccak(b'mapping'), 5))", style="bold green") # should print the value of the key 5 55 | console.print("You can also check what variable is available with pprint(locals()) pprint(globals())", style="bold green") # should print the value of the key 5 56 | 57 | breakpoint() 58 | %} 59 | fill_mapping(array_len - 1, array + 1); 60 | return (); 61 | } 62 | 63 | @view 64 | func product_mapping{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 65 | res: felt 66 | ) { 67 | let (length) = mapping_length.read(); 68 | let (res) = product_mapping_internal(length); 69 | return (res,); 70 | } 71 | 72 | func product_mapping_internal{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 73 | len: felt 74 | ) -> (res: felt) { 75 | if (len == 0) { 76 | let (res) = mapping.read(len); 77 | return (res,); 78 | } 79 | let (temp) = product_mapping_internal(len - 1); 80 | let (mapping_val) = mapping.read(len); 81 | let res = temp * mapping_val; 82 | return (res,); 83 | } 84 | -------------------------------------------------------------------------------- /python/tests/test_array_contract.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from asynctest import TestCase 4 | from functools import reduce 5 | from starkware.starknet.testing.starknet import Starknet 6 | 7 | # The path to the contract source code. 8 | CONTRACT_FILE = os.path.join("contracts", "array_contract.cairo") 9 | PRODUCT_ARRAY = [(x, x + 1) for x in range(1, 6, 2)] 10 | 11 | 12 | class CairoContractTest(TestCase): 13 | @classmethod 14 | async def setUp(cls): 15 | cls.starknet = await Starknet.empty() 16 | 17 | cls.contract = await cls.starknet.deploy( 18 | source=CONTRACT_FILE, disable_hint_validation=True 19 | ) 20 | 21 | @pytest.mark.asyncio 22 | async def test_array_contract(self): 23 | res = await self.contract.view_product(array=PRODUCT_ARRAY).call() 24 | self.assertEqual( 25 | res.call_info.result.pop(), 26 | self.contract.contract_address 27 | + reduce(lambda x, y: x * y, list(sum(PRODUCT_ARRAY, ()))), 28 | "Contract is still not correct", 29 | ) 30 | -------------------------------------------------------------------------------- /python/tests/test_mock_contract.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from asynctest import TestCase 4 | from functools import reduce 5 | from starkware.starknet.testing.starknet import Starknet 6 | 7 | # The path to the contract source code. 8 | CONTRACT_FILE = os.path.join("contracts", "mock_contract.cairo") 9 | PRODUCT_ARRAY = [1, 2, 3, 4, 5] 10 | 11 | 12 | class CairoContractTest(TestCase): 13 | @classmethod 14 | async def setUp(cls): 15 | cls.starknet = await Starknet.empty() 16 | 17 | cls.contract = await cls.starknet.deploy( 18 | source=CONTRACT_FILE, 19 | constructor_calldata=[len(PRODUCT_ARRAY), *PRODUCT_ARRAY], 20 | disable_hint_validation=True, 21 | ) 22 | 23 | @pytest.mark.asyncio 24 | async def test_mock_contract(self): 25 | res = await self.contract.product_mapping().call() 26 | self.assertEqual( 27 | res.call_info.result.pop(), 28 | reduce(lambda x, y: x * y, PRODUCT_ARRAY), 29 | "Contract is still not correct", 30 | ) 31 | --------------------------------------------------------------------------------