├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Makefile ├── README.md ├── accounts.json ├── contracts ├── Evaluator.cairo ├── IERC20Solution.cairo ├── IExerciseSolution.cairo ├── lib │ └── UTILS.cairo ├── token │ └── ERC20 │ │ ├── DTKERC20.cairo │ │ ├── IDTKERC20.cairo │ │ ├── IERC20.cairo │ │ ├── ITUTOERC20.cairo │ │ └── TUTOERC20.cairo └── utils │ ├── Iplayers_registry.cairo │ ├── ex00_base.cairo │ └── players_registry.cairo ├── deploy └── deploying.txt ├── tests └── test_contract.py └── utils.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run StarkNet compilation 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Compile all the contracts 16 | run: > 17 | for cairo_file in $(find contracts/ -type f -name "*.cairo"); do 18 | docker run --mount type=bind,source=$(pwd)/contracts/,target=/contracts/ shardlabs/cairo-cli:latest starknet-compile $cairo_file >/dev/null; 19 | done 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | *DS_STORE 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Env 12 | *.env 13 | *.accounts.json 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | pip-wheel-metadata/ 30 | share/python-wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | MANIFEST 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | goerli.deployments.txt 138 | Makefile 139 | 140 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Build and test 2 | build :; nile compile 3 | test :; pytest tests/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DISCLAIMER 2 | Hello Starknet community, this repository is not updated with the latest Cairo syntax 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 | # ERC20 on Starknet 9 | 10 | Welcome! This is an automated workshop that will explain how to deploy an ERC20 token on Starknet and customize it to perform specific functions. The ERC20 standard is described [here](https://docs.openzeppelin.com/contracts/3.x/api/token/erc20) 11 | It is aimed at developers that: 12 | 13 | - Understand Cairo syntax 14 | - Understand the ERC20 token standard 15 | 16 | This tutorial was written by Florian Charlier ([@trevis_dev](https://twitter.com/trevis_dev)) in collaboration with Henri Lieutaud and Lucas Levy, based on Henri's original [ERC20 101](https://github.com/l-henri/erc20-101) and [ERC20 102](https://github.com/l-henri/erc20-102) tutorials for Solidity. 17 | 18 | ​ 19 | ​ 20 | 21 | ## Introduction 22 | 23 | ### Disclaimer 24 | 25 | Don't expect any kind of benefit from using this, other than learning a bunch of cool stuff about Starknet, the first general purpose validity rollup on the Ethereum Mainnnet. 26 | ​ 27 | Starknet is still in Alpha. This means that development is ongoing, and the paint is not dry everywhere. Things will get better, and in the meanwhile, we make things work with a bit of duct tape here and there! 28 | ​ 29 | 30 | ### How it works 31 | 32 | The goal of this tutorial is for you to customize and deploy an ERC20 contract on Starknet. Your progress will be check by an [evaluator contract](contracts/Evaluator.cairo), deployed on Starknet, which will grant you points in the form of [ERC20 tokens](contracts/token/ERC20/TUTOERC20.cairo). 33 | 34 | Each exercise will require you to add functionality to your ERC20 token. 35 | 36 | For each exercise, you will have to write a new version on your contract, deploy it, and submit it to the evaluator for correction. 37 | 38 | ### Where am I? 39 | 40 | This workshop is the third in a series aimed at teaching how to build on Starknet. Checkout out the following: 41 | 42 | | Topic | GitHub repo | 43 | | -------------------------------------------------- | -------------------------------------------------------------------------------------- | 44 | | Learn how to read Cairo code | [Cairo 101](https://github.com/starknet-edu/starknet-cairo-101) | 45 | | Deploy and customize an ERC721 NFT | [Starknet ERC721](https://github.com/starknet-edu/starknet-erc721) | 46 | | Deploy and customize an ERC20 token (you are here) | [Starknet ERC20](https://github.com/starknet-edu/starknet-erc20) | 47 | | Build a cross layer application | [Starknet messaging bridge](https://github.com/starknet-edu/starknet-messaging-bridge) | 48 | | Debug your Cairo contracts easily | [Starknet debug](https://github.com/starknet-edu/starknet-debug) | 49 | | Design your own account contract | [Starknet account abstraction](https://github.com/starknet-edu/starknet-accounts) | 50 | 51 | ### Providing feedback & getting help 52 | 53 | Once you are done working on this tutorial, your feedback would be greatly appreciated! 54 | 55 | **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.** 56 | 57 | ​ 58 | 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. 59 | 60 | ​ 61 | Do you have a question? Join our [Discord server](https://starknet.io/discord), register, and join channel #tutorials-support 62 | ​ 63 | Are you interested in following online workshops about learning how to dev on Starknet? [Subscribe here](http://eepurl.com/hFnpQ5) 64 | 65 | ### Contributing 66 | 67 | 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: 68 | 69 | - Create a branch with a translation to your language 70 | - Correct bugs if you find some 71 | - Add an explanation in the comments of the exercise if you feel it needs more explanation 72 | - Add exercises showcasing your favorite Cairo feature 73 | 74 | ​ 75 | 76 | ## Getting ready to work 77 | 78 | ### Step 1 - Clone the repo 79 | 80 | ```bash 81 | git clone https://github.com/starknet-edu/starknet-erc20 82 | cd starknet-erc20 83 | ``` 84 | 85 | ### Step 2 - Set up your environment 86 | 87 | There are two ways to set up your environment on Starknet: a local installation, or using a docker container 88 | 89 | - For Mac and Linux users, we recommend either 90 | - For windows users we recommand docker 91 | 92 | For a production setup instructions we wrote [this article](https://medium.com/starknet-edu/the-ultimate-starknet-dev-environment-716724aef4a7). 93 | 94 | #### Option A - Set up a local python environment 95 | 96 | - Set up the environment following [these instructions](https://starknet.io/docs/quickstart.html#quickstart) 97 | - Install [OpenZeppelin's cairo contracts](https://github.com/OpenZeppelin/cairo-contracts). 98 | 99 | ```bash 100 | pip install openzeppelin-cairo-contracts 101 | ``` 102 | 103 | #### Option B - Use a dockerized environment 104 | 105 | - Linux and macos 106 | 107 | for mac m1: 108 | 109 | ```bash 110 | alias cairo='docker run --rm -v "$PWD":"$PWD" -w "$PWD" shardlabs/cairo-cli:latest-arm' 111 | ``` 112 | 113 | for amd processors 114 | 115 | ```bash 116 | alias cairo='docker run --rm -v "$PWD":"$PWD" -w "$PWD" shardlabs/cairo-cli:latest' 117 | ``` 118 | 119 | - Windows 120 | 121 | ```bash 122 | docker run --rm -it -v ${pwd}:/work --workdir /work shardlabs/cairo-cli:latest 123 | ``` 124 | 125 | ### Step 3 -Test that you are able to compile the project 126 | 127 | ```bash 128 | starknet-compile contracts/Evaluator.cairo 129 | ``` 130 | 131 | ### Step 4 - Define your environment variables to set up your account 132 | 133 | ```bash 134 | export STARKNET_NETWORK=alpha-goerli 135 | export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount 136 | ``` 137 | 138 | ### Step 5 - Create and deploy your account 139 | Before deploying your account, send a few Goerli ETH to it. 140 | ```bash 141 | starknet new_account 142 | starknet deploy_account 143 | ``` 144 | 145 | ## Working on the tutorial 146 | 147 | ### Workflow 148 | 149 | To do this tutorial you will have to interact with the [`Evaluator.cairo`](contracts/Evaluator.cairo) contract. To validate an exercise you will have to 150 | 151 | - Read the evaluator code to figure out what is expected of your contract 152 | - Customize your contract's code 153 | - Deploy it to Starknet's testnet. This is done using the CLI. 154 | - Register your exercise for correction, using the `submit_exercise` function on the evaluator. This is done using Voyager. 155 | - Call the relevant function on the evaluator contract to get your exercise corrected and receive your points. This is done using Voyager. 156 | 157 | For example to solve the first exercise the workflow would be the following: 158 | 159 | `deploy a smart contract that answers ex1` → `call submit_exercise on the evaluator providing your smart contract address` → `call ex2_test_erc20 on the evaluator contract` 160 | 161 | Notes: 162 | To deploy a smart contract, follow these instructions: 163 | 164 | ```bash 165 | starknet-compile my_contrats/erc20.cairo --output artifacts/erc20.json 166 | starknet declare --contract artifacts/erc20.json 167 | ``` 168 | 169 | Use the contract class from the output of the previous command 170 | 171 | ```bash 172 | starknet deploy --class_hash --network alpha-goerli 173 | ``` 174 | 175 | ***Your objective is to gather as many ERC20-101 points as possible.*** Please note : 176 | 177 | - The 'transfer' function of ERC20-101 has been disabled to encourage you to finish the tutorial with only one address 178 | - In order to receive points, you will have to reach the calls to the `validate_and_distribute_points_once` function. 179 | - This repo contains two interfaces ([`IERC20Solution.cairo`](contracts/IERC20Solution.cairo) and [`IExerciseSolution.cairo`](contracts/IERC20Solution.cairo)). For example, for the first part, your ERC20 contract will have to conform to the first interface in order to validate the exercises; that is, your contract needs to implement all the functions described in `IERC20Solution.cairo`. 180 | - **We really recommend that your read the [`Evaluator.cairo`](contracts/Evaluator.cairo) contract in order to fully understand what's expected for each exercise**. A high level description of what is expected for each exercise is provided in this readme. 181 | - The Evaluator contract sometimes needs to make payments to buy your tokens. Make sure he has enough dummy tokens to do so! If not, you should get dummy tokens from the dummy tokens contract and send them to the evaluator. 182 | 183 | ### Contracts code and addresses 184 | 185 | | Contract code | Contract on voyager | 186 | | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 187 | | [Points counter ERC20](contracts/token/ERC20/TUTOERC20.cairo) | [0x228c0e6db14052a66901df14a9e8493c0711fa571860d9c62b6952997aae58b](https://goerli.voyager.online/contract/0x228c0e6db14052a66901df14a9e8493c0711fa571860d9c62b6952997aae58b) | 188 | | [Evaluator](contracts/Evaluator.cairo) | [0x14ece8a1dcdcc5a56f01a987046f2bd8ddfb56bc358da050864ae6da5f71394](https://goerli.voyager.online/contract/0x14ece8a1dcdcc5a56f01a987046f2bd8ddfb56bc358da050864ae6da5f71394) | 189 | | [Dummy ERC20 token (DTK20)](contracts/token/ERC20/DTKERC20.cairo) | [0x66aa72ce2916bbfc654fd18f9c9aaed29a4a678274639a010468a948a5e2a96](https://goerli.voyager.online/contract/0x66aa72ce2916bbfc654fd18f9c9aaed29a4a678274639a010468a948a5e2a96) | 190 | 191 | ​ 192 | ​ 193 | 194 | ## Tasks list 195 | 196 | Today you will deploy your own ERC20 token on Starknet! 197 | 198 | The tutorial is structured in two parts 199 | 200 | - In the first part (exercises 1 to 9), you will have to deploy an ERC-20 contract. 201 | - In the second part (exercises 10 to 18), you will deploy another contract that will itself have to interact with ERC20 tokens. 202 | 203 | ### Exercise 1 - Deploy an ERC20 204 | 205 | - Call [`ex1_assign_rank()`](contracts/Evaluator.cairo#L134) in the evaluator contract to receive a random ticker for your ERC20 token, as well as an initial token supply (1 pt). You can read your assigned ticker and supply through the [evaluator page in voyager](https://goerli.voyager.online/contract/0x14ece8a1dcdcc5a56f01a987046f2bd8ddfb56bc358da050864ae6da5f71394) by calling getters [`read_ticker()`](contracts/Evaluator.cairo#L93) and [`read_supply()`](contracts/Evaluator.cairo#L102) 206 | - Create an ERC20 token contract with the proper ticker and supply. You can use [this implementation](https://github.com/OpenZeppelin/cairo-contracts/blob/main/src/openzeppelin/token/erc20/ERC20.cairo) as a base (2 pts) 207 | - Deploy it to the testnet (check the constructor for the needed arguments. Also note that the arguments should be decimals.) (1pt) 208 | 209 | ```bash 210 | starknet-compile contracts/token/ERC20/ERC20.cairo --output artifacts/ERC20.json 211 | starknet deploy --contract ERC20 --inputs arg1 arg2 arg3 --network alpha-goerli 212 | ``` 213 | 214 | - Call [`submit_erc20_solution()`](contracts/Evaluator.cairo#L733) in the Evaluator to set the contract you want evaluated (2pts) (Previous 3 points for the ERC20 and the deployment are also attributed at that step) 215 | 216 | ### Exercise 2 - Verifying your ERC20 217 | 218 | - Call [`ex2_test_erc20()`](contracts/Evaluator.cairo#L150) in the evaluator for it to check ticker and supply and attribute your points (2 pts) 219 | 220 | ### Exercise 3 - Creating a faucet 221 | 222 | - Create a `get_tokens()` function in your contract. It should mint some of your token for the caller. It should return the exact amount it mints so that the Evaluator can check that the increase of balance and the amount sent corresponds. 223 | - Deploy your contract and call [`submit_erc20_solution()`](contracts/Evaluator.cairo#L733) in the Evaluator to register it 224 | - Call the [`ex3_test_get_token()`](contracts/Evaluator.cairo#L209) function that distributes tokens to the caller (2 pts). 225 | 226 | ### Exercises 4, 5 and 6 - Creating an allow list 227 | 228 | - Create a customer allow listing function. Only allow listed users should be able to call `get_tokens()`. 229 | - Create a function `request_allowlist()` that the evaluator will call during the exercise check to be allowed to get tokens. 230 | - Create a function `allowlist_level()` that can be called by anyone to know whether an account is allowed to get tokens. 231 | - Deploy your contract and call [`submit_erc20_solution()`](contracts/Evaluator.cairo#L733) in the Evaluator to register it 232 | - Call [`ex4_5_6_test_fencing()`](contracts/Evaluator.cairo#L231) in the evaluator to show 233 | - It can't get tokens using `get_tokens()` (1 pt) 234 | - It can call `request_allowlist()` and have confirmation that it went through (1 pt) 235 | - It can then get tokens using the same `get_tokens()` (2 pt) 236 | 237 | ### Exercises 7, 8 and 9 - Creating a multi tier allow list 238 | 239 | - Create a customer multi tier listing function. Only allow listed users should be able to call `get_token()`; and customers should receive a different amount of tokens based on their level 240 | - Create a function `request_allowlist_level()` that the evaluator will call during the exercise check to be allowed to get tokens at a certain tier level 241 | - Modify the function `allowlist_level()` so that it returns the allowed level of accounts. 242 | - Deploy your contract and call [`submit_erc20_solution()`](contracts/Evaluator.cairo#L733) in the Evaluator to register it 243 | - Call [`ex7_8_9_test_fencing_levels()`](contracts/Evaluator.cairo#L291) in the evaluator to show 244 | - It can't get tokens using `get_tokens()` (1 pt) 245 | - It can call `request_allowlist_level(1)` , then call `get_tokens()` and get N tokens (2 pt) 246 | - It can call `request_allowlist_level(2)` , then call `get_tokens()` and get > N tokens (2 pt) 247 | 248 | ### Exercise 10 - Claiming dummy tokens 249 | 250 | - Manually claim tokens on the predeployed claimable [ERC20](https://goerli.voyager.online/contract/0x66aa72ce2916bbfc654fd18f9c9aaed29a4a678274639a010468a948a5e2a96) ([DTK tokens](contracts/token/ERC20/DTKERC20.cairo)) (1 pts) 251 | - Claim your points by calling [`ex10_claimed_tokens()`](contracts/Evaluator.cairo#L364) in the evaluator (1 pts) 252 | 253 | ### Exercise 11 - Calling the faucet from your contract 254 | 255 | - Create a contract `ExerciseSolution` that: 256 | - Can claim and hold DTK tokens on behalf of the calling address 257 | - Keeps track of addresses who claimed tokens, and how much 258 | - Implements a `tokens_in_custody` function to show these claimed amounts 259 | - Deploy your contract and call [`submit_exercise_solution()`](contracts/Evaluator.cairo#L754) in the Evaluator to register it 260 | - Call [`ex11_claimed_from_contract()`](contracts/Evaluator.cairo#L383) in the evaluator to prove your code works (3 pts) 261 | 262 | ### Exercise 12 - Using transferFrom on an ERC20 263 | 264 | - Create a function `withdraw_all_tokens()` in `ExerciseSolution` to withdraw the claimed tokens from the `ExerciseSolution` to the address that initially claimed them 265 | - Deploy your contract and call [`submit_exercise_solution()`](contracts/Evaluator.cairo#L754) in the Evaluator to register it 266 | - Call [`ex12_withdraw_from_contract()`](contracts/Evaluator.cairo#L431) in the evaluator to prove your code works (2 pts) 267 | 268 | ### Exercise 13 - Approve 269 | 270 | - Mint some DTK tokens and use voyager to authorize the evaluator to manipulate them 271 | - Call [`ex13_approved_exercise_solution()`](contracts/Evaluator.cairo#L491) to claim points (1 pts) 272 | 273 | ### Exercise 14 - Revoking approval 274 | 275 | - Use voyager to revoke the previous authorization. 276 | - Call [`ex14_revoked_exercise_solution()`](contracts/Evaluator.cairo#L512) to claim points (1 pts) 277 | 278 | ### Exercise 15 - Using transferFrom 279 | 280 | - Create a function `deposit_tokens()` in your contract through which a user can deposit DTKs in `ExerciseSolution`, by using the `transferFrom` of DTK 281 | - Deploy your contract and call [`submit_exercise_solution()`](contracts/Evaluator.cairo#L754) in the Evaluator to register it 282 | - Call [`ex15_deposit_tokens`](contracts/Evaluator.cairo#L533) in the evaluator to prove your code works (2 pts) 283 | 284 | ### Exercise 16 and 17 - Tracking deposits with a wrapping ERC20 285 | 286 | - Create and deploy a new ERC20 `ExerciseSolutionToken` to track user deposit. This ERC20 should be mintable and mint authorization given to `ExerciseSolution` 287 | - Deploy `ExerciseSolutionToken` and make sure that `ExerciseSolution` knows its address 288 | - Update the deposit function on `ExerciseSolution` so that user balances are tokenized: when a deposit is made in `ExerciseSolution`, tokens are minted in `ExerciseSolutionToken` and transferred to the address depositing 289 | - Deploy your contract and call [`submit_exercise_solution()`](contracts/Evaluator.cairo#L754) in the Evaluator to register it 290 | - Call [`ex16_17_deposit_and_mint`](contracts/Evaluator.cairo#L591) in the evaluator to prove your code works (4 pts) 291 | 292 | ### Exercise 18 - Withdrawing tokens and burning wrapped tokens 293 | 294 | - Update the `ExerciseSolution` withdraw function so that it uses `transferFrom()` in `ExerciseSolutionToken`, burns these tokens, and returns the DTKs 295 | - Deploy your contract and call [`submit_exercise_solution()`](contracts/Evaluator.cairo#L754) in the Evaluator to register it 296 | - Call [`ex18_withdraw_and_burn`](contracts/Evaluator.cairo#L659) in the evaluator to prove your code works (2 pts) 297 | 298 | ​ 299 | ​ 300 | 301 | ## Annex - Useful tools 302 | 303 | ### Converting data to and from decimal 304 | 305 | To convert data to felt use the [`utils.py`](utils.py) script 306 | To open Python in interactive mode after running script 307 | 308 | ```bash 309 | python -i utils.py 310 | ``` 311 | 312 | ```python 313 | >>> str_to_felt('ERC20-101') 314 | 1278752977803006783537 315 | ``` 316 | 317 | ### Checking your progress & counting your points 318 | 319 | ​ 320 | Your points will get credited in your wallet; though this may take some time. If you want to monitor your points count in real time, you can also see your balance in voyager! 321 | ​ 322 | 323 | - Go to the [ERC20 counter](https://goerli.voyager.online/contract/0x228c0e6db14052a66901df14a9e8493c0711fa571860d9c62b6952997aae58b#readContract) in voyager, in the "read contract" tab 324 | - Enter your address in decimal in the "balanceOf" function 325 | 326 | You can also check your overall progress [here](https://starknet-tutorials.vercel.app) 327 | ​ 328 | 329 | ### Transaction status 330 | 331 | ​ 332 | You sent a transaction, and it is shown as "undetected" in voyager? This can mean two things: 333 | ​ 334 | 335 | - Your transaction is pending, and will be included in a block shortly. It will then be visible in voyager. 336 | - Your transaction was invalid, and will NOT be included in a block (there is no such thing as a failed transaction in Starknet). 337 | ​ 338 | You can (and should) check the status of your transaction with the following URL [https://alpha4.starknet.io/feeder_gateway/get_transaction_receipt?transactionHash=](https://alpha4.starknet.io/feeder_gateway/get_transaction_receipt?transactionHash=) , where you can append your transaction hash. 339 | ​ 340 | 341 | ​ 342 | -------------------------------------------------------------------------------- /accounts.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /contracts/Evaluator.cairo: -------------------------------------------------------------------------------- 1 | // ######## ERC-20 Tutorial Evaluator 2 | // Soundtrack https://www.youtube.com/watch?v=iuWa5wh8lG0 3 | 4 | %lang starknet 5 | 6 | from starkware.cairo.common.cairo_builtins import HashBuiltin 7 | from starkware.cairo.common.math import assert_lt, assert_not_zero 8 | 9 | from starkware.starknet.common.syscalls import get_contract_address, get_caller_address 10 | from starkware.cairo.common.uint256 import ( 11 | Uint256, 12 | uint256_add, 13 | uint256_sub, 14 | uint256_mul, 15 | uint256_le, 16 | uint256_lt, 17 | uint256_check, 18 | uint256_eq, 19 | uint256_neg, 20 | ) 21 | 22 | from contracts.lib.UTILS import ( 23 | UTILS_assert_uint256_difference, 24 | UTILS_assert_uint256_eq, 25 | UTILS_assert_uint256_le, 26 | UTILS_assert_uint256_strictly_positive, 27 | UTILS_assert_uint256_zero, 28 | UTILS_assert_uint256_lt, 29 | ) 30 | 31 | from contracts.utils.ex00_base import ( 32 | tuto_erc20_address, 33 | ex_initializer, 34 | has_validated_exercise, 35 | validate_and_distribute_points_once, 36 | only_teacher, 37 | teacher_accounts, 38 | assigned_rank, 39 | assign_rank_to_player, 40 | random_attributes_storage, 41 | max_rank_storage, 42 | ) 43 | 44 | from contracts.token.ERC20.ITUTOERC20 import ITUTOERC20 45 | from contracts.token.ERC20.IDTKERC20 import IDTKERC20 46 | from contracts.token.ERC20.IERC20 import IERC20 47 | 48 | from contracts.IERC20Solution import IERC20Solution 49 | from contracts.IExerciseSolution import IExerciseSolution 50 | 51 | // 52 | // Declaring storage vars 53 | // Storage vars are by default not visible through the ABI. They are similar to "private" variables in Solidity 54 | // 55 | 56 | @storage_var 57 | func dummy_token_address_storage() -> (dummy_token_address_storage: felt) { 58 | } 59 | 60 | // Part 1 is "ERC20", part 2 is "Exercise" 61 | @storage_var 62 | func has_been_paired(contract_address: felt) -> (has_been_paired: felt) { 63 | } 64 | 65 | @storage_var 66 | func player_exercise_solution_storage(player_address: felt, part: felt) -> ( 67 | contract_address: felt 68 | ) { 69 | } 70 | 71 | // 72 | // Declaring getters 73 | // Public variables should be declared explicitly with a getter 74 | // 75 | 76 | @view 77 | func dummy_token_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 78 | account: felt 79 | ) { 80 | let (address) = dummy_token_address_storage.read(); 81 | return (address,); 82 | } 83 | 84 | @view 85 | func player_exercise_solution{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 86 | player_address: felt, part: felt 87 | ) -> (contract_address: felt) { 88 | let (contract_address) = player_exercise_solution_storage.read(player_address, part); 89 | return (contract_address,); 90 | } 91 | 92 | @view 93 | func read_ticker{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 94 | player_address: felt 95 | ) -> (ticker: felt) { 96 | let (rank) = assigned_rank(player_address); 97 | let (ticker) = random_attributes_storage.read(rank, 0); 98 | return (ticker,); 99 | } 100 | 101 | @view 102 | func read_supply{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 103 | player_address: felt 104 | ) -> (supply: Uint256) { 105 | let (rank) = assigned_rank(player_address); 106 | let (supply_felt) = random_attributes_storage.read(rank, 1); 107 | let supply: Uint256 = Uint256(supply_felt, 0); 108 | return (supply,); 109 | } 110 | 111 | // ######## Constructor 112 | // This function is called when the contract is deployed 113 | // 114 | @constructor 115 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 116 | _players_registry: felt, 117 | _tuto_erc20_address: felt, 118 | _dummy_token_address: felt, 119 | _workshop_id: felt, 120 | _first_teacher: felt, 121 | ) { 122 | ex_initializer(_tuto_erc20_address, _players_registry, _workshop_id); 123 | dummy_token_address_storage.write(_dummy_token_address); 124 | teacher_accounts.write(_first_teacher, 1); 125 | // Hard coded value for now 126 | max_rank_storage.write(100); 127 | return (); 128 | } 129 | 130 | // ######## External functions 131 | // These functions are callable by other contracts 132 | // 133 | 134 | @external 135 | func ex1_assign_rank{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 136 | // Allocating locals. Make your code easier to write and read by avoiding some revoked references 137 | alloc_locals; 138 | 139 | // Reading caller address 140 | let (sender_address) = get_caller_address(); 141 | 142 | assign_rank_to_player(sender_address); 143 | 144 | // Distributing points the first time this exercise is completed 145 | validate_and_distribute_points_once(sender_address, 1, 1); 146 | return (); 147 | } 148 | 149 | @external 150 | func ex2_test_erc20{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 151 | alloc_locals; 152 | // Reading caller address 153 | let (sender_address) = get_caller_address(); 154 | 155 | // Retrieve expected characteristics 156 | let (expected_supply) = read_supply(sender_address); 157 | let (expected_symbol) = read_ticker(sender_address); 158 | 159 | // Retrieve player's erc20 solution address 160 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 161 | player_address=sender_address, part=1 162 | ); 163 | 164 | // Reading supply of submission address 165 | let (submission_supply) = IERC20.totalSupply(contract_address=submitted_exercise_address); 166 | // Checking supply is correct 167 | let (is_equal) = uint256_eq(submission_supply, expected_supply); 168 | with_attr error_message("Supply does not match the assignement's request") { 169 | assert is_equal = 1; 170 | } 171 | 172 | // Reading symbol of submission address 173 | let (submission_symbol) = IERC20.symbol(contract_address=submitted_exercise_address); 174 | with_attr error_message("Ticker does not match the assignement's request") { 175 | // Checking symbol is correct 176 | assert submission_symbol = expected_symbol; 177 | } 178 | 179 | // Checking some ERC20 functions were created 180 | let (evaluator_address) = get_contract_address(); 181 | let (balance) = IERC20.balanceOf( 182 | contract_address=submitted_exercise_address, account=evaluator_address 183 | ); 184 | 185 | // 10 tokens 186 | let ten_tokens_uint256: Uint256 = Uint256(10 * 1000000000000000000, 0); 187 | // Check that the Evaluator can approve the spender to transfer ten tokens 188 | IERC20.approve( 189 | contract_address=submitted_exercise_address, 190 | spender=sender_address, 191 | amount=ten_tokens_uint256, 192 | ); 193 | 194 | // Check that the allowance is now 10 195 | let (allowance) = IERC20.allowance( 196 | contract_address=submitted_exercise_address, owner=evaluator_address, spender=sender_address 197 | ); 198 | 199 | // Assertions with Uint256 require a little more verbosity. We'll see more about that later on. 200 | let (is_equal) = uint256_eq(allowance, ten_tokens_uint256); 201 | assert is_equal = 1; 202 | 203 | // Distributing points the first time this exercise is completed 204 | validate_and_distribute_points_once(sender_address, 2, 2); 205 | return (); 206 | } 207 | 208 | @external 209 | func ex3_test_get_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 210 | alloc_locals; 211 | // Reading addresses 212 | let (sender_address) = get_caller_address(); 213 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 214 | player_address=sender_address, part=1 215 | ); 216 | 217 | // test_get_tokens includes a check that the amount returned effectively matches the difference in the evaluator's 218 | // balance. See its implementation at the bottom of this file. 219 | let (has_received_tokens, amount_received) = test_get_tokens(submitted_exercise_address); 220 | 221 | with_attr error_message("No tokens received") { 222 | assert has_received_tokens = 1; 223 | } 224 | 225 | // Distributing points the first time this exercise is completed 226 | validate_and_distribute_points_once(sender_address, 3, 2); 227 | return (); 228 | } 229 | 230 | @external 231 | func ex4_5_6_test_fencing{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 232 | alloc_locals; 233 | // Reading addresses 234 | let (evaluator_address) = get_contract_address(); 235 | let (sender_address) = get_caller_address(); 236 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 237 | player_address=sender_address, part=1 238 | ); 239 | 240 | // Check that Evaluator is not allowed to get tokens 241 | let (allowlist_level_eval) = IERC20Solution.allowlist_level( 242 | contract_address=submitted_exercise_address, account=evaluator_address 243 | ); 244 | 245 | with_attr error_message("Allowlist_level did not return 0 initially") { 246 | assert allowlist_level_eval = 0; 247 | } 248 | 249 | // Try to get token. We use `_` to show that we do not intend to use the second returned value. 250 | let (has_received_tokens, _) = test_get_tokens(submitted_exercise_address); 251 | 252 | // Checking that nothing happened 253 | with_attr error_message("It was possible to get tokens from the start") { 254 | assert has_received_tokens = 0; 255 | } 256 | 257 | // Get whitelisted by asking politely 258 | let (whitelisted) = IERC20Solution.request_allowlist( 259 | contract_address=submitted_exercise_address 260 | ); 261 | 262 | with_attr error_message("request_allowlist did not return the correct value") { 263 | assert whitelisted = 1; 264 | } 265 | 266 | // Check that Evaluator is whitelisted 267 | let (allowlist_level_eval) = IERC20Solution.allowlist_level( 268 | submitted_exercise_address, evaluator_address 269 | ); 270 | with_attr error_message("Allowlist_level did not return the correct value") { 271 | assert_not_zero(allowlist_level_eval); 272 | } 273 | // Check that we can now get tokens 274 | let (has_received_tokens, _) = test_get_tokens(submitted_exercise_address); 275 | 276 | with_attr error_message("Got no tokens when I should have") { 277 | assert has_received_tokens = 1; 278 | } 279 | 280 | // Distributing points the first time this exercise is completed 281 | // Implementing allow list view function 282 | validate_and_distribute_points_once(sender_address, 4, 1); 283 | // Implementing allow list management 284 | validate_and_distribute_points_once(sender_address, 5, 1); 285 | // Linking get tokens to allow list 286 | validate_and_distribute_points_once(sender_address, 6, 2); 287 | return (); 288 | } 289 | 290 | @external 291 | func ex7_8_9_test_fencing_levels{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 292 | ) { 293 | alloc_locals; 294 | // Reading addresses 295 | let (evaluator_address) = get_contract_address(); 296 | let (sender_address) = get_caller_address(); 297 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 298 | sender_address, part=1 299 | ); 300 | 301 | // Check that we are initially not allowed to get tokens. 302 | let (has_received, _) = test_get_tokens(submitted_exercise_address); 303 | assert has_received = 0; 304 | 305 | // Get whitelisted at level 1 306 | let (level) = IERC20Solution.request_allowlist_level( 307 | contract_address=submitted_exercise_address, level_requested=1 308 | ); 309 | assert level = 1; 310 | 311 | // Check allowlist_level view reflects the same change 312 | let (allowlist_level_eval) = IERC20Solution.allowlist_level( 313 | submitted_exercise_address, evaluator_address 314 | ); 315 | assert allowlist_level_eval = 1; 316 | 317 | // Check that we received tokens, and retrieve how much 318 | let (has_received, first_amount_received) = test_get_tokens(submitted_exercise_address); 319 | assert has_received = 1; 320 | 321 | // Get whitelisted at level 2 322 | let (level) = IERC20Solution.request_allowlist_level( 323 | contract_address=submitted_exercise_address, level_requested=2 324 | ); 325 | assert level = 2; 326 | 327 | // Check allowlist_level view 328 | let (allowlist_level_eval) = IERC20Solution.allowlist_level( 329 | submitted_exercise_address, evaluator_address 330 | ); 331 | assert allowlist_level_eval = 2; 332 | 333 | // Check that we received tokens, and retrieve how much 334 | let (has_received, second_amount_received) = test_get_tokens(submitted_exercise_address); 335 | assert has_received = 1; 336 | 337 | // Check that we received more with level 2 than with level 1 338 | let (is_larger) = uint256_lt(first_amount_received, second_amount_received); 339 | assert is_larger = 1; 340 | 341 | // Now is a good time to introduce a few functions made for this tutorial. 342 | // While we can assert in cairo that a felt is smaller than another with `assert_lt(a, b)`, 343 | // it is a little longer for uint256. We've had to use this structure above in two lines of 344 | // code since the earlier exercises to compare Uint256 values. 345 | 346 | // The next line does the same and we'll rather use that for improved readability later on. 347 | // It is defined in `contracts/lib/UTILS.cairo` with similar ones to assert equality, 348 | // positivity, etc. UTILS is the library name, used to prevent name clashes, as described in 349 | // https://github.com/OpenZeppelin/cairo-contracts/blob/main/docs/Extensibility.md 350 | UTILS_assert_uint256_lt(first_amount_received, second_amount_received); 351 | 352 | // Distributing points the first time this exercise is completed 353 | // Denying claiming to non allowed contracts 354 | validate_and_distribute_points_once(sender_address, 7, 1); 355 | // Allowing level 1 claimers 356 | validate_and_distribute_points_once(sender_address, 8, 2); 357 | // Distributing more points to level 2 claimers 358 | validate_and_distribute_points_once(sender_address, 9, 2); 359 | return (); 360 | } 361 | 362 | // ######## 363 | // PART 2 364 | 365 | @external 366 | func ex10_claimed_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 367 | alloc_locals; 368 | // Reading addresses 369 | let (sender_address) = get_caller_address(); 370 | let (read_dtk_address) = dummy_token_address(); 371 | 372 | let (dummy_token_balance) = IERC20.balanceOf( 373 | contract_address=read_dtk_address, account=sender_address 374 | ); 375 | 376 | // Checking that the sender's dummy token balance is positive 377 | UTILS_assert_uint256_strictly_positive(dummy_token_balance); 378 | 379 | // Distributing points the first time this exercise is completed 380 | validate_and_distribute_points_once(sender_address, 10, 2); 381 | return (); 382 | } 383 | 384 | @external 385 | func ex11_claimed_from_contract{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 386 | alloc_locals; 387 | // Reading addresses 388 | let (evaluator_address) = get_contract_address(); 389 | let (sender_address) = get_caller_address(); 390 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 391 | sender_address, part=2 392 | ); 393 | let (read_dtk_address) = dummy_token_address(); 394 | 395 | // Initial state 396 | let (initial_dtk_custody) = IExerciseSolution.tokens_in_custody( 397 | contract_address=submitted_exercise_address, account=evaluator_address 398 | ); 399 | // Initial balance of ExerciseSolution (used to check that the faucet was called during this execution) 400 | let (initial_solution_dtk_balance) = IDTKERC20.balanceOf( 401 | read_dtk_address, submitted_exercise_address 402 | ); 403 | 404 | // Claiming tokens for the evaluator 405 | let (claimed_amount) = IExerciseSolution.get_tokens_from_contract(submitted_exercise_address); 406 | 407 | // Checking that the amount returned is positive 408 | UTILS_assert_uint256_strictly_positive(claimed_amount); 409 | 410 | // Checking that the amount in custody increased 411 | let (final_dtk_custody) = IExerciseSolution.tokens_in_custody( 412 | contract_address=submitted_exercise_address, account=evaluator_address 413 | ); 414 | let (custody_difference) = uint256_sub(final_dtk_custody, initial_dtk_custody); 415 | UTILS_assert_uint256_strictly_positive(custody_difference); 416 | 417 | // Checking that the amount returned is the same as the custody balance increase 418 | UTILS_assert_uint256_eq(custody_difference, claimed_amount); 419 | 420 | // Finally, checking that the balance of ExerciseSolution was also increased by the same amount 421 | let (final_solution_dtk_balance) = IDTKERC20.balanceOf( 422 | read_dtk_address, submitted_exercise_address 423 | ); 424 | UTILS_assert_uint256_difference( 425 | final_solution_dtk_balance, initial_solution_dtk_balance, custody_difference 426 | ); 427 | 428 | // Distributing points the first time this exercise is completed 429 | validate_and_distribute_points_once(sender_address, 11, 3); 430 | return (); 431 | } 432 | 433 | @external 434 | func ex12_withdraw_from_contract{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 435 | ) { 436 | alloc_locals; 437 | // Reading addresses 438 | let (evaluator_address) = get_contract_address(); 439 | let (sender_address) = get_caller_address(); 440 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 441 | sender_address, part=2 442 | ); 443 | let (read_dtk_address) = dummy_token_address_storage.read(); 444 | 445 | // ############## Initial state 446 | // Initial balance of ExerciseSolution that will be used to check that its balance decreased in this tx 447 | let (initial_dtk_balance_submission) = IDTKERC20.balanceOf( 448 | read_dtk_address, submitted_exercise_address 449 | ); 450 | 451 | // Initial balance of Evaluator 452 | let (initial_dtk_balance_eval) = IDTKERC20.balanceOf(read_dtk_address, evaluator_address); 453 | 454 | // Initial amount in custody of ExerciseSolution for Evaluator 455 | let (initial_dtk_custody) = IExerciseSolution.tokens_in_custody( 456 | contract_address=submitted_exercise_address, account=evaluator_address 457 | ); 458 | 459 | // ############## Actions 460 | // Withdrawing tokens claimed in previous exercise 461 | let (withdrawn_amount) = IExerciseSolution.withdraw_all_tokens( 462 | contract_address=submitted_exercise_address 463 | ); 464 | 465 | // Checking that the amount is equal to the total evaluator balance in custody 466 | UTILS_assert_uint256_eq(withdrawn_amount, initial_dtk_custody); 467 | 468 | // ############## Balances checks 469 | // Checking that the evaluator's balance is now increased by `withdrawn_amount` 470 | let (final_dtk_balance_eval) = IDTKERC20.balanceOf(read_dtk_address, evaluator_address); 471 | UTILS_assert_uint256_difference( 472 | final_dtk_balance_eval, initial_dtk_balance_eval, withdrawn_amount 473 | ); 474 | 475 | // Checking that the balance of ExerciseSolution was also decreased by the same amount 476 | let (final_dtk_balance_submission) = IDTKERC20.balanceOf( 477 | read_dtk_address, submitted_exercise_address 478 | ); 479 | UTILS_assert_uint256_difference( 480 | initial_dtk_balance_submission, final_dtk_balance_submission, withdrawn_amount 481 | ); 482 | 483 | // ############## Custody checks 484 | // And finally checking that the amount in custody was decreased by same amount 485 | let (final_dtk_custody) = IExerciseSolution.tokens_in_custody( 486 | contract_address=submitted_exercise_address, account=evaluator_address 487 | ); 488 | UTILS_assert_uint256_difference(initial_dtk_custody, final_dtk_custody, withdrawn_amount); 489 | 490 | // Distributing points the first time this exercise is completed 491 | validate_and_distribute_points_once(sender_address, 12, 2); 492 | return (); 493 | } 494 | 495 | @external 496 | func ex13_approved_exercise_solution{ 497 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr 498 | }() { 499 | alloc_locals; 500 | // Reading addresses 501 | let (sender_address) = get_caller_address(); 502 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 503 | sender_address, part=2 504 | ); 505 | let (read_dtk_address) = dummy_token_address_storage.read(); 506 | 507 | // Check the dummy token allowance of ExerciseSolution 508 | let (submission_dtk_allowance) = IERC20.allowance( 509 | contract_address=read_dtk_address, owner=sender_address, spender=submitted_exercise_address 510 | ); 511 | UTILS_assert_uint256_strictly_positive(submission_dtk_allowance); 512 | 513 | // Distributing points the first time this exercise is completed 514 | validate_and_distribute_points_once(sender_address, 13, 1); 515 | return (); 516 | } 517 | 518 | @external 519 | func ex14_revoked_exercise_solution{ 520 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr 521 | }() { 522 | alloc_locals; 523 | // Reading addresses 524 | let (sender_address) = get_caller_address(); 525 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 526 | sender_address, part=2 527 | ); 528 | let (read_dtk_address) = dummy_token_address_storage.read(); 529 | 530 | // Check the dummy token allowance of ExerciseSolution is zero 531 | let (submission_dtk_allowance) = IERC20.allowance( 532 | contract_address=read_dtk_address, owner=sender_address, spender=submitted_exercise_address 533 | ); 534 | UTILS_assert_uint256_zero(submission_dtk_allowance); 535 | 536 | // Distributing points the first time this exercise is completed 537 | validate_and_distribute_points_once(sender_address, 14, 1); 538 | return (); 539 | } 540 | 541 | @external 542 | func ex15_deposit_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 543 | alloc_locals; 544 | // Reading addresses 545 | let (evaluator_address) = get_contract_address(); 546 | let (sender_address) = get_caller_address(); 547 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 548 | sender_address, part=2 549 | ); 550 | let (read_dtk_address) = dummy_token_address_storage.read(); 551 | 552 | // ############## Initial state 553 | // Reading initial balances of DTK 554 | let (initial_dtk_balance_eval) = IDTKERC20.balanceOf(read_dtk_address, evaluator_address); 555 | let (initial_dtk_balance_submission) = IDTKERC20.balanceOf( 556 | read_dtk_address, submitted_exercise_address 557 | ); 558 | 559 | // Reading initial amount of DTK in custody of ExerciseSolution for Evaluator 560 | let (initial_dtk_custody) = IExerciseSolution.tokens_in_custody( 561 | contract_address=submitted_exercise_address, account=evaluator_address 562 | ); 563 | 564 | // ############## Actions 565 | // Allow ExerciseSolution to spend 10 DTK of Evaluator 566 | let ten_tokens_uint256: Uint256 = Uint256(10 * 1000000000000000000, 0); 567 | IERC20.approve(read_dtk_address, submitted_exercise_address, ten_tokens_uint256); 568 | 569 | // Deposit them into ExerciseSolution 570 | let (total_custody) = IExerciseSolution.deposit_tokens( 571 | contract_address=submitted_exercise_address, amount=ten_tokens_uint256 572 | ); 573 | 574 | // ############## Balances check 575 | // Check that ExerciseSolution's balance of DTK also increased by ten tokens 576 | let (final_dtk_balance_submission) = IDTKERC20.balanceOf( 577 | read_dtk_address, submitted_exercise_address 578 | ); 579 | UTILS_assert_uint256_difference( 580 | final_dtk_balance_submission, initial_dtk_balance_submission, ten_tokens_uint256 581 | ); 582 | 583 | // Check that Evaluator's balance of DTK decreased by ten tokens 584 | let (final_dtk_balance_eval) = IDTKERC20.balanceOf(read_dtk_address, evaluator_address); 585 | UTILS_assert_uint256_difference( 586 | initial_dtk_balance_eval, final_dtk_balance_eval, ten_tokens_uint256 587 | ); 588 | 589 | // ############## Custody check 590 | // Check that the custody balance did increase by ten tokens 591 | let (final_dtk_custody) = IExerciseSolution.tokens_in_custody( 592 | submitted_exercise_address, evaluator_address 593 | ); 594 | UTILS_assert_uint256_difference(final_dtk_custody, initial_dtk_custody, ten_tokens_uint256); 595 | 596 | // Distributing points the first time this exercise is completed 597 | validate_and_distribute_points_once(sender_address, 15, 2); 598 | return (); 599 | } 600 | 601 | @external 602 | func ex16_17_deposit_and_mint{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 603 | alloc_locals; 604 | // Reading addresses 605 | let (evaluator_address) = get_contract_address(); 606 | let (sender_address) = get_caller_address(); 607 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 608 | sender_address, part=2 609 | ); 610 | let (submitted_exercise_token_address) = IExerciseSolution.deposit_tracker_token( 611 | submitted_exercise_address 612 | ); 613 | let (read_dtk_address) = dummy_token_address_storage.read(); 614 | 615 | // ############## Initial state 616 | // Reading ExerciseSolutionToken (est) supply and evaluator's initial balance 617 | let (initial_est_supply) = IERC20.totalSupply(submitted_exercise_token_address); 618 | let (initial_est_balance_eval) = IERC20.balanceOf( 619 | submitted_exercise_token_address, evaluator_address 620 | ); 621 | 622 | // Reading initial balances of DTK 623 | let (initial_dtk_balance_eval) = IDTKERC20.balanceOf(read_dtk_address, evaluator_address); 624 | let (initial_dtk_balance_submission) = IDTKERC20.balanceOf( 625 | read_dtk_address, submitted_exercise_address 626 | ); 627 | 628 | // ############## Actions 629 | // Allow ExerciseSolution to spend 10 DTK of Evaluator 630 | let ten_tokens_uint256: Uint256 = Uint256(10 * 1000000000000000000, 0); 631 | IERC20.approve(read_dtk_address, submitted_exercise_address, ten_tokens_uint256); 632 | 633 | // Deposit them into ExerciseSolution 634 | IExerciseSolution.deposit_tokens( 635 | contract_address=submitted_exercise_address, amount=ten_tokens_uint256 636 | ); 637 | 638 | // ############## Balances checks 639 | // Check that ExerciseSolution's balance of DTK also increased by ten tokens 640 | let (final_dtk_balance_submission) = IDTKERC20.balanceOf( 641 | read_dtk_address, submitted_exercise_address 642 | ); 643 | UTILS_assert_uint256_difference( 644 | final_dtk_balance_submission, initial_dtk_balance_submission, ten_tokens_uint256 645 | ); 646 | 647 | // Check that Evaluator's balance of DTK decreased by ten tokens 648 | let (final_dtk_balance_eval) = IDTKERC20.balanceOf(read_dtk_address, evaluator_address); 649 | UTILS_assert_uint256_difference( 650 | initial_dtk_balance_eval, final_dtk_balance_eval, ten_tokens_uint256 651 | ); 652 | 653 | // ############## ExerciseSolutionToken checks 654 | let (final_est_supply) = IERC20.totalSupply(contract_address=submitted_exercise_token_address); 655 | let (minted_tokens) = uint256_sub(final_est_supply, initial_est_supply); 656 | 657 | // Check that evaluator's balance increased by the minted amount 658 | let (final_est_balance_eval) = IERC20.balanceOf( 659 | submitted_exercise_token_address, evaluator_address 660 | ); 661 | UTILS_assert_uint256_difference( 662 | final_est_balance_eval, initial_est_balance_eval, minted_tokens 663 | ); 664 | 665 | // Distributing points the first time this exercise is completed 666 | // Create and link ERC20 667 | validate_and_distribute_points_once(sender_address, 16, 2); 668 | // Tokenize custody 669 | validate_and_distribute_points_once(sender_address, 17, 2); 670 | return (); 671 | } 672 | 673 | @external 674 | func ex18_withdraw_and_burn{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 675 | alloc_locals; 676 | // Reading addresses 677 | let (evaluator_address) = get_contract_address(); 678 | let (sender_address) = get_caller_address(); 679 | let (submitted_exercise_address) = player_exercise_solution_storage.read( 680 | sender_address, part=2 681 | ); 682 | let (submitted_exercise_token_address) = IExerciseSolution.deposit_tracker_token( 683 | submitted_exercise_address 684 | ); 685 | let (read_dtk_address) = dummy_token_address_storage.read(); 686 | 687 | // ############## Initial state 688 | // Reading ExerciseSolutionToken (est) supply and evaluator's initial balance 689 | let (initial_est_supply) = IERC20.totalSupply(submitted_exercise_token_address); 690 | let (initial_est_balance_eval) = IERC20.balanceOf( 691 | submitted_exercise_token_address, evaluator_address 692 | ); 693 | 694 | // Reading initial balances of DTK 695 | let (initial_dtk_balance_eval) = IDTKERC20.balanceOf(read_dtk_address, evaluator_address); 696 | let (initial_dtk_balance_submission) = IDTKERC20.balanceOf( 697 | read_dtk_address, submitted_exercise_address 698 | ); 699 | 700 | // ############## Actions 701 | // Allow ExerciseSolution to spend all evaluator's ExercisesSolutionTokens 702 | IERC20.approve( 703 | contract_address=submitted_exercise_token_address, 704 | spender=submitted_exercise_address, 705 | amount=initial_est_balance_eval, 706 | ); 707 | 708 | // Withdrawing tokens deposited in previous exercise 709 | let (withdrawn_amount) = IExerciseSolution.withdraw_all_tokens( 710 | contract_address=submitted_exercise_address 711 | ); 712 | 713 | // Checking that some money was withdrawn 714 | UTILS_assert_uint256_strictly_positive(withdrawn_amount); 715 | 716 | // ############## Balances checks 717 | // Checking that the evaluator's balance is now increased by `withdrawn_amount` 718 | let (final_dtk_balance_eval) = IDTKERC20.balanceOf(read_dtk_address, evaluator_address); 719 | UTILS_assert_uint256_difference( 720 | final_dtk_balance_eval, initial_dtk_balance_eval, withdrawn_amount 721 | ); 722 | 723 | // Checking that the balance of ExerciseSolution was also decreased by the same amount 724 | let (final_dtk_balance_submission) = IDTKERC20.balanceOf( 725 | read_dtk_address, submitted_exercise_address 726 | ); 727 | UTILS_assert_uint256_difference( 728 | initial_dtk_balance_submission, final_dtk_balance_submission, withdrawn_amount 729 | ); 730 | 731 | // ############## ExerciseSolutionToken checks 732 | let (final_est_supply) = IERC20.totalSupply(contract_address=submitted_exercise_token_address); 733 | let (burned_amount) = uint256_sub(initial_est_supply, final_est_supply); 734 | 735 | // Check that evaluator's balance decreased by the burned amount 736 | let (final_est_balance_eval) = IERC20.balanceOf( 737 | submitted_exercise_token_address, evaluator_address 738 | ); 739 | UTILS_assert_uint256_difference( 740 | initial_est_balance_eval, final_est_balance_eval, burned_amount 741 | ); 742 | 743 | // Distributing points the first time this exercise is completed 744 | validate_and_distribute_points_once(sender_address, 18, 2); 745 | return (); 746 | } 747 | 748 | // ########### 749 | // Submissions 750 | 751 | @external 752 | func submit_erc20_solution{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 753 | erc20_address: felt 754 | ) { 755 | // Reading caller address 756 | let (sender_address) = get_caller_address(); 757 | // Checking this contract was not used by another group before 758 | let (has_solution_been_submitted_before) = has_been_paired.read(erc20_address); 759 | assert has_solution_been_submitted_before = 0; 760 | 761 | // Assigning passed ERC20 as player ERC20 762 | player_exercise_solution_storage.write( 763 | player_address=sender_address, part=1, value=erc20_address 764 | ); 765 | has_been_paired.write(erc20_address, 1); 766 | 767 | // Distributing points the first time this exercise is completed 768 | validate_and_distribute_points_once(sender_address, 0, 5); 769 | return (); 770 | } 771 | 772 | @external 773 | func submit_exercise_solution{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 774 | exercise_address: felt 775 | ) { 776 | // Reading caller address 777 | let (sender_address) = get_caller_address(); 778 | // Checking this contract was not used by another group before 779 | let (has_solution_been_submitted_before) = has_been_paired.read(exercise_address); 780 | assert has_solution_been_submitted_before = 0; 781 | 782 | // Assigning passed ExerciseSolution to the player 783 | player_exercise_solution_storage.write( 784 | player_address=sender_address, part=2, value=exercise_address 785 | ); 786 | has_been_paired.write(exercise_address, 1); 787 | return (); 788 | } 789 | 790 | // 791 | // Internal functions 792 | // 793 | 794 | func test_get_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 795 | tested_contract: felt 796 | ) -> (has_received_tokens: felt, amount_received: Uint256) { 797 | // This function will 798 | // * get initial evaluator balance on the given contract, 799 | // * call that contract's `get_tokens` 800 | // * get the evaluator's final balance 801 | // and return two values: 802 | // * Whether the evaluator's balance increased or not 803 | // * The balance difference (amount) 804 | // It will also make sure that the two values are consistent (asserts will fail otherwise) 805 | alloc_locals; 806 | let (evaluator_address) = get_contract_address(); 807 | 808 | let (initial_balance) = IERC20.balanceOf( 809 | contract_address=tested_contract, account=evaluator_address 810 | ); 811 | let (amount_received) = IERC20Solution.get_tokens(contract_address=tested_contract); 812 | 813 | // Checking returned value 814 | let zero_as_uint256: Uint256 = Uint256(0, 0); 815 | let (has_received_tokens) = uint256_lt(zero_as_uint256, amount_received); 816 | 817 | // Checking that current balance is initial_balance + amount_received (even if 0) 818 | let (final_balance) = IERC20.balanceOf( 819 | contract_address=tested_contract, account=evaluator_address 820 | ); 821 | UTILS_assert_uint256_difference(final_balance, initial_balance, amount_received); 822 | 823 | return (has_received_tokens, amount_received); 824 | } 825 | -------------------------------------------------------------------------------- /contracts/IERC20Solution.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256 4 | 5 | @contract_interface 6 | namespace IERC20Solution { 7 | func get_tokens() -> (amount: Uint256) { 8 | } 9 | 10 | func allowlist_level(account: felt) -> (level: felt) { 11 | } 12 | 13 | func request_allowlist() -> (level_granted: felt) { 14 | } 15 | 16 | func request_allowlist_level(level_requested: felt) -> (level_granted: felt) { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/IExerciseSolution.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256 4 | 5 | @contract_interface 6 | namespace IExerciseSolution { 7 | func deposit_tokens(amount: Uint256) -> (total_amount: Uint256) { 8 | } 9 | 10 | func tokens_in_custody(account: felt) -> (amount: Uint256) { 11 | } 12 | 13 | func get_tokens_from_contract() -> (amount: Uint256) { 14 | } 15 | 16 | func withdraw_all_tokens() -> (amount: Uint256) { 17 | } 18 | 19 | func deposit_tracker_token() -> (deposit_tracker_token_address: felt) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/lib/UTILS.cairo: -------------------------------------------------------------------------------- 1 | // ######## Uint256 helpers for asserting amount changes 2 | 3 | %lang starknet 4 | 5 | from starkware.cairo.common.cairo_builtins import HashBuiltin 6 | from starkware.cairo.common.uint256 import Uint256, uint256_eq, uint256_le, uint256_lt, uint256_sub 7 | 8 | // UTILS_assert_uint256_X functions will trigger assertion errors, not return 0 9 | 10 | func UTILS_assert_uint256_difference{ 11 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr 12 | }(after: Uint256, before: Uint256, expected_difference: Uint256) { 13 | let (calculated_difference) = uint256_sub(after, before); 14 | let (calculated_is_expected) = uint256_eq(calculated_difference, expected_difference); 15 | assert calculated_is_expected = 1; 16 | return (); 17 | } 18 | 19 | func UTILS_assert_uint256_eq{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 20 | a: Uint256, b: Uint256 21 | ) { 22 | let (is_equal) = uint256_eq(a, b); 23 | assert is_equal = 1; 24 | return (); 25 | } 26 | 27 | func UTILS_assert_uint256_le{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 28 | a: Uint256, b: Uint256 29 | ) { 30 | let (is_le) = uint256_le(a, b); 31 | assert is_le = 1; 32 | return (); 33 | } 34 | 35 | func UTILS_assert_uint256_lt{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 36 | a: Uint256, b: Uint256 37 | ) { 38 | let (is_lt) = uint256_lt(a, b); 39 | assert is_lt = 1; 40 | return (); 41 | } 42 | 43 | func UTILS_assert_uint256_not_zero{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 44 | a: Uint256 45 | ) { 46 | let zero: Uint256 = Uint256(0, 0); 47 | let (is_equal) = uint256_eq(a, zero); 48 | assert is_equal = 0; 49 | return (); 50 | } 51 | 52 | func UTILS_assert_uint256_strictly_positive{ 53 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr 54 | }(a: Uint256) { 55 | let zero: Uint256 = Uint256(0, 0); 56 | let (positive) = uint256_lt(zero, a); 57 | assert positive = 1; 58 | return (); 59 | } 60 | 61 | func UTILS_assert_uint256_zero{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 62 | a: Uint256 63 | ) { 64 | let zero: Uint256 = Uint256(0, 0); 65 | let (is_equal) = uint256_eq(a, zero); 66 | assert is_equal = 1; 67 | return (); 68 | } 69 | -------------------------------------------------------------------------------- /contracts/token/ERC20/DTKERC20.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.cairo.common.uint256 import Uint256 5 | from starkware.starknet.common.syscalls import get_caller_address 6 | from openzeppelin.token.erc20.library import ERC20 7 | 8 | @constructor 9 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 10 | name: felt, symbol: felt, initial_supply: Uint256, recipient: felt 11 | ) { 12 | ERC20.initializer(name, symbol, 18); 13 | ERC20._mint(recipient, initial_supply); 14 | return (); 15 | } 16 | 17 | // 18 | // Getters 19 | // 20 | 21 | @view 22 | func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (name: felt) { 23 | let (name) = ERC20.name(); 24 | return (name,); 25 | } 26 | 27 | @view 28 | func symbol{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (symbol: felt) { 29 | let (symbol) = ERC20.symbol(); 30 | return (symbol,); 31 | } 32 | 33 | @view 34 | func totalSupply{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 35 | totalSupply: Uint256 36 | ) { 37 | let (totalSupply: Uint256) = ERC20.total_supply(); 38 | return (totalSupply,); 39 | } 40 | 41 | @view 42 | func decimals{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 43 | decimals: felt 44 | ) { 45 | let (decimals) = ERC20.decimals(); 46 | return (decimals,); 47 | } 48 | 49 | @view 50 | func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(account: felt) -> ( 51 | balance: Uint256 52 | ) { 53 | let (balance: Uint256) = ERC20.balance_of(account); 54 | return (balance,); 55 | } 56 | 57 | @view 58 | func allowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 59 | owner: felt, spender: felt 60 | ) -> (remaining: Uint256) { 61 | let (remaining: Uint256) = ERC20.allowance(owner, spender); 62 | return (remaining,); 63 | } 64 | 65 | // 66 | // Externals 67 | // 68 | 69 | @external 70 | func faucet{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (success: felt) { 71 | let amount: Uint256 = Uint256(100 * 1000000000000000000, 0); 72 | let (caller) = get_caller_address(); 73 | ERC20._mint(caller, amount); 74 | // Cairo equivalent to 'return (true)' 75 | return (1,); 76 | } 77 | 78 | @external 79 | func transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 80 | recipient: felt, amount: Uint256 81 | ) -> (success: felt) { 82 | ERC20.transfer(recipient, amount); 83 | // Cairo equivalent to 'return (true)' 84 | return (1,); 85 | } 86 | 87 | @external 88 | func transferFrom{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 89 | sender: felt, recipient: felt, amount: Uint256 90 | ) -> (success: felt) { 91 | ERC20.transfer_from(sender, recipient, amount); 92 | // Cairo equivalent to 'return (true)' 93 | return (1,); 94 | } 95 | 96 | @external 97 | func approve{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 98 | spender: felt, amount: Uint256 99 | ) -> (success: felt) { 100 | ERC20.approve(spender, amount); 101 | // Cairo equivalent to 'return (true)' 102 | return (1,); 103 | } 104 | 105 | @external 106 | func increaseAllowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 107 | spender: felt, added_value: Uint256 108 | ) -> (success: felt) { 109 | ERC20.increase_allowance(spender, added_value); 110 | // Cairo equivalent to 'return (true)' 111 | return (1,); 112 | } 113 | 114 | @external 115 | func decreaseAllowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 116 | spender: felt, subtracted_value: Uint256 117 | ) -> (success: felt) { 118 | ERC20.decrease_allowance(spender, subtracted_value); 119 | // Cairo equivalent to 'return (true)' 120 | return (1,); 121 | } 122 | -------------------------------------------------------------------------------- /contracts/token/ERC20/IDTKERC20.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256 4 | 5 | // Dummy token is an ERC20 with a faucet 6 | @contract_interface 7 | namespace IDTKERC20 { 8 | func faucet() -> (success: felt) { 9 | } 10 | 11 | func name() -> (name: felt) { 12 | } 13 | 14 | func symbol() -> (symbol: felt) { 15 | } 16 | 17 | func decimals() -> (decimals: felt) { 18 | } 19 | 20 | func totalSupply() -> (totalSupply: Uint256) { 21 | } 22 | 23 | func balanceOf(account: felt) -> (balance: Uint256) { 24 | } 25 | 26 | func allowance(owner: felt, spender: felt) -> (remaining: Uint256) { 27 | } 28 | 29 | func transfer(recipient: felt, amount: Uint256) -> (success: felt) { 30 | } 31 | 32 | func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) { 33 | } 34 | 35 | func approve(spender: felt, amount: Uint256) -> (success: felt) { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/token/ERC20/IERC20.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256 4 | 5 | @contract_interface 6 | namespace IERC20 { 7 | func name() -> (name: felt) { 8 | } 9 | 10 | func symbol() -> (symbol: felt) { 11 | } 12 | 13 | func decimals() -> (decimals: felt) { 14 | } 15 | 16 | func totalSupply() -> (totalSupply: Uint256) { 17 | } 18 | 19 | func balanceOf(account: felt) -> (balance: Uint256) { 20 | } 21 | 22 | func allowance(owner: felt, spender: felt) -> (remaining: Uint256) { 23 | } 24 | 25 | func transfer(recipient: felt, amount: Uint256) -> (success: felt) { 26 | } 27 | 28 | func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) { 29 | } 30 | 31 | func approve(spender: felt, amount: Uint256) -> (success: felt) { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/token/ERC20/ITUTOERC20.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256 4 | 5 | @contract_interface 6 | namespace ITUTOERC20 { 7 | func distribute_points(to: felt, amount: Uint256) { 8 | } 9 | 10 | func remove_points(to: felt, amount: Uint256) { 11 | } 12 | 13 | func set_teacher(account: felt, permission: felt) { 14 | } 15 | 16 | func is_teacher_or_exercise(account: felt) -> (permission: felt) { 17 | } 18 | 19 | func set_teachers_temp(accounts_len: felt, accounts: felt*) { 20 | } 21 | 22 | func set_teacher_temp(account: felt) { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/token/ERC20/TUTOERC20.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.cairo.common.uint256 import Uint256 5 | from starkware.starknet.common.syscalls import get_caller_address 6 | 7 | from openzeppelin.token.erc20.library import ERC20 8 | 9 | @storage_var 10 | func teachers_and_exercises_accounts(account: felt) -> (balance: felt) { 11 | } 12 | 13 | @storage_var 14 | func is_transferable_storage() -> (is_transferable_storage: felt) { 15 | } 16 | 17 | @constructor 18 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 19 | name: felt, symbol: felt, initial_supply: Uint256, recipient: felt, owner: felt 20 | ) { 21 | ERC20.initializer(name, symbol, 18); 22 | ERC20._mint(recipient, initial_supply); 23 | teachers_and_exercises_accounts.write(owner, 1); 24 | return (); 25 | } 26 | 27 | // 28 | // Getters 29 | // 30 | 31 | @view 32 | func is_transferable{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 33 | is_transferable: felt 34 | ) { 35 | let (is_transferable) = is_transferable_storage.read(); 36 | return (is_transferable,); 37 | } 38 | 39 | @view 40 | func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (name: felt) { 41 | let (name) = ERC20.name(); 42 | return (name,); 43 | } 44 | 45 | @view 46 | func symbol{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (symbol: felt) { 47 | let (symbol) = ERC20.symbol(); 48 | return (symbol,); 49 | } 50 | 51 | @view 52 | func totalSupply{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 53 | totalSupply: Uint256 54 | ) { 55 | let (totalSupply: Uint256) = ERC20.total_supply(); 56 | return (totalSupply,); 57 | } 58 | 59 | @view 60 | func decimals{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 61 | decimals: felt 62 | ) { 63 | let (decimals) = ERC20.decimals(); 64 | return (decimals,); 65 | } 66 | 67 | @view 68 | func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(account: felt) -> ( 69 | balance: Uint256 70 | ) { 71 | let (balance: Uint256) = ERC20.balance_of(account); 72 | return (balance,); 73 | } 74 | 75 | @view 76 | func allowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 77 | owner: felt, spender: felt 78 | ) -> (remaining: Uint256) { 79 | let (remaining: Uint256) = ERC20.allowance(owner, spender); 80 | return (remaining,); 81 | } 82 | 83 | // 84 | // Externals 85 | // 86 | 87 | @external 88 | func transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 89 | recipient: felt, amount: Uint256 90 | ) -> (success: felt) { 91 | _is_transferable(); 92 | ERC20.transfer(recipient, amount); 93 | // Cairo equivalent to 'return (true)' 94 | return (1,); 95 | } 96 | 97 | @external 98 | func transferFrom{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 99 | sender: felt, recipient: felt, amount: Uint256 100 | ) -> (success: felt) { 101 | _is_transferable(); 102 | ERC20.transfer_from(sender, recipient, amount); 103 | // Cairo equivalent to 'return (true)' 104 | return (1,); 105 | } 106 | 107 | @external 108 | func approve{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 109 | spender: felt, amount: Uint256 110 | ) -> (success: felt) { 111 | ERC20.approve(spender, amount); 112 | // Cairo equivalent to 'return (true)' 113 | return (1,); 114 | } 115 | 116 | @external 117 | func increaseAllowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 118 | spender: felt, added_value: Uint256 119 | ) -> (success: felt) { 120 | ERC20.increase_allowance(spender, added_value); 121 | // Cairo equivalent to 'return (true)' 122 | return (1,); 123 | } 124 | 125 | @external 126 | func decreaseAllowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 127 | spender: felt, subtracted_value: Uint256 128 | ) -> (success: felt) { 129 | ERC20.decrease_allowance(spender, subtracted_value); 130 | // Cairo equivalent to 'return (true)' 131 | return (1,); 132 | } 133 | 134 | @external 135 | func distribute_points{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 136 | to: felt, amount: Uint256 137 | ) { 138 | only_teacher_or_exercise(); 139 | ERC20._mint(to, amount); 140 | return (); 141 | } 142 | 143 | @external 144 | func remove_points{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 145 | to: felt, amount: Uint256 146 | ) { 147 | only_teacher_or_exercise(); 148 | ERC20._burn(to, amount); 149 | return (); 150 | } 151 | 152 | @external 153 | func set_teacher{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 154 | account: felt, permission: felt 155 | ) { 156 | only_teacher_or_exercise(); 157 | teachers_and_exercises_accounts.write(account, permission); 158 | 159 | return (); 160 | } 161 | 162 | @external 163 | func set_teachers{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 164 | accounts_len: felt, accounts: felt* 165 | ) { 166 | only_teacher_or_exercise(); 167 | _set_teacher(accounts_len, accounts); 168 | return (); 169 | } 170 | 171 | @external 172 | func set_transferable{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 173 | permission: felt 174 | ) { 175 | only_teacher_or_exercise(); 176 | _set_transferable(permission); 177 | return (); 178 | } 179 | 180 | @view 181 | func is_teacher_or_exercise{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 182 | account: felt 183 | ) -> (permission: felt) { 184 | let (permission: felt) = teachers_and_exercises_accounts.read(account); 185 | return (permission,); 186 | } 187 | 188 | func _set_teacher{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 189 | accounts_len: felt, accounts: felt* 190 | ) { 191 | if (accounts_len == 0) { 192 | // Start with sum=0. 193 | return (); 194 | } 195 | 196 | // If length is NOT zero, then the function calls itself again, by moving forward one slot 197 | _set_teacher(accounts_len=accounts_len - 1, accounts=accounts + 1); 198 | 199 | // This part of the function is first reached when length=0. 200 | teachers_and_exercises_accounts.write([accounts], 1); 201 | return (); 202 | } 203 | 204 | func only_teacher_or_exercise{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 205 | let (caller) = get_caller_address(); 206 | let (permission) = teachers_and_exercises_accounts.read(account=caller); 207 | assert permission = 1; 208 | return (); 209 | } 210 | 211 | func _is_transferable{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 212 | let (permission) = is_transferable_storage.read(); 213 | assert permission = 1; 214 | return (); 215 | } 216 | 217 | func _set_transferable{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 218 | permission: felt 219 | ) { 220 | is_transferable_storage.write(permission); 221 | return (); 222 | } 223 | -------------------------------------------------------------------------------- /contracts/utils/Iplayers_registry.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | //################### 4 | // INTERFACE 5 | //################### 6 | 7 | @contract_interface 8 | namespace Iplayers_registry { 9 | func has_validated_exercise(account: felt, workshop: felt, exercise: felt) -> ( 10 | has_validated_exercise: felt 11 | ) { 12 | } 13 | func is_exercise_or_admin(account: felt) -> (permission: felt) { 14 | } 15 | func next_player_rank() -> (next_player_rank: felt) { 16 | } 17 | func players_registry(rank: felt) -> (account: felt) { 18 | } 19 | func player_ranks(account: felt) -> (rank: felt) { 20 | } 21 | func set_exercise_or_admin(account: felt, permission: felt) { 22 | } 23 | func set_exercises_or_admins(accounts_len: felt, accounts: felt*) { 24 | } 25 | func validate_exercise(account: felt, workshop: felt, exercise: felt) { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/utils/ex00_base.cairo: -------------------------------------------------------------------------------- 1 | // ######## Ex 00 2 | // # A contract from which other contracts can import functions 3 | 4 | %lang starknet 5 | 6 | from starkware.cairo.common.cairo_builtins import HashBuiltin 7 | from starkware.cairo.common.uint256 import ( 8 | Uint256, 9 | uint256_add, 10 | uint256_sub, 11 | uint256_le, 12 | uint256_lt, 13 | uint256_check, 14 | ) 15 | from starkware.cairo.common.math import assert_not_zero 16 | from starkware.starknet.common.syscalls import get_contract_address, get_caller_address 17 | 18 | from contracts.token.ERC20.IERC20 import IERC20 19 | from contracts.token.ERC20.ITUTOERC20 import ITUTOERC20 20 | from contracts.utils.Iplayers_registry import Iplayers_registry 21 | from contracts.lib.UTILS import UTILS_assert_uint256_difference 22 | from contracts.IERC20Solution import IERC20Solution 23 | 24 | // 25 | // Declaring storage vars 26 | // Storage vars are by default not visible through the ABI. They are similar to "private" variables in Solidity 27 | // 28 | 29 | @storage_var 30 | func tuto_erc20_address_storage() -> (tuto_erc20_address_address: felt) { 31 | } 32 | 33 | @storage_var 34 | func players_registry_storage() -> (players_registry_address: felt) { 35 | } 36 | 37 | @storage_var 38 | func workshop_id_storage() -> (workshop_id: felt) { 39 | } 40 | 41 | @storage_var 42 | func teacher_accounts(account: felt) -> (balance: felt) { 43 | } 44 | 45 | @storage_var 46 | func max_rank_storage() -> (max_rank: felt) { 47 | } 48 | 49 | @storage_var 50 | func next_rank_storage() -> (next_rank: felt) { 51 | } 52 | 53 | @storage_var 54 | func random_attributes_storage(rank: felt, column: felt) -> (value: felt) { 55 | } 56 | 57 | @storage_var 58 | func assigned_rank_storage(player_address: felt) -> (rank: felt) { 59 | } 60 | 61 | // 62 | // Declaring getters 63 | // Public variables should be declared explicitly with a getter 64 | // 65 | 66 | @view 67 | func tuto_erc20_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 68 | _tuto_erc20_address: felt 69 | ) { 70 | let (_tuto_erc20_address) = tuto_erc20_address_storage.read(); 71 | return (_tuto_erc20_address,); 72 | } 73 | 74 | @view 75 | func players_registry{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 76 | _players_registry: felt 77 | ) { 78 | let (_players_registry) = players_registry_storage.read(); 79 | return (_players_registry,); 80 | } 81 | 82 | @view 83 | func has_validated_exercise{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 84 | account: felt, exercise_id: felt 85 | ) -> (has_validated_exercise: felt) { 86 | // reading player registry 87 | let (_players_registry) = players_registry_storage.read(); 88 | let (_workshop_id) = workshop_id_storage.read(); 89 | // Checking if the user already validated this exercise 90 | let (has_current_user_validated_exercise) = Iplayers_registry.has_validated_exercise( 91 | contract_address=_players_registry, 92 | account=account, 93 | workshop=_workshop_id, 94 | exercise=exercise_id, 95 | ); 96 | return (has_current_user_validated_exercise,); 97 | } 98 | 99 | @view 100 | func next_rank{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 101 | next_rank: felt 102 | ) { 103 | let (next_rank) = next_rank_storage.read(); 104 | return (next_rank,); 105 | } 106 | 107 | @view 108 | func assigned_rank{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 109 | player_address: felt 110 | ) -> (rank: felt) { 111 | let (rank) = assigned_rank_storage.read(player_address); 112 | return (rank,); 113 | } 114 | 115 | // 116 | // Internal constructor 117 | // This function is used to initialize the contract. It can be called from the constructor 118 | // 119 | 120 | func ex_initializer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 121 | _tuto_erc20_address: felt, _players_registry: felt, _workshop_id: felt 122 | ) { 123 | tuto_erc20_address_storage.write(_tuto_erc20_address); 124 | players_registry_storage.write(_players_registry); 125 | workshop_id_storage.write(_workshop_id); 126 | return (); 127 | } 128 | 129 | // 130 | // Internal functions 131 | // These functions can not be called directly by a transaction 132 | // Similar to internal functions in Solidity 133 | // 134 | 135 | func distribute_points{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 136 | to: felt, amount: felt 137 | ) { 138 | // Converting felt to uint256. We assume it's a small number 139 | // We also add the required number of decimals 140 | let points_to_credit: Uint256 = Uint256(amount * 1000000000000000000, 0); 141 | // Retrieving contract address from storage 142 | let (contract_address) = tuto_erc20_address_storage.read(); 143 | // Calling the ERC20 contract to distribute points 144 | ITUTOERC20.distribute_points(contract_address=contract_address, to=to, amount=points_to_credit); 145 | return (); 146 | } 147 | 148 | func validate_exercise{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 149 | account: felt, exercise_id 150 | ) { 151 | // reading player registry 152 | let (_players_registry) = players_registry_storage.read(); 153 | let (_workshop_id) = workshop_id_storage.read(); 154 | // Checking if the user already validated this exercise 155 | let (has_current_user_validated_exercise) = Iplayers_registry.has_validated_exercise( 156 | contract_address=_players_registry, 157 | account=account, 158 | workshop=_workshop_id, 159 | exercise=exercise_id, 160 | ); 161 | assert (has_current_user_validated_exercise) = 0; 162 | 163 | // Marking the exercise as completed 164 | Iplayers_registry.validate_exercise( 165 | contract_address=_players_registry, 166 | account=account, 167 | workshop=_workshop_id, 168 | exercise=exercise_id, 169 | ); 170 | 171 | return (); 172 | } 173 | 174 | func validate_and_distribute_points_once{ 175 | syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr 176 | }(sender_address: felt, exercise: felt, points: felt) { 177 | // Checking if player has validated this exercise before 178 | let (has_validated) = has_validated_exercise(sender_address, exercise); 179 | // This is necessary because of revoked references. Don't be scared, they won't stay around for too long... 180 | 181 | tempvar syscall_ptr = syscall_ptr; 182 | tempvar pedersen_ptr = pedersen_ptr; 183 | tempvar range_check_ptr = range_check_ptr; 184 | 185 | if (has_validated == 0) { 186 | // player has validated 187 | validate_exercise(sender_address, exercise); 188 | // Sending Setup, contract & deployment points 189 | distribute_points(sender_address, points); 190 | } 191 | return (); 192 | } 193 | 194 | func only_teacher{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 195 | let (caller) = get_caller_address(); 196 | let (permission) = teacher_accounts.read(account=caller); 197 | assert permission = 1; 198 | return (); 199 | } 200 | 201 | @external 202 | func set_teacher{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 203 | account: felt, permission: felt 204 | ) { 205 | only_teacher(); 206 | teacher_accounts.write(account, permission); 207 | 208 | return (); 209 | } 210 | 211 | @view 212 | func is_teacher{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(account: felt) -> ( 213 | permission: felt 214 | ) { 215 | let (permission: felt) = teacher_accounts.read(account); 216 | return (permission,); 217 | } 218 | 219 | // 220 | // External functions - Administration 221 | // Only admins can call these. You don't need to understand them to finish the exercise. 222 | // 223 | 224 | @external 225 | func set_random_values{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 226 | values_len: felt, values: felt*, column: felt 227 | ) { 228 | only_teacher(); 229 | // Check that we fill max_ranK_storage cells 230 | let (max_rank) = max_rank_storage.read(); 231 | assert values_len = max_rank; 232 | // Storing passed values in the store 233 | set_a_random_value(values_len, values, column); 234 | return (); 235 | } 236 | 237 | // 238 | // Internal functions - Administration 239 | // Only admins can call these. You don't need to understand them to finish the exercise. 240 | // 241 | 242 | func set_a_random_value{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 243 | values_len: felt, values: felt*, column: felt 244 | ) { 245 | if (values_len == 0) { 246 | // Start with sum=0. 247 | return (); 248 | } 249 | set_a_random_value(values_len=values_len - 1, values=values + 1, column=column); 250 | random_attributes_storage.write(values_len - 1, column, [values]); 251 | return (); 252 | } 253 | 254 | func assign_rank_to_player{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 255 | sender_address: felt 256 | ) { 257 | alloc_locals; 258 | 259 | // Reading next available slot 260 | let (next_rank) = next_rank_storage.read(); 261 | // Assigning to user 262 | assigned_rank_storage.write(sender_address, next_rank); 263 | 264 | let new_next_rank = next_rank + 1; 265 | let (max_rank) = max_rank_storage.read(); 266 | 267 | // Checking if we reach max_rank 268 | if (new_next_rank == max_rank) { 269 | next_rank_storage.write(0); 270 | } else { 271 | next_rank_storage.write(new_next_rank); 272 | } 273 | return (); 274 | } 275 | -------------------------------------------------------------------------------- /contracts/utils/players_registry.cairo: -------------------------------------------------------------------------------- 1 | // ######## Players registry 2 | // # A contract to record all addresses who participated, and which exercises and workshops they completed 3 | 4 | %lang starknet 5 | 6 | from starkware.cairo.common.cairo_builtins import HashBuiltin 7 | from starkware.cairo.common.uint256 import ( 8 | Uint256, 9 | uint256_add, 10 | uint256_sub, 11 | uint256_le, 12 | uint256_lt, 13 | uint256_check, 14 | ) 15 | from starkware.cairo.common.math import assert_not_zero 16 | from starkware.starknet.common.syscalls import get_caller_address 17 | // 18 | // Declaring storage vars 19 | // Storage vars are by default not visible through the ABI. They are similar to "private" variables in Solidity 20 | // 21 | 22 | @storage_var 23 | func has_validated_exercise_storage(account: felt, workshop: felt, exercise: felt) -> ( 24 | has_validated_exercise_storage: felt 25 | ) { 26 | } 27 | 28 | @storage_var 29 | func exercises_and_admins_accounts(account: felt) -> (permission: felt) { 30 | } 31 | 32 | @storage_var 33 | func next_player_rank_storage() -> (next_player_rank_storage: felt) { 34 | } 35 | 36 | @storage_var 37 | func players_registry_storage(rank: felt) -> (account: felt) { 38 | } 39 | 40 | @storage_var 41 | func players_ranks_storage(account: felt) -> (rank: felt) { 42 | } 43 | 44 | // 45 | // Declaring getters 46 | // Public variables should be declared explicitely with a getter 47 | // 48 | 49 | @view 50 | func has_validated_exercise{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 51 | account: felt, workshop: felt, exercise: felt 52 | ) -> (has_validated_exercise: felt) { 53 | let (has_validated_exercise) = has_validated_exercise_storage.read(account, workshop, exercise); 54 | return (has_validated_exercise,); 55 | } 56 | 57 | @view 58 | func is_exercise_or_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 59 | account: felt 60 | ) -> (permission: felt) { 61 | let (permission: felt) = exercises_and_admins_accounts.read(account); 62 | return (permission,); 63 | } 64 | 65 | @view 66 | func next_player_rank{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( 67 | next_player_rank: felt 68 | ) { 69 | let (next_player_rank) = next_player_rank_storage.read(); 70 | return (next_player_rank,); 71 | } 72 | 73 | @view 74 | func players_registry{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 75 | rank: felt 76 | ) -> (account: felt) { 77 | let (account) = players_registry_storage.read(rank); 78 | return (account,); 79 | } 80 | 81 | @view 82 | func player_ranks{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 83 | account: felt 84 | ) -> (rank: felt) { 85 | let (rank) = players_ranks_storage.read(account); 86 | return (rank,); 87 | } 88 | 89 | // 90 | // Events 91 | // Keeping tracks of what happened 92 | // 93 | @event 94 | func modificate_exercise_or_admin(account: felt, permission: felt) { 95 | } 96 | 97 | @event 98 | func new_player(account: felt, rank: felt) { 99 | } 100 | 101 | @event 102 | func new_validation(account: felt, workshop: felt, exercise: felt) { 103 | } 104 | 105 | // 106 | // Internal constructor 107 | // This function is used to initialize the contract. It can be called from the constructor 108 | // 109 | 110 | @constructor 111 | func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 112 | first_admin: felt 113 | ) { 114 | exercises_and_admins_accounts.write(first_admin, 1); 115 | modificate_exercise_or_admin.emit(account=first_admin, permission=1); 116 | next_player_rank_storage.write(1); 117 | return (); 118 | } 119 | 120 | // 121 | // Internal functions 122 | // These functions can not be called directly by a transaction 123 | // Similar to internal functions in Solidity 124 | // 125 | 126 | func only_exercise_or_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { 127 | let (caller) = get_caller_address(); 128 | let (permission) = exercises_and_admins_accounts.read(account=caller); 129 | assert permission = 1; 130 | return (); 131 | } 132 | 133 | func _set_exercises_or_admins{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 134 | accounts_len: felt, accounts: felt* 135 | ) { 136 | if (accounts_len == 0) { 137 | // Start with sum=0. 138 | return (); 139 | } 140 | 141 | // If length is NOT zero, then the function calls itself again, by moving forward one slot 142 | _set_exercises_or_admins(accounts_len=accounts_len - 1, accounts=accounts + 1); 143 | 144 | // This part of the function is first reached when length=0. 145 | exercises_and_admins_accounts.write([accounts], 1); 146 | modificate_exercise_or_admin.emit(account=[accounts], permission=1); 147 | 148 | return (); 149 | } 150 | 151 | // 152 | // External functions 153 | // 154 | // 155 | // 156 | 157 | @external 158 | func set_exercise_or_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 159 | account: felt, permission: felt 160 | ) { 161 | only_exercise_or_admin(); 162 | exercises_and_admins_accounts.write(account, permission); 163 | modificate_exercise_or_admin.emit(account=account, permission=permission); 164 | 165 | return (); 166 | } 167 | 168 | @external 169 | func set_exercises_or_admins{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 170 | accounts_len: felt, accounts: felt* 171 | ) { 172 | only_exercise_or_admin(); 173 | _set_exercises_or_admins(accounts_len, accounts); 174 | return (); 175 | } 176 | 177 | @external 178 | func validate_exercise{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( 179 | account: felt, workshop: felt, exercise: felt 180 | ) { 181 | only_exercise_or_admin(); 182 | // Checking if the user already validated this exercise 183 | let (has_current_user_validated_exercise) = has_validated_exercise_storage.read( 184 | account, workshop, exercise 185 | ); 186 | assert (has_current_user_validated_exercise) = 0; 187 | 188 | // Marking the exercise as completed 189 | has_validated_exercise_storage.write(account, workshop, exercise, 1); 190 | new_validation.emit(account=account, workshop=workshop, exercise=exercise); 191 | 192 | // Recording player if he is not yet recorded 193 | let (player_rank) = players_ranks_storage.read(account); 194 | 195 | if (player_rank == 0) { 196 | // Player is not yet record, let's record 197 | let (next_player_rank) = next_player_rank_storage.read(); 198 | players_registry_storage.write(next_player_rank, account); 199 | players_ranks_storage.write(account, next_player_rank); 200 | let next_player_rank_plus_one = next_player_rank + 1; 201 | next_player_rank_storage.write(next_player_rank_plus_one); 202 | new_player.emit(account=account, rank=next_player_rank); 203 | tempvar syscall_ptr = syscall_ptr; 204 | tempvar pedersen_ptr = pedersen_ptr; 205 | tempvar range_check_ptr = range_check_ptr; 206 | } else { 207 | tempvar syscall_ptr = syscall_ptr; 208 | tempvar pedersen_ptr = pedersen_ptr; 209 | tempvar range_check_ptr = range_check_ptr; 210 | } 211 | 212 | return (); 213 | } 214 | -------------------------------------------------------------------------------- /deploy/deploying.txt: -------------------------------------------------------------------------------- 1 | # Following order of evaluator's constructor 2 | # Deploy players registry (admin) 3 | nile deploy players_registry 630921626810232507712044280983612479889477627366728615579512531114753636522 --network goerli 4 | 5 | # Deploy TUTOERC20 (ERC20-101 ERC20-101 admin admin) 6 | nile deploy TUTOERC20 1278752977803006783537 1278752977803006783537 0 0 630921626810232507712044280983612479889477627366728615579512531114753636522 630921626810232507712044280983612479889477627366728615579512531114753636522 --network goerli 7 | 8 | # Deploy dummy token (DummyToken-ERC20 DTK20 admin) 9 | nile deploy dummy_token 90997221901889128397906381721202537008 293471990320 100000000000000000000 0 630921626810232507712044280983612479889477627366728615579512531114753636522 --network goerli 10 | 11 | # Deploy evaluator (registry_address TUTOERC20_address DTK20_address Workshop_id admin) 12 | nile deploy Evaluator 0x05af2ba86ed7df13ee2b7557a7e6db163e04c5238980c6e9fdeb4bcc040a48bb 0x037b0ca3995eb2d79626b6a0eac40fe4ba19ddf73d81423626b44755614b9cee 0x029260ce936efafa6d0042bc59757a653e3f992b97960c1c4f8ccd63b7a90136 2 630921626810232507712044280983612479889477627366728615579512531114753636522 --network goerli 13 | 14 | # Set random value stores on evaluator through voyager (set_random_values) 15 | Column: 0 - Tickers 16 | 20 Values: 17 | 1179474266, 1313821013, 1330532182, 1129467970, 1213486657, 1447909197, 1128879685, 1196835666, 1430736209, 1514553670, 1111705174, 1514359370, 1280659284, 1481725258, 1096436313, 1314543695, 1262769744, 1313888849, 1430341977, 1112753489 18 | 100 values: 19 | 1146045253, 1246123597, 1095194946, 1229539148, 1146378315, 1447972440, 1380930885, 1481919816, 1514423620, 1380865093, 1447970121, 1347639363, 1313424714, 1314149714, 1314282072, 1514358355, 1481263182, 1146178124, 1213156683, 1129270861, 1514623319, 1515081808, 1331315536, 1229672001, 1111704643, 1230651986, 1380731732, 1162824782, 1296586325, 1112163394, 1498369876, 1246844240, 1481984340, 1111641942, 1196639555, 1280789843, 1196051542, 1514494545, 1297763154, 1481919820, 1313362001, 1095586649, 1431589459, 1313754188, 1096174913, 1262765395, 1196578885, 1431062350, 1245924184, 1498497603, 1263229530, 1313559621, 1096239194, 1096112194, 1380800078, 1112688714, 1163480154, 1296519509, 1465404249, 1263224399, 1380271447, 1296191831, 1279677518, 1431785299, 1180191310, 1129989449, 1297634636, 1363957319, 1380666188, 1380996691, 1346586457, 1415136583, 1363299162, 1280070481, 1246843724, 1331123524, 1380995159, 1497846082, 1263554369, 1146504773, 1162037337, 1095453511, 1447712589, 1213091650, 1229670221, 1297238857, 1431328854, 1515017289, 1230133831, 1195526998, 1346521154, 1280464705, 1431849558, 1230262872, 1095648083, 1413761609, 1162824514, 1331057752, 1262637655, 1145130563 20 | 21 | Column: 1 - Supplies 22 | 20 values: 23 | 7800000000000000000000, 6600000000000000000000, 610000000000000000000, 6700000000000000000000, 5800000000000000000000, 83000000000000000000000, 2900000000000000000000, 97000000000000000000000, 980000000000000000000, 620000000000000000000, 690000000000000000000, 5300000000000000000000, 92000000000000000000000, 70000000000000000000000, 270000000000000000000, 2400000000000000000000, 8200000000000000000000, 3300000000000000000000, 2700000000000000000000, 76000000000000000000000 24 | 100 values: 25 | 5700000000000000000000, 7800000000000000000000, 6700000000000000000000, 56000000000000000000000, 13000000000000000000000, 930000000000000000000, 80000000000000000000, 450000000000000000000, 95000000000000000000000, 6100000000000000000000, 25000000000000000000000, 700000000000000000000, 93000000000000000000000, 89000000000000000000000, 360000000000000000000, 37000000000000000000000, 410000000000000000000, 470000000000000000000, 2000000000000000000000, 84000000000000000000000, 160000000000000000000, 64000000000000000000000, 4000000000000000000000, 400000000000000000000, 100000000000000000000000, 810000000000000000000, 93000000000000000000000, 410000000000000000000, 3900000000000000000000, 30000000000000000000, 8800000000000000000000, 91000000000000000000000, 83000000000000000000000, 5000000000000000000000, 74000000000000000000000, 62000000000000000000000, 8800000000000000000000, 4900000000000000000000, 430000000000000000000, 41000000000000000000000, 950000000000000000000, 9300000000000000000000, 95000000000000000000000, 100000000000000000000, 990000000000000000000, 9600000000000000000000, 280000000000000000000, 4400000000000000000000, 130000000000000000000, 88000000000000000000000, 64000000000000000000000, 2900000000000000000000, 6900000000000000000000, 830000000000000000000, 850000000000000000000, 80000000000000000000, 1800000000000000000000, 570000000000000000000, 450000000000000000000, 3700000000000000000000, 800000000000000000000, 8400000000000000000000, 9100000000000000000000, 4200000000000000000000, 29000000000000000000000, 600000000000000000000, 77000000000000000000000, 33000000000000000000000, 6200000000000000000000, 91000000000000000000000, 37000000000000000000000, 960000000000000000000, 17000000000000000000000, 9100000000000000000000, 140000000000000000000, 90000000000000000000000, 9800000000000000000000, 6800000000000000000000, 8200000000000000000000, 96000000000000000000000, 36000000000000000000000, 760000000000000000000, 52000000000000000000000, 8800000000000000000000, 570000000000000000000, 72000000000000000000000, 1400000000000000000000, 720000000000000000000, 9800000000000000000000, 60000000000000000000, 1400000000000000000000, 2200000000000000000000, 770000000000000000000, 5900000000000000000000, 66000000000000000000000, 5600000000000000000000, 620000000000000000000, 770000000000000000000, 5800000000000000000000, 160000000000000000000 26 | 27 | 28 | # Set evaluator as admin in TUTOERC20 through voyager 29 | # Set evaluator as admin in players registry through voyager 30 | -------------------------------------------------------------------------------- /tests/test_contract.py: -------------------------------------------------------------------------------- 1 | """contract.cairo test file.""" 2 | import os 3 | 4 | import pytest 5 | from starkware.starknet.testing.starknet import Starknet 6 | 7 | # The path to the contract source code. 8 | CONTRACT_FILE = os.path.join("contracts", "contract.cairo") 9 | 10 | 11 | # The testing library uses python's asyncio. So the following 12 | # decorator and the ``async`` keyword are needed. 13 | @pytest.mark.asyncio 14 | async def test_increase_balance(): 15 | """Test increase_balance method.""" 16 | # Create a new Starknet class that simulates the StarkNet 17 | # system. 18 | starknet = await Starknet.empty() 19 | 20 | # Deploy the contract. 21 | contract = await starknet.deploy( 22 | source=CONTRACT_FILE, 23 | ) 24 | 25 | # Invoke increase_balance() twice. 26 | await contract.increase_balance(amount=10).invoke() 27 | await contract.increase_balance(amount=20).invoke() 28 | 29 | # Check the result of get_balance(). 30 | execution_info = await contract.get_balance().call() 31 | assert execution_info.result == (30,) 32 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | MAX_LEN_FELT = 31 2 | 3 | 4 | def str_to_felt(text): 5 | if len(text) > MAX_LEN_FELT: 6 | raise Exception("Text length too long to convert to felt.") 7 | 8 | return int.from_bytes(text.encode(), "big") 9 | 10 | 11 | def felt_to_str(felt): 12 | length = (felt.bit_length() + 7) // 8 13 | return felt.to_bytes(length, byteorder="big").decode("utf-8") 14 | 15 | 16 | def str_to_felt_array(text): 17 | return [str_to_felt(text[i:i+MAX_LEN_FELT]) for i in range(0, len(text), MAX_LEN_FELT)] 18 | 19 | 20 | def uint256_to_int(uint256): 21 | return uint256[0] + uint256[1]*2**128 22 | 23 | 24 | def uint256(val): 25 | return (val & 2**128-1, (val & (2**256-2**128)) >> 128) 26 | --------------------------------------------------------------------------------