├── .env ├── .gitignore ├── README.md ├── Scarb.lock ├── Scarb.toml ├── install.sh ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── assets.cairo ├── assets ├── Logo.webp ├── challenge1.cairo ├── challenge10.cairo ├── challenge11.cairo ├── challenge12.cairo ├── challenge13.cairo ├── challenge14.cairo ├── challenge14_coin.cairo ├── challenge14_wallet.cairo ├── challenge2.cairo ├── challenge3.cairo ├── challenge4.cairo ├── challenge5.cairo ├── challenge6.cairo ├── challenge7.cairo ├── challenge7_erc20.cairo ├── challenge8.cairo ├── challenge8_dex.cairo ├── challenge8_erc20.cairo ├── challenge8_erc223.cairo ├── challenge9.cairo ├── design.png ├── logo.png ├── main.cairo ├── main_abi.json ├── nft.cairo ├── nft │ ├── 01.jpeg │ ├── 02.jpeg │ ├── 03.jpeg │ ├── 04.jpeg │ ├── 05.jpeg │ ├── 06.jpeg │ ├── 07.jpeg │ ├── 08.jpeg │ ├── 09.jpeg │ ├── 1.json │ ├── 10.jpeg │ ├── 10.json │ ├── 11.jpeg │ ├── 11.json │ ├── 12.jpeg │ ├── 12.json │ ├── 13.jpeg │ ├── 13.json │ ├── 14.jpeg │ ├── 14.json │ ├── 2.json │ ├── 3.json │ ├── 4.json │ ├── 5.json │ ├── 6.json │ ├── 7.json │ ├── 8.json │ └── 9.json └── screenshot.png ├── components ├── Challenge.jsx ├── Home.css ├── Home.jsx ├── Leaderboard.jsx ├── Nopage.jsx ├── ToggleSwitch.css ├── ToggleSwitch.js ├── ToggleSwitch.scss └── logossc.png ├── global.jsx ├── index.css ├── index.js ├── layout ├── Layout.jsx ├── components │ └── sidebar │ │ ├── Navbar.css │ │ ├── Sidebar.jsx │ │ ├── menu.config.js │ │ ├── navItem │ │ ├── NavItem.jsx │ │ ├── NavItemHeader.jsx │ │ └── navItem.module.css │ │ ├── sidebar.module.css │ │ └── starknet.webp └── layout.module.css ├── lib.cairo ├── logo.svg ├── reportWebVitals.js ├── setupTests.js └── utils └── utils.js /.env: -------------------------------------------------------------------------------- 1 | # mandatory 2 | MAIN_CONTRACT_ADDRESS='0x0229f4b42c80ad3d7a497b0f1787d1ef9ae540dd4954c8a45b2bbc3e619eb102' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | *.log 25 | .vercel 26 | /__pycache__ 27 | 28 | # Scarb 29 | /target 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](./src/assets/logo.png) 2 | # INTRODUCTION 3 | Starknet Security Challenges Factory is an open source platform where you can build Starknet CTFs, earn points, keep records on a leaderboard and mint nfts (worth nothing, just for fun) to challenge resolutors. You can play a live version in Sepolia network [here.](https://starknet-security-challenges.app/) 4 | 5 | Here you will find: 6 | 7 | * [Requirements to install as a local CTF.](#requirements) 8 | 9 | * [How to install in local devnet.](#how-to-install) 10 | 11 | * [How to add challenges and contribute.](#how-to-add-a-challenge) 12 | 13 | * [How it works in background.](#how-it-works) 14 | 15 | # REQUIREMENTS 16 | - scarb v2.4.3 17 | ``` 18 | curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh 19 | ``` 20 | - starkli 0.2.4 21 | ``` 22 | curl https://get.starkli.sh | sh 23 | restart your shell 24 | starkliup 25 | ``` 26 | - rust 27 | ``` 28 | sudo curl --proto '=https' -tslv1.2 -sSf https://sh.rustup.rs | sh 29 | ``` 30 | - starknet-devnet-rs commited jan-22 or newer 31 | ``` 32 | git clone https://github.com/0xSpaceShard/starknet-devnet-rs 33 | ``` 34 | - node v19.6.1 35 | ``` 36 | sudo apt-get update 37 | curl -fsSL https://deb.nodesource.com/setup_19.x | sudo -E bash - 38 | sudo apt-get install -y nodejs 39 | ``` 40 | - python3.9 (optional only for cairo0 challenges support) 41 | 42 | - cairo_lang v0.13 (optional only for cairo0 challenges support) 43 | ``` 44 | pip3 install cairo-lang 45 | ``` 46 | # HOW TO INSTALL LOCALLY 47 | 1) Start a local starknet devnet in rust instance 48 | ``` 49 | cd starknet-devnet-rs 50 | cargo run -- --seed 0 51 | ``` 52 | 2) Create your devnet account file: 53 | ``` 54 | starkli account fetch --output ~/devnet-ssc-acct.json 0x7f8460cdc3b7b45b6d9d17c44b5e56deab0df4ab5f313930e02907d58f2a6ba --rpc http://localhost:5050 55 | ``` 56 | 3) Clone repository 57 | ``` 58 | git clone https://github.com/devnet0x/Starknet-Security-Challenges-Factory 59 | ``` 60 | 4) Deploy contracts to local devnet 61 | ``` 62 | cd Starknet-Security-Challenges-Factory 63 | ./install.sh 64 | ``` 65 | 5) Install and run web3 platform 66 | ``` 67 | npm install 68 | npm start run 69 | ``` 70 | 6) Connect your Argentx or Braavos wallet to devnet and play at: 71 | ``` 72 | http://localhost:3000 73 | ``` 74 | ![](./src/assets/screenshot.png) 75 | 76 | # HOW TO ADD A CHALLENGE 77 | 1) Compile your Cairo challenge with a isComplete function returning true when challenge is completed. 78 | ``` 79 | scarb build 80 | ``` 81 | 82 | 2) Declare your Cairo challenge in localhost devnet. 83 | ``` 84 | starkli declare --watch --rpc http://localhost:5050 --account ~/devnet-ssc-acct.json target/dev/ 85 | ``` 86 | 3) Add your challenge to main contract. 87 | ``` 88 | starkli invoke --watch --rpc http://localhost:5050 --account ~/devnet-ssc-acct.json updateChallenge 89 | 90 | Example: 91 | starkli invoke --watch --rpc http://localhost:5050 --account ~/devnet-ssc-acct.json 0x02e82451d558cfeca232b490094daef0fe5148e5bb4a412e2f94aaa45c3483ba updateChallenge 8 1500 0x0649f54b81c3f5a6385f57b25db5131cece97fd92d21aa0af196eeb77b5d4c9c 92 | ``` 93 | 5) Copy your new .cairo file into src/assets 94 | 6) Add your new nft image file to src/assets/nft 95 | 7) Add your new nft json file to src/assets/nft 96 | 8) Edit src/components/Challenge.jsx and add your challenge and descriptions. 97 | 9) Edit src/layout/components/menu_config.js and add your challenge to the menu. 98 | 10) Edit src/App.js and add challenge to page route. 99 | 11) Edit install.py and add your challenge in cairo1_challenge array. 100 | 12) Test your challenge in http://localhost:3000 101 | 13) Send your PR to github. 102 | 103 | # HOW IT WORKS 104 | ![](./src/assets/design.png) 105 | 106 | 1) User press deploy button in web interface. 107 | 2) Starknet-react library calls deploy function on main contract. 108 | 3) Main contract deploys a challenge instance to user. 109 | 4) User exploit and solve challenge. 110 | 5) User press check button in web interface. 111 | 6) Starknet-react library calls check function on main contract. 112 | 7) Main contract calls isComplete funcion in challenge instance. 113 | 8) If isComplete returns true then: 114 | * Main contracts add points to user record (displayed in leaderboard). 115 | * Mint button appears in web interface. 116 | 9) User press mint button 117 | 10) Starknet-react library calls mint function on main contract. 118 | 11) Main contract calls mint funcion in nft smart contract. 119 | 12) User can press link in web interface to watch his nft. 120 | 121 | # HOW TO DEPLOY WEB3 PLATFORM IN PRODUCTION (ONLY PRODUCTION ADMINS). 122 | 1) Commit PR 123 | 2) Clone Repository 124 | ``` 125 | git clone https://github.com/devnet0x/Starknet-Security-Challenges-Factory 126 | ``` 127 | 3) Compile challenge. 128 | ``` 129 | scarb build 130 | ``` 131 | 4) Declare challenge. 132 | ``` 133 | starkli declare --watch --rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_6 --account ~/sepolia-ssc-acct.json target/dev/ 134 | ``` 135 | 5) Register new challenge on main contract. 136 | ``` 137 | starkli invoke --watch --rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_6 --account ~/sepolia-ssc-acct.json updateChallenge 138 | ``` 139 | 6) Upload to test environment. 140 | ``` 141 | vercel login 142 | vercel link (to: starknet-challenges) 143 | vercel (if error then check node version in vercel.com project settings) 144 | ``` 145 | 7) Test interface. 146 | 147 | 8) Upload to production environment. 148 | ``` 149 | vercel --prod 150 | ``` 151 | 152 | # HOW TO UPGRADE CORE CONTRACTS (ONLY PRODUCTION ADMINS) 153 | 154 | 1) Declare new main.cairo or nft.cairo smart contract. 155 | 2) Invoke upgrade 156 | ``` 157 | starkli invoke --watch --rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_6 --account ~/sepolia-ssc-acct.json upgrade 158 | 159 | main contract:0x0667b3f486c25a9afc38626706fb83eabf0f8a6c8a9b7393111f63e51a6dd5dd 160 | nft contract :0x007d85f33b50c06d050cca1889decca8a20e5e08f3546a7f010325cb06e8963f 161 | 162 | WARNING!!! IF CLASS_HASH DOESN'T EXIST WE WILL LOST DATA AND UPGRADE FUNCTIONS. 163 | ``` 164 | -------------------------------------------------------------------------------- /Scarb.lock: -------------------------------------------------------------------------------- 1 | # Code generated by scarb DO NOT EDIT. 2 | version = 1 3 | 4 | [[package]] 5 | name = "openzeppelin" 6 | version = "0.10.0" 7 | source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#d77082732daab2690ba50742ea41080eb23299d3" 8 | 9 | [[package]] 10 | name = "ssc" 11 | version = "0.1.0" 12 | dependencies = [ 13 | "openzeppelin", 14 | ] 15 | -------------------------------------------------------------------------------- /Scarb.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ssc" 3 | version = "0.1.0" 4 | edition = "2023_10" 5 | 6 | # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html 7 | 8 | [dependencies] 9 | starknet = ">=2.6.0" 10 | openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.10.0" } 11 | 12 | [[target.starknet-contract]] 13 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #*************** 4 | #* Environment * 5 | #*************** 6 | 7 | # Account for starknet-devnet-rs (cargo run -- --seed 0) 8 | export STARKNET_PRIVATE_KEY="0x71d7bb07b9a64f6f78ac4c816aff4da9" 9 | STARKNET_ACCOUNT="~/devnet-ssc-acct.json" 10 | STARKNET_RPC="http://localhost:5050" 11 | 12 | #**************************************** 13 | #* Cairo challenge filenames and points * 14 | #**************************************** 15 | declare -A cairo1_challenge 16 | cairo1_challenge["challenge1"]=50 17 | cairo1_challenge["challenge2"]=100 18 | cairo1_challenge["challenge3"]=200 19 | cairo1_challenge["challenge4"]=200 20 | cairo1_challenge["challenge5"]=300 21 | cairo1_challenge["challenge6"]=300 22 | cairo1_challenge["challenge7"]=500 23 | cairo1_challenge["challenge7_erc20"]=0 24 | cairo1_challenge["challenge9"]=500 25 | cairo1_challenge["challenge10"]=700 26 | cairo1_challenge["challenge11"]=300 27 | cairo1_challenge["challenge12"]=700 28 | cairo1_challenge["challenge13"]=700 29 | cairo1_challenge["challenge14"]=1000 30 | cairo1_challenge["challenge14_coin"]=0 31 | cairo1_challenge["challenge14_wallet"]=0 32 | 33 | declare -A cairo0_challenge 34 | cairo0_challenge["challenge8"]=1500 35 | cairo0_challenge["challenge8_dex"]=0 36 | cairo0_challenge["challenge8_erc20"]=0 37 | cairo0_challenge["challenge8_erc223"]=0 38 | 39 | 40 | #************************** 41 | #* Constants (not change) * 42 | #************************** 43 | COMPILED_MAIN_FILE="target/dev/ssc_SecurityChallenge.contract_class.json" 44 | COMPILED_NFT_FILE="target/dev/ssc_StarknetChallengeNft.contract_class.json" 45 | 46 | #************* 47 | #* Compile * 48 | #************* 49 | echo -e "\033[1;32mCompiling...\033[0m" 50 | COMPILE_STATEMENT="scarb build" 51 | echo ${COMPILE_STATEMENT} 52 | eval ${COMPILE_STATEMENT} 53 | if [ $? -ne 0 ] 54 | then 55 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${COMPILE_STATEMENT} 56 | exit 57 | fi 58 | 59 | #***************************** 60 | #* Declare and deploy MAIN * 61 | #***************************** 62 | echo -e "\033[1;32mDeclaring main...\033[0m" 63 | DECLARE_STATEMENT="starkli declare --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${COMPILED_MAIN_FILE} > install.tmp" 64 | echo ${DECLARE_STATEMENT} 65 | eval ${DECLARE_STATEMENT} 66 | if [ $? -ne 0 ] 67 | then 68 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${DECLARE_STATEMENT} 69 | exit 70 | fi 71 | MAIN_CLASS_HASH=$(tail -n 1 install.tmp) 72 | 73 | echo -e "\033[1;32mDeploying main...\033[0m" 74 | DEPLOY_STATEMENT="starkli deploy --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${MAIN_CLASS_HASH} > install.tmp" 75 | echo ${DEPLOY_STATEMENT} 76 | eval ${DEPLOY_STATEMENT} 77 | if [ $? -ne 0 ] 78 | then 79 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${DEPLOY_STATEMENT} 80 | exit 81 | fi 82 | MAIN_CONTRACT_ADDRESS=$(tail -n 1 install.tmp) 83 | echo ${MAIN_CONTRACT_ADDRESS} 84 | 85 | #************************** 86 | #* Declare and deploy NFT * 87 | #************************** 88 | echo -e "\033[1;32mDeclaring nft...\033[0m" 89 | DECLARE_STATEMENT="starkli declare --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${COMPILED_NFT_FILE} > install.tmp" 90 | echo ${DECLARE_STATEMENT} 91 | eval ${DECLARE_STATEMENT} 92 | if [ $? -ne 0 ] 93 | then 94 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${DECLARE_STATEMENT} 95 | exit 96 | fi 97 | NFT_CLASS_HASH=$(tail -n 1 install.tmp) 98 | 99 | echo -e "\033[1;32mDeploying nft...\033[0m" 100 | DEPLOY_STATEMENT="starkli deploy --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${NFT_CLASS_HASH} > install.tmp" 101 | echo ${DEPLOY_STATEMENT} 102 | eval ${DEPLOY_STATEMENT} 103 | if [ $? -ne 0 ] 104 | then 105 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${DEPLOY_STATEMENT} 106 | exit 107 | fi 108 | NFT_CONTRACT_ADDRESS=$(tail -n 1 install.tmp) 109 | echo ${NFT_CONTRACT_ADDRESS} 110 | 111 | #********************* 112 | #* UPDATE global.jsx * 113 | #********************* 114 | echo -e "\033[1;32mUpdating global.jsx...\033[0m" 115 | echo "const global = {}" > ./src/global.jsx 116 | echo "global.MAIN_CONTRACT_ADDRESS = \"${MAIN_CONTRACT_ADDRESS}\";" >> ./src/global.jsx 117 | echo "export default global" >> ./src/global.jsx 118 | 119 | #*************************** 120 | #* SET NFT ADDRESS ON MAIN * 121 | #*************************** 122 | echo -e "\033[1;32mSetting nft address on main...\033[0m" 123 | INVOKE_STATEMENT="starkli invoke --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${MAIN_CONTRACT_ADDRESS} setNFTAddress ${NFT_CONTRACT_ADDRESS} > install.tmp" 124 | echo ${INVOKE_STATEMENT} 125 | eval ${INVOKE_STATEMENT} 126 | if [ $? -ne 0 ] 127 | then 128 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${INVOKE_STATEMENT} 129 | exit 130 | fi 131 | 132 | #********************* 133 | #* SET NFT OWNERSHIP * 134 | #********************* 135 | echo -e "\033[1;32mSetting nft ownership to main...\033[0m" 136 | INVOKE_STATEMENT="starkli invoke --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${NFT_CONTRACT_ADDRESS} transferOwnership ${MAIN_CONTRACT_ADDRESS} > install.tmp" 137 | echo ${INVOKE_STATEMENT} 138 | eval ${INVOKE_STATEMENT} 139 | if [ $? -ne 0 ] 140 | then 141 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${INVOKE_STATEMENT} 142 | exit 143 | fi 144 | 145 | #*********************************** 146 | #* Declare and register challenges * 147 | #*********************************** 148 | echo -e "\033[1;32mDeclaring and registering challenges...\033[0m" 149 | 150 | for challenge_name in "${!cairo1_challenge[@]}" 151 | do 152 | echo -e "\033[1;32mDeclaring challenge $challenge_name...\033[0m" 153 | MOD_NAME=`grep "mod " src/assets/$challenge_name.cairo | awk '{print $2}' | head -1` 154 | FILE_NAME="target/dev/ssc_${MOD_NAME}.contract_class.json" 155 | 156 | DECLARE_STATEMENT="starkli declare --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${FILE_NAME} > install.tmp" 157 | echo ${DECLARE_STATEMENT} 158 | eval ${DECLARE_STATEMENT} 159 | if [ $? -ne 0 ] 160 | then 161 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${DECLARE_STATEMENT} 162 | exit 163 | fi 164 | 165 | CHALLENGE_CLASS_HASH=$(tail -n 1 install.tmp) 166 | 167 | POINTS=${cairo1_challenge[$challenge_name]} 168 | 169 | if [ ${POINTS} -gt 0 ] 170 | then 171 | echo -e "\033[1;32mRegistering challenge $challenge_name...\033[0m" 172 | CHALLENGE_NUMBER=$(echo $challenge_name | grep -o -E '[0-9]+' | head -1) 173 | INVOKE_STATEMENT="starkli invoke --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${MAIN_CONTRACT_ADDRESS} updateChallenge ${CHALLENGE_NUMBER} ${CHALLENGE_CLASS_HASH} ${POINTS} > install.tmp" 174 | echo ${INVOKE_STATEMENT} 175 | eval ${INVOKE_STATEMENT} 176 | if [ $? -ne 0 ] 177 | then 178 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${INVOKE_STATEMENT} 179 | exit 180 | fi 181 | else 182 | echo -e "\033[1;32mAuxiliary challenge $challenge_name not registered...\033[0m" 183 | fi 184 | done 185 | 186 | #***************************** 187 | #* Optional cairo0 challenges * 188 | #***************************** 189 | read -p "Do you want to install cairo0 challenges? (require access to starknet-compile-deprecated) (y/n) " -n 1 -r 190 | echo 191 | if [[ ! $REPLY =~ ^[Yy]$ ]] 192 | then 193 | rm install.tmp 194 | echo -e "\033[1;32mDone.\033[0m" 195 | exit 196 | fi 197 | 198 | #***************************** 199 | #* COMPILE cairo0 challenges * 200 | #***************************** 201 | echo -e "\033[1;32mCompiling cairo0 challenges...\033[0m" 202 | for challenge_name in "${!cairo0_challenge[@]}" 203 | do 204 | echo -e "\033[1;32mCompiling cairo0 challenge ${challenge_name}...\033[0m" 205 | MOD_NAME=`grep "mod " src/assets/$challenge_name.cairo | awk '{print $2}'` 206 | FILE_NAME="target/dev/ssc_${MOD_NAME}.contract_class.json" 207 | 208 | COMPILE_STATEMENT="starknet-compile-deprecated ./src/assets/$challenge_name.cairo --output target/dev/$challenge_name.cairo_compiled.json" 209 | echo ${COMPILE_STATEMENT} 210 | eval ${COMPILE_STATEMENT} 211 | if [ $? -ne 0 ] 212 | then 213 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${COMPILE_STATEMENT} 214 | exit 215 | fi 216 | done 217 | 218 | for challenge_name in "${!cairo0_challenge[@]}" 219 | do 220 | echo -e "\033[1;32mDeclaring and registering challenge $challenge_name...\033[0m" 221 | FILE_NAME="target/dev/$challenge_name.cairo_compiled.json" 222 | 223 | DECLARE_STATEMENT="starkli declare --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${FILE_NAME} > install.tmp" 224 | echo ${DECLARE_STATEMENT} 225 | eval ${DECLARE_STATEMENT} 226 | if [ $? -ne 0 ] 227 | then 228 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${DECLARE_STATEMENT} 229 | exit 230 | fi 231 | 232 | CHALLENGE_CLASS_HASH=$(tail -n 1 install.tmp) 233 | POINTS=${cairo0_challenge[$challenge_name]} 234 | 235 | if [ ${POINTS} -gt 0 ] 236 | then 237 | echo -e "\033[1;32mRegistering cairo0 challenge $challenge_name...\033[0m" 238 | CHALLENGE_NUMBER=$(echo $challenge_name | grep -o -E '[0-9]+' | head -1) 239 | 240 | INVOKE_STATEMENT="starkli invoke --watch --rpc ${STARKNET_RPC} --account ${STARKNET_ACCOUNT} ${MAIN_CONTRACT_ADDRESS} updateChallenge ${CHALLENGE_NUMBER} ${POINTS} ${CHALLENGE_CLASS_HASH} > install.tmp" 241 | echo ${INVOKE_STATEMENT} 242 | eval ${INVOKE_STATEMENT} 243 | if [ $? -ne 0 ] 244 | then 245 | echo -e "\n\033[0;41mFailed command:\033[0m\n"${INVOKE_STATEMENT} 246 | exit 247 | fi 248 | else 249 | echo -e "\033[1;32mAuxiliary challenge $challenge_name not registered...\033[0m" 250 | fi 251 | done 252 | 253 | rm install.tmp 254 | echo -e "\033[1;32mDone.\033[0m" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cte", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@starknet-react/chains": "^0.1.6", 7 | "@starknet-react/core": "^2.2.4", 8 | "@testing-library/jest-dom": "^5.16.5", 9 | "@testing-library/react": "^13.4.0", 10 | "@testing-library/user-event": "^13.5.0", 11 | "buffer": "^6.0.3", 12 | "get-starknet-core": "^3.2.0", 13 | "html-to-image": "^1.11.11", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-icons": "^4.7.1", 17 | "react-router-dom": "^6.6.2", 18 | "react-scripts": "5.0.1", 19 | "react-syntax-highlighter": "^15.5.0", 20 | "starknet": "^5.27.0", 21 | "web-vitals": "^2.1.4" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | "chrome >= 67", 38 | "edge >= 79", 39 | "firefox >= 68", 40 | "opera >= 54", 41 | "safari >= 14" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Security Challenges for Starknet Sepolia 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #343464; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | 32 | @keyframes App-logo-spin { 33 | from { 34 | transform: rotate(0deg); 35 | } 36 | 37 | to { 38 | transform: rotate(360deg); 39 | } 40 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 2 | import Layout from "./layout/Layout"; 3 | import Home from "./components/Home"; 4 | import Leaderboard from "./components/Leaderboard"; 5 | import Nopage from "./components/Nopage"; 6 | import Challenge from "./components/Challenge"; 7 | 8 | export default function App() { 9 | return ( 10 | 11 | 12 | }> 13 | } /> 14 | } /> 15 | }/> 16 | }/> 17 | } /> 18 | } /> 19 | } /> 20 | } /> 21 | } /> 22 | } /> 23 | } /> 24 | } /> 25 | } /> 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/assets.cairo: -------------------------------------------------------------------------------- 1 | mod main; 2 | mod nft; 3 | mod challenge1; 4 | mod challenge2; 5 | mod challenge3; 6 | mod challenge4; 7 | mod challenge5; 8 | mod challenge6; 9 | mod challenge7; 10 | mod challenge7_erc20; 11 | mod challenge8; 12 | mod challenge8_dex; 13 | mod challenge8_erc20; 14 | mod challenge8_erc223; 15 | mod challenge9; 16 | mod challenge10; 17 | mod challenge11; 18 | mod challenge12; 19 | mod challenge13; 20 | mod challenge14; 21 | mod challenge14_coin; 22 | mod challenge14_wallet; 23 | -------------------------------------------------------------------------------- /src/assets/Logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/Logo.webp -------------------------------------------------------------------------------- /src/assets/challenge1.cairo: -------------------------------------------------------------------------------- 1 | // ######## Challenge1 2 | #[starknet::interface] 3 | trait IDeployTrait { 4 | fn isComplete(self: @TContractState) -> bool; 5 | } 6 | 7 | #[starknet::contract] 8 | mod Deploy { 9 | #[storage] 10 | struct Storage {} 11 | 12 | #[abi(embed_v0)] 13 | impl DeployImpl of super::IDeployTrait { 14 | fn isComplete(self: @ContractState) -> bool { 15 | true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/challenge10.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::interface] 2 | trait ICoinFlipTrait { 3 | fn isComplete(self: @TContractState) -> bool; 4 | fn guess(ref self: TContractState, guess: felt252) -> bool; 5 | fn getConsecutiveWins(self: @TContractState) -> u8; 6 | } 7 | 8 | #[starknet::contract] 9 | mod CoinFlip { 10 | use starknet::{ContractAddress, get_block_info, get_caller_address, get_tx_info}; 11 | 12 | const HEAD: felt252 = 1; 13 | const TAIL: felt252 = 0; 14 | 15 | #[storage] 16 | struct Storage { 17 | _consecutive_wins: u8, 18 | _lastGuessFromPlayer: LegacyMap, 19 | } 20 | 21 | /// @notice Event emmited when a coin flip is won 22 | /// @param wins (u8): Players consecutive win count; 23 | #[event] 24 | #[derive(Drop, starknet::Event)] 25 | enum Event { 26 | wins_counter: wins_counter 27 | } 28 | 29 | #[derive(Drop, starknet::Event)] 30 | struct wins_counter { 31 | wins: u8 32 | } 33 | 34 | #[abi(embed_v0)] 35 | impl CoinFlipImpl of super::ICoinFlipTrait { 36 | /// @notice gets a player consecutive win count 37 | /// @return status (u8): Count of consecutive wins by player 38 | fn getConsecutiveWins(self: @ContractState) -> u8 { 39 | self._consecutive_wins.read() 40 | } 41 | 42 | /// @notice Show if the game is completed 43 | /// @return status (bool): Count of consecutive wins by player 44 | fn isComplete(self: @ContractState) -> bool { 45 | let wins = self._consecutive_wins.read(); 46 | 47 | if (wins >= 6) { 48 | true 49 | } else { 50 | false 51 | } 52 | } 53 | 54 | /// @notice evaluates if the player guesses correctly 55 | /// @dev function is extrenal 56 | /// @param guess (felt252): numeric guess of coninflip results HEAD = 1 57 | /// TAIL == 0 58 | /// @return status (bool): true if the player guessed correctly, false if it didn't 59 | fn guess(ref self: ContractState, guess: felt252) -> bool { 60 | let player = get_caller_address(); 61 | let last_guess = self._lastGuessFromPlayer.read(player); 62 | let block_number = starknet::get_block_info().unbox().block_number; 63 | 64 | assert(block_number > last_guess, 'one guess per block'); 65 | 66 | self._lastGuessFromPlayer.write(player, block_number); 67 | 68 | let mut consecutive_wins = self._consecutive_wins.read(); 69 | let mut newConsecutiveWins = 0; 70 | 71 | let answer = self.compute_answer(); 72 | if guess == answer { 73 | newConsecutiveWins = consecutive_wins + 1; 74 | } else { 75 | newConsecutiveWins = 0; 76 | } 77 | 78 | self._consecutive_wins.write(newConsecutiveWins); 79 | 80 | self.emit(Event::wins_counter(wins_counter { wins: newConsecutiveWins })); 81 | 82 | guess == answer 83 | } 84 | } 85 | 86 | #[generate_trait] 87 | impl PrivateMethods of PrivateMethodsTrait { 88 | /// @notice computes the if the answer given is the righ answer 89 | /// @dev interanl function 90 | /// @return status (felt252): ( HEAD or TAIL ) 91 | fn compute_answer(self: @ContractState) -> felt252 { 92 | let txInfo = get_tx_info(); 93 | let entropy: u256 = txInfo.unbox().transaction_hash.into(); 94 | 95 | if (entropy.low % 2 == 0) { 96 | HEAD 97 | } else { 98 | TAIL 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/assets/challenge11.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | 3 | #[starknet::interface] 4 | trait IChallenge11 { 5 | fn isComplete(self: @TContractState) -> bool; 6 | fn changeOwner(ref self: TContractState, _owner: ContractAddress); 7 | } 8 | 9 | #[starknet::contract] 10 | mod Challenge11 { 11 | use starknet::{ContractAddress, get_caller_address, get_tx_info}; 12 | 13 | #[storage] 14 | struct Storage { 15 | owner: ContractAddress, 16 | is_complete: bool 17 | } 18 | 19 | #[constructor] 20 | fn constructor(ref self: ContractState) { 21 | let sender = get_caller_address(); 22 | self.owner.write(sender); 23 | self.is_complete.write(false); 24 | } 25 | 26 | #[abi(embed_v0)] 27 | impl Challenge11 of super::IChallenge11 { 28 | fn isComplete(self: @ContractState) -> bool { 29 | self.is_complete.read() 30 | } 31 | 32 | fn changeOwner(ref self: ContractState, _owner: ContractAddress) { 33 | let tx_info = get_tx_info().unbox(); 34 | let sender = get_caller_address(); 35 | 36 | if (tx_info.account_contract_address != sender) { 37 | self.owner.write(_owner); 38 | self.is_complete.write(true); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/assets/challenge12.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::interface] 2 | trait IVault { 3 | fn unlock(ref self: TContractState, _password: felt252); 4 | fn isComplete(self: @TContractState) -> bool; 5 | } 6 | 7 | #[starknet::contract] 8 | mod Vault { 9 | use starknet::{contract_address_to_felt252, get_tx_info}; 10 | 11 | #[storage] 12 | struct Storage { 13 | locked: bool, 14 | password: felt252 15 | } 16 | 17 | #[constructor] 18 | fn constructor(ref self: ContractState) { 19 | let tx_info = get_tx_info().unbox(); 20 | let param1: felt252 = tx_info.nonce; 21 | let param2: felt252 = contract_address_to_felt252(tx_info.account_contract_address); 22 | 23 | let _password: felt252 = core::pedersen::pedersen(param1, param2); 24 | self.locked.write(true); 25 | self.password.write(_password); 26 | } 27 | 28 | #[abi(embed_v0)] 29 | impl Vault of super::IVault { 30 | fn unlock(ref self: ContractState, _password: felt252) { 31 | if (self.password.read() == _password) { 32 | self.locked.write(false); 33 | } 34 | } 35 | 36 | fn isComplete(self: @ContractState) -> bool { 37 | assert(!self.locked.read(), 'challenge not resolved'); 38 | 39 | true 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/assets/challenge13.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::contract] 2 | mod ERC20_vulnerable { 3 | use core::zeroable::Zeroable; 4 | use starknet::{ContractAddress, contract_address_const, get_caller_address}; 5 | 6 | #[storage] 7 | struct Storage { 8 | time_lock: u64, 9 | name: felt252, 10 | symbol: felt252, 11 | decimals: u8, 12 | total_supply: u256, 13 | balances: LegacyMap::, 14 | allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>, 15 | } 16 | 17 | #[event] 18 | #[derive(Drop, starknet::Event)] 19 | enum Event { 20 | transfer: transfer, 21 | approval: approval, 22 | } 23 | 24 | #[derive(Drop, starknet::Event)] 25 | struct transfer { 26 | from: ContractAddress, 27 | to: ContractAddress, 28 | value: u256, 29 | } 30 | 31 | #[derive(Drop, starknet::Event)] 32 | struct approval { 33 | owner: ContractAddress, 34 | spender: ContractAddress, 35 | value: u256, 36 | } 37 | 38 | #[abi(per_item)] 39 | #[generate_trait] 40 | impl NaughtyCoin of NaughtyCoinTrait { 41 | // Challenge setup 42 | #[constructor] 43 | fn constructor(ref self: ContractState) { 44 | // Set locktime 45 | let blockInfo = starknet::get_block_info().unbox(); 46 | let block_timestamp: u64 = blockInfo.block_timestamp; 47 | 48 | self 49 | .time_lock 50 | .write( 51 | block_timestamp + 10_u64 * 365_u64 * 24_u64 * 60_u64 * 60_u64 52 | ); // block 10 years 53 | 54 | // Set Token 55 | let tx_info = starknet::get_tx_info().unbox(); 56 | let tx_origin: ContractAddress = tx_info.account_contract_address; 57 | 58 | self.name.write('NaughtCoin'); 59 | self.symbol.write('0x0'); 60 | self.decimals.write(16_u8); 61 | let initial_supply = u256 { low: 1000000_u128, high: 0_u128 }; 62 | 63 | // Mint to player 64 | self.balances.write(tx_origin, initial_supply); 65 | self 66 | .emit( 67 | Event::transfer( 68 | transfer { 69 | from: contract_address_const::<0>(), 70 | to: tx_origin, 71 | value: initial_supply 72 | } 73 | ) 74 | ); 75 | } 76 | 77 | // Custom transfer to lock transfers for 10 years 78 | // Prevent the initial owner from transferring tokens until the timelock has passed 79 | #[external(v0)] 80 | fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) { 81 | //Lock check 82 | let blockInfo = starknet::get_block_info().unbox(); 83 | let block_timestamp: u64 = blockInfo.block_timestamp; 84 | assert(block_timestamp > self.time_lock.read(), 'Timelock has not passed'); 85 | 86 | // Transfer 87 | let sender = get_caller_address(); 88 | self.transfer_helper(sender, recipient, amount); 89 | } 90 | 91 | #[external(v0)] 92 | fn isComplete(self: @ContractState) -> bool { 93 | let tx_info = starknet::get_tx_info().unbox(); 94 | let tx_origin: ContractAddress = tx_info.account_contract_address; 95 | assert( 96 | self.balances.read(tx_origin) == u256 { low: 0_u128, high: 0_u128 }, 97 | 'Challenge not resolved' 98 | ); 99 | return (true); 100 | } 101 | 102 | // From here all function are as in the original ERC20 (not modified). 103 | 104 | #[external(v0)] 105 | fn get_name(self: @ContractState) -> felt252 { 106 | self.name.read() 107 | } 108 | 109 | #[external(v0)] 110 | fn get_symbol(self: @ContractState) -> felt252 { 111 | self.symbol.read() 112 | } 113 | 114 | #[external(v0)] 115 | fn get_decimals(self: @ContractState) -> u8 { 116 | self.decimals.read() 117 | } 118 | 119 | #[external(v0)] 120 | fn get_total_supply(self: @ContractState) -> u256 { 121 | self.total_supply.read() 122 | } 123 | 124 | #[external(v0)] 125 | fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { 126 | self.balances.read(account) 127 | } 128 | 129 | #[external(v0)] 130 | fn allowance( 131 | self: @ContractState, owner: ContractAddress, spender: ContractAddress 132 | ) -> u256 { 133 | self.allowances.read((owner, spender)) 134 | } 135 | 136 | #[external(v0)] 137 | fn transfer_from( 138 | ref self: ContractState, 139 | sender: ContractAddress, 140 | recipient: ContractAddress, 141 | amount: u256 142 | ) { 143 | let caller = get_caller_address(); 144 | self.spend_allowance(sender, caller, amount); 145 | self.transfer_helper(sender, recipient, amount); 146 | } 147 | 148 | #[external(v0)] 149 | fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) { 150 | let caller = get_caller_address(); 151 | self.approve_helper(caller, spender, amount); 152 | } 153 | 154 | #[external(v0)] 155 | fn increase_allowance( 156 | ref self: ContractState, spender: ContractAddress, added_value: u256 157 | ) { 158 | let caller = get_caller_address(); 159 | self 160 | .approve_helper( 161 | caller, spender, self.allowances.read((caller, spender)) + added_value 162 | ); 163 | } 164 | 165 | #[external(v0)] 166 | fn decrease_allowance( 167 | ref self: ContractState, spender: ContractAddress, subtracted_value: u256 168 | ) { 169 | let caller = get_caller_address(); 170 | self 171 | .approve_helper( 172 | caller, spender, self.allowances.read((caller, spender)) - subtracted_value 173 | ); 174 | } 175 | 176 | fn transfer_helper( 177 | ref self: ContractState, 178 | sender: ContractAddress, 179 | recipient: ContractAddress, 180 | amount: u256 181 | ) { 182 | assert(!sender.is_zero(), 'ERC20: transfer from 0'); 183 | assert(!recipient.is_zero(), 'ERC20: transfer to 0'); 184 | self.balances.write(sender, self.balances.read(sender) - amount); 185 | self.balances.write(recipient, self.balances.read(recipient) + amount); 186 | self.emit(Event::transfer(transfer { from: sender, to: recipient, value: amount })); 187 | } 188 | 189 | fn spend_allowance( 190 | ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 191 | ) { 192 | let current_allowance = self.allowances.read((owner, spender)); 193 | let ONES_MASK = 0xffffffffffffffffffffffffffffffff_u128; 194 | let is_unlimited_allowance = current_allowance.low == ONES_MASK 195 | && current_allowance.high == ONES_MASK; 196 | if !is_unlimited_allowance { 197 | self.approve_helper(owner, spender, current_allowance - amount); 198 | } 199 | } 200 | 201 | fn approve_helper( 202 | ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 203 | ) { 204 | assert(!spender.is_zero(), 'ERC20: approve from 0'); 205 | self.allowances.write((owner, spender), amount); 206 | self.emit(Event::approval(approval { owner: owner, spender: spender, value: amount })); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/assets/challenge14.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | 3 | #[starknet::interface] 4 | trait IWALLET { 5 | fn donate10(self: @TContractState, dest_: ContractAddress) -> bool; 6 | fn transfer_remainder(self: @TContractState, dest_: ContractAddress); 7 | fn set_coin(ref self: TContractState, coin_: ContractAddress); 8 | } 9 | 10 | #[starknet::interface] 11 | trait ICOIN { 12 | fn get_balance(self: @TContractState, account_: ContractAddress) -> u256; 13 | } 14 | 15 | #[starknet::contract] 16 | mod GoodSamaritan { 17 | use super::{ICOINDispatcher, ICOINDispatcherTrait}; 18 | use super::{IWALLETDispatcher, IWALLETDispatcherTrait}; 19 | use starknet::{ContractAddress, get_caller_address, get_contract_address}; 20 | use starknet::class_hash::ClassHash; 21 | use starknet::contract_address::contract_address_to_felt252; 22 | use starknet::syscalls::deploy_syscall; 23 | 24 | #[storage] 25 | struct Storage { 26 | wallet_address: ContractAddress, 27 | coin_address: ContractAddress, 28 | } 29 | 30 | #[abi(per_item)] 31 | #[generate_trait] 32 | impl GoodSamaritan of GoodSamaritanTrait { 33 | #[constructor] 34 | fn constructor(ref self: ContractState) { 35 | // Deploy wallet 36 | let mut calldata = ArrayTrait::new(); 37 | let wallet_class_hash: ClassHash = starknet::class_hash_const::< 38 | 0x078274c350f7c4d447007b3aec9d49c3e7a2306533d8b218769e99b822d6331d 39 | >(); 40 | 41 | let (address0, _) = deploy_syscall(wallet_class_hash, 0, calldata.span(), false) 42 | .unwrap(); 43 | self.wallet_address.write(address0); 44 | 45 | // Deploy coin 46 | let coin_class_hash: ClassHash = starknet::class_hash_const::< 47 | 0x02fab46bd68f096d1e86a5c34fdc7d90178fe17306259c004bb7ca2628f3ae14 48 | >(); 49 | calldata.append(contract_address_to_felt252(self.wallet_address.read())); 50 | 51 | let (address1, _) = deploy_syscall(coin_class_hash, 0, calldata.span(), false).unwrap(); 52 | self.coin_address.write(address1); 53 | 54 | // Set coin address on wallet 55 | IWALLETDispatcher { contract_address: self.wallet_address.read() } 56 | .set_coin(self.coin_address.read()); 57 | } 58 | 59 | #[external(v0)] 60 | fn get_addresses(self: @ContractState) -> (ContractAddress, ContractAddress) { 61 | (self.wallet_address.read(), self.coin_address.read()) 62 | } 63 | 64 | #[external(v0)] 65 | fn request_donation(self: @ContractState) -> bool { 66 | let sender = get_caller_address(); 67 | let mut enough_balance: bool = true; 68 | let result: bool = IWALLETDispatcher { contract_address: self.wallet_address.read() } 69 | .donate10(sender); 70 | if !result { 71 | IWALLETDispatcher { contract_address: self.wallet_address.read() } 72 | .transfer_remainder(sender); 73 | enough_balance = false 74 | } 75 | 76 | enough_balance 77 | } 78 | 79 | #[external(v0)] 80 | fn isComplete(self: @ContractState) -> bool { 81 | let wallet_balance: u256 = ICOINDispatcher { 82 | contract_address: self.coin_address.read() 83 | } 84 | .get_balance(self.wallet_address.read()); 85 | let eth_0 = u256 { low: 0_u128, high: 0_u128 }; 86 | assert(wallet_balance == eth_0, 'Challenge not resolved'); 87 | 88 | true 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/assets/challenge14_coin.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::interface] 2 | trait INotifyable { 3 | fn notify(self: @TContractState, amount: u256) -> bool; 4 | } 5 | 6 | #[starknet::contract] 7 | mod Coin { 8 | use super::{INotifyableDispatcher, INotifyableDispatcherTrait}; 9 | use starknet::{ContractAddress, get_caller_address}; 10 | 11 | #[storage] 12 | struct Storage { 13 | balances: LegacyMap::, 14 | } 15 | 16 | #[abi(per_item)] 17 | #[generate_trait] 18 | impl Coin of CoinTrait { 19 | #[constructor] 20 | fn constructor(ref self: ContractState, wallet_: ContractAddress) { 21 | let eth_1000000 = u256 { 22 | low: 1000000000000000000000000_u128, high: 0_u128 23 | }; // 1.000.000 eth 24 | self 25 | .balances 26 | .write(wallet_, eth_1000000) // one million coins for Good Samaritan initially 27 | } 28 | 29 | #[external(v0)] 30 | fn transfer(ref self: ContractState, dest_: ContractAddress, amount_: u256) -> bool { 31 | let sender = get_caller_address(); 32 | let current_balance: u256 = self.balances.read(sender); 33 | 34 | // transfer only occurs if balance is enough 35 | if amount_ <= current_balance { 36 | self.balances.write(sender, self.balances.read(sender) - amount_); 37 | self.balances.write(dest_, self.balances.read(dest_) + amount_); 38 | // notify contract 39 | let result: bool = INotifyableDispatcher { contract_address: dest_ } 40 | .notify(amount_); 41 | // revert on unssuccesful notify 42 | if !result { 43 | self.balances.write(sender, self.balances.read(sender) + amount_); 44 | self.balances.write(dest_, self.balances.read(dest_) - amount_); 45 | } 46 | 47 | result 48 | } else { 49 | false 50 | } 51 | } 52 | 53 | #[external(v0)] 54 | fn get_balance(self: @ContractState, account_: ContractAddress) -> u256 { 55 | self.balances.read(account_) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/assets/challenge14_wallet.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | 3 | #[starknet::interface] 4 | trait ICOIN { 5 | fn transfer(ref self: TContractState, dest_: ContractAddress, amount_: u256) -> bool; 6 | fn get_balance(self: @TContractState, account_: ContractAddress) -> u256; 7 | } 8 | 9 | #[starknet::contract] 10 | mod Wallet { 11 | use super::{ICOINDispatcher, ICOINDispatcherTrait}; 12 | use starknet::{ContractAddress, get_caller_address, get_contract_address}; 13 | 14 | #[storage] 15 | // The owner of the wallet instance 16 | struct Storage { 17 | owner: ContractAddress, 18 | coin_address: ContractAddress, 19 | } 20 | 21 | #[abi(per_item)] 22 | #[generate_trait] 23 | impl Wallet of WalletTrait { 24 | #[constructor] 25 | fn constructor(ref self: ContractState) { 26 | let sender = get_caller_address(); 27 | self.owner.write(sender); 28 | } 29 | 30 | #[external(v0)] 31 | fn donate10(self: @ContractState, dest_: ContractAddress) -> bool { 32 | // Only Owner 33 | let sender = get_caller_address(); 34 | assert(sender == self.owner.read(), 'Only Owner'); 35 | 36 | let this = get_contract_address(); 37 | 38 | let eth_10 = u256 { low: 10000000000000000000_u128, high: 0_u128 }; // 10 eth 39 | let current_balance: u256 = ICOINDispatcher { 40 | contract_address: self.coin_address.read() 41 | } 42 | .get_balance(this); 43 | 44 | if current_balance < eth_10 { 45 | false 46 | } else { 47 | // donate 10 coins 48 | ICOINDispatcher { contract_address: self.coin_address.read() } 49 | .transfer(dest_, eth_10) 50 | } 51 | } 52 | 53 | #[external(v0)] 54 | fn transfer_remainder(self: @ContractState, dest_: ContractAddress) { 55 | // Only Owner 56 | let sender = get_caller_address(); 57 | assert(sender == self.owner.read(), 'Only Owner'); 58 | 59 | // transfer balance left 60 | let this = get_contract_address(); 61 | let current_balance: u256 = ICOINDispatcher { 62 | contract_address: self.coin_address.read() 63 | } 64 | .get_balance(this); 65 | ICOINDispatcher { contract_address: self.coin_address.read() } 66 | .transfer(dest_, current_balance); 67 | } 68 | 69 | #[external(v0)] 70 | fn set_coin(ref self: ContractState, coin_: ContractAddress) { 71 | // Only Owner 72 | let sender = get_caller_address(); 73 | assert(sender == self.owner.read(), 'Only Owner'); 74 | 75 | self.coin_address.write(coin_); 76 | } 77 | 78 | #[external(v0)] 79 | fn get_owner(self: @ContractState) -> ContractAddress { 80 | self.owner.read() 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/assets/challenge2.cairo: -------------------------------------------------------------------------------- 1 | // ######## Challenge2 2 | #[starknet::interface] 3 | trait ICallmeTrait { 4 | fn isComplete(self: @TContractState) -> bool; 5 | fn call_me(ref self: TContractState); 6 | } 7 | 8 | #[starknet::contract] 9 | mod Callme { 10 | #[storage] 11 | struct Storage { 12 | is_complete: bool, 13 | } 14 | 15 | #[abi(embed_v0)] 16 | impl CallmeImpl of super::ICallmeTrait { 17 | fn isComplete(self: @ContractState) -> bool { 18 | let output = self.is_complete.read(); 19 | 20 | output 21 | } 22 | 23 | fn call_me(ref self: ContractState) { 24 | self.is_complete.write(true); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/challenge3.cairo: -------------------------------------------------------------------------------- 1 | // ######## Challenge3 2 | #[starknet::interface] 3 | trait IMain { 4 | fn get_nickname(self: @TContractState, _player: felt252) -> felt252; 5 | fn set_nickname(self: @TContractState, _nickname: felt252); 6 | } 7 | 8 | #[starknet::interface] 9 | trait INicknameTrait { 10 | fn isComplete(self: @TContractState) -> bool; 11 | } 12 | 13 | #[starknet::contract] 14 | mod Nickname { 15 | use super::{IMainDispatcherTrait, IMainDispatcher}; 16 | use starknet::contract_address::contract_address_to_felt252; 17 | use starknet::get_caller_address; 18 | 19 | #[storage] 20 | struct Storage {} 21 | 22 | #[abi(embed_v0)] 23 | impl NicknameImpl of super::INicknameTrait { 24 | fn isComplete(self: @ContractState) -> bool { 25 | let sender = get_caller_address(); 26 | let tx_info = starknet::get_tx_info().unbox(); 27 | let nick: felt252 = IMainDispatcher { contract_address: sender } 28 | .get_nickname(contract_address_to_felt252(tx_info.account_contract_address)); 29 | 30 | if (nick == 0) { 31 | false 32 | } else { 33 | true 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/assets/challenge4.cairo: -------------------------------------------------------------------------------- 1 | use starknet::{ContractAddress}; 2 | 3 | #[starknet::interface] 4 | trait IERC20 { 5 | fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; 6 | fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256); 7 | } 8 | 9 | #[starknet::contract] 10 | mod GuessNumber { 11 | use super::{IERC20Dispatcher, IERC20DispatcherTrait}; 12 | use starknet::{get_contract_address, get_caller_address, contract_address_const}; 13 | 14 | #[storage] 15 | struct Storage { 16 | is_complete: bool, 17 | answer: felt252, 18 | } 19 | 20 | #[abi(per_item)] 21 | #[generate_trait] 22 | impl GuessNumberImpl of GuessNumberTrait { 23 | #[constructor] 24 | fn constructor(ref self: ContractState) { 25 | self.answer.write(42); 26 | self.is_complete.write(false); 27 | } 28 | 29 | #[external(v0)] 30 | fn isComplete(self: @ContractState) -> bool { 31 | let output = self.is_complete.read(); 32 | 33 | output 34 | } 35 | 36 | #[external(v0)] 37 | fn guess(ref self: ContractState, n: felt252) { 38 | let l2_token_address = contract_address_const::< 39 | 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 40 | >(); 41 | 42 | let contract_address = get_contract_address(); 43 | let balance = IERC20Dispatcher { contract_address: l2_token_address } 44 | .balanceOf(account: contract_address); 45 | let amount: u256 = 10000000000000000; 46 | 47 | assert(balance == amount, 'deposit required'); 48 | 49 | let number = self.answer.read(); 50 | assert(n == number, 'Incorrect guessed number'); 51 | 52 | let sender = get_caller_address(); 53 | IERC20Dispatcher { contract_address: l2_token_address } 54 | .transfer(recipient: sender, amount: amount); 55 | 56 | self.is_complete.write(true); 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/assets/challenge5.cairo: -------------------------------------------------------------------------------- 1 | use starknet::{ContractAddress}; 2 | 3 | #[starknet::interface] 4 | trait IERC20 { 5 | fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; 6 | fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256); 7 | } 8 | 9 | #[starknet::contract] 10 | mod SecretNumber { 11 | use super::{IERC20Dispatcher, IERC20DispatcherTrait}; 12 | use core::hash::{HashStateTrait, HashStateExTrait}; 13 | use starknet::{get_contract_address, get_caller_address, contract_address_const}; 14 | 15 | const hash_result: felt252 = 0x23c16a2a9adbcd4988f04bbc6bc6d90275cfc5a03fbe28a6a9a3070429acb96; 16 | 17 | #[storage] 18 | struct Storage { 19 | is_complete: bool, 20 | } 21 | 22 | #[abi(per_item)] 23 | #[generate_trait] 24 | impl SecretNumberImpl of SecretNumberTrait { 25 | #[constructor] 26 | fn constructor(ref self: ContractState) { 27 | self.is_complete.write(false); 28 | } 29 | 30 | #[external(v0)] 31 | fn isComplete(self: @ContractState) -> bool { 32 | let output = self.is_complete.read(); 33 | 34 | output 35 | } 36 | 37 | #[external(v0)] 38 | fn guess(ref self: ContractState, n: felt252) { 39 | let l2_token_address = contract_address_const::< 40 | 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 41 | >(); 42 | let contract_address = get_contract_address(); 43 | let balance: u256 = IERC20Dispatcher { contract_address: l2_token_address } 44 | .balanceOf(account: contract_address); 45 | let amount: u256 = 10000000000000000; 46 | assert(balance == amount, 'deposit required'); 47 | 48 | let res = core::pedersen::pedersen(1000, n); 49 | assert(res == hash_result, 'Incorrect guessed number.'); 50 | 51 | let sender = get_caller_address(); 52 | IERC20Dispatcher { contract_address: l2_token_address } 53 | .transfer(recipient: sender, amount: amount); 54 | 55 | self.is_complete.write(true); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/assets/challenge6.cairo: -------------------------------------------------------------------------------- 1 | use starknet::{ContractAddress}; 2 | 3 | #[starknet::interface] 4 | trait IERC20 { 5 | fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; 6 | fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256); 7 | } 8 | 9 | #[starknet::contract] 10 | mod Random { 11 | use super::{IERC20Dispatcher, IERC20DispatcherTrait}; 12 | use core::hash::{HashStateTrait, HashStateExTrait}; 13 | use starknet::{ 14 | ContractAddress, get_block_info, get_block_timestamp, get_contract_address, 15 | get_caller_address, contract_address_const 16 | }; 17 | 18 | #[storage] 19 | struct Storage { 20 | is_complete: bool, 21 | hash_result: felt252, 22 | } 23 | 24 | #[abi(per_item)] 25 | #[generate_trait] 26 | impl RandomImpl of RandomTrait { 27 | #[constructor] 28 | fn constructor(ref self: ContractState) { 29 | let block_number = get_block_info().unbox().block_number.into(); 30 | let block_timestamp = get_block_timestamp(); 31 | let res = core::pedersen::pedersen(block_number - 1, block_timestamp.into()); 32 | self.hash_result.write(res); 33 | self.is_complete.write(false); 34 | } 35 | 36 | #[external(v0)] 37 | fn isComplete(self: @ContractState) -> bool { 38 | let output = self.is_complete.read(); 39 | 40 | output 41 | } 42 | 43 | #[external(v0)] 44 | fn guess(ref self: ContractState, n: felt252) { 45 | let l2_token_address = contract_address_const::< 46 | 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 47 | >(); 48 | let contract_address = get_contract_address(); 49 | let balance: u256 = IERC20Dispatcher { contract_address: l2_token_address } 50 | .balanceOf(account: contract_address); 51 | let amount: u256 = 10000000000000000; 52 | assert(balance == amount, 'deposit required'); 53 | 54 | let answer = self.hash_result.read(); 55 | let diff = n - answer; 56 | 57 | if (diff == 0) { 58 | let sender = get_caller_address(); 59 | IERC20Dispatcher { contract_address: l2_token_address } 60 | .transfer(recipient: sender, amount: amount); 61 | 62 | self.is_complete.write(true); 63 | } else { 64 | let block_number = get_block_info().unbox().block_number.into(); 65 | let block_timestamp = get_block_timestamp(); 66 | let res = core::pedersen::pedersen(block_number - 1, block_timestamp.into()); 67 | 68 | self.hash_result.write(res); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/assets/challenge7.cairo: -------------------------------------------------------------------------------- 1 | use starknet::{ContractAddress}; 2 | 3 | #[starknet::interface] 4 | trait ICustomERC20 { 5 | fn balance_of(self: @TContractState, account: ContractAddress) -> u256; 6 | } 7 | 8 | #[starknet::interface] 9 | trait IVitaTokenChallenge { 10 | fn isComplete(self: @TContractState) -> bool; 11 | fn get_vtoken_address(self: @TContractState) -> ContractAddress; 12 | } 13 | 14 | #[starknet::contract] 15 | mod VitaTokenChallenge { 16 | use super::{ICustomERC20Dispatcher, ICustomERC20DispatcherTrait}; 17 | use starknet::syscalls::deploy_syscall; 18 | use starknet::{ 19 | ContractAddress, get_contract_address, ClassHash, class_hash_to_felt252, class_hash_const 20 | }; 21 | 22 | #[storage] 23 | struct Storage { 24 | vtoken_address: ContractAddress, 25 | salt: u128 26 | } 27 | 28 | #[constructor] 29 | fn constructor(ref self: ContractState) { 30 | let vtoken_address: ContractAddress = get_contract_address(); 31 | let current_salt: felt252 = self.salt.read().into(); 32 | let ERC20_name = 94920107574606; 33 | let ERC20_symbol = 1448365131; 34 | let ERC20_intial_supply: u256 = 100000000000000000000; 35 | let ERC20_initial_supply_low = ERC20_intial_supply.low; 36 | let ERC20_initial_supply_high = ERC20_intial_supply.high; 37 | let mut calldata = array![ 38 | ERC20_name.into(), 39 | ERC20_symbol.into(), 40 | ERC20_initial_supply_low.into(), 41 | ERC20_initial_supply_high.into(), 42 | vtoken_address.into() 43 | ]; 44 | 45 | let vtoken_class_hash: ClassHash = class_hash_const::< 46 | 0x07903b722bdaac5ad5140e1a951f8565bd5763a54242d8e56dfe3c0a15b3d0c4 47 | >(); 48 | 49 | let (new_contract_address, _) = deploy_syscall( 50 | vtoken_class_hash, current_salt, calldata.span(), false 51 | ) 52 | .expect('failed to deploy vtoken'); 53 | self.salt.write(self.salt.read() + 1); 54 | self.vtoken_address.write(new_contract_address); 55 | } 56 | 57 | #[abi(embed_v0)] 58 | impl Challenge7Real of super::IVitaTokenChallenge { 59 | fn isComplete(self: @ContractState) -> bool { 60 | let vitalik_address = get_contract_address(); 61 | let vtoken: ContractAddress = self.vtoken_address.read(); 62 | let erc20_dispatcher = ICustomERC20Dispatcher { contract_address: vtoken }; 63 | let current_balance = erc20_dispatcher.balance_of(vitalik_address); 64 | assert(current_balance == 0, 'challenge not completed yet'); 65 | 66 | true 67 | } 68 | 69 | fn get_vtoken_address(self: @ContractState) -> ContractAddress { 70 | self.vtoken_address.read() 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/assets/challenge7_erc20.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | 3 | #[starknet::interface] 4 | trait ICustomERC20 { 5 | fn balance_of(self: @TContractState, account: ContractAddress) -> u256; 6 | fn transfer_from( 7 | ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 8 | ); 9 | fn approve( 10 | ref self: TContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 11 | ); 12 | } 13 | 14 | #[starknet::contract] 15 | mod CustomERC20 { 16 | use core::num::traits::zero::Zero; 17 | use core::integer::BoundedInt; 18 | use starknet::{ContractAddress, get_caller_address}; 19 | 20 | #[storage] 21 | struct Storage { 22 | ERC20_name: felt252, 23 | ERC20_symbol: felt252, 24 | ERC20_total_supply: u256, 25 | ERC20_balances: LegacyMap, 26 | ERC20_allowances: LegacyMap<(ContractAddress, ContractAddress), u256> 27 | } 28 | 29 | #[constructor] 30 | fn constructor( 31 | ref self: ContractState, 32 | name: felt252, 33 | symbol: felt252, 34 | initial_supply_low: u128, 35 | initial_supply_high: u128, 36 | recipient: ContractAddress 37 | ) { 38 | let initial_supply: u256 = u256 { low: initial_supply_low, high: initial_supply_high }; 39 | self.ERC20_name.write(name); 40 | self.ERC20_symbol.write(symbol); 41 | self._mint(recipient, initial_supply); 42 | } 43 | 44 | mod Errors { 45 | const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; 46 | const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; 47 | const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; 48 | const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; 49 | const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; 50 | const INSUFFICIENT_BALANCE: felt252 = 'ERC20: insufficient balance'; 51 | } 52 | 53 | #[abi(embed_v0)] 54 | impl CustomERC20 of super::ICustomERC20 { 55 | fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { 56 | self.ERC20_balances.read(account) 57 | } 58 | 59 | fn transfer_from( 60 | ref self: ContractState, 61 | sender: ContractAddress, 62 | recipient: ContractAddress, 63 | amount: u256 64 | ) { 65 | let caller = get_caller_address(); 66 | self._spend_allowance(sender, caller, amount); 67 | self._transfer(sender, recipient, amount); 68 | } 69 | 70 | fn approve( 71 | ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 72 | ) { 73 | self._approve(owner, spender, amount); 74 | } 75 | } 76 | 77 | #[generate_trait] 78 | impl Private of PrivateTrait { 79 | fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { 80 | assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); 81 | self.ERC20_total_supply.write(self.ERC20_total_supply.read() + amount); 82 | self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount); 83 | } 84 | 85 | fn _transfer( 86 | ref self: ContractState, 87 | sender: ContractAddress, 88 | recipient: ContractAddress, 89 | amount: u256 90 | ) { 91 | assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); 92 | assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); 93 | assert(self.ERC20_balances.read(sender) >= amount, Errors::INSUFFICIENT_BALANCE); 94 | self.ERC20_balances.write(sender, self.ERC20_balances.read(sender) - amount); 95 | self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount); 96 | } 97 | 98 | fn _approve( 99 | ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 100 | ) { 101 | assert(!owner.is_zero(), Errors::APPROVE_FROM_ZERO); 102 | assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO); 103 | self.ERC20_allowances.write((owner, spender), amount); 104 | } 105 | 106 | fn _spend_allowance( 107 | ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 108 | ) { 109 | let current_allowance = self.ERC20_allowances.read((owner, spender)); 110 | if current_allowance != BoundedInt::max() { 111 | assert(current_allowance >= amount, Errors::INSUFFICIENT_BALANCE); 112 | self._approve(owner, spender, current_allowance - amount); 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/assets/challenge8.cairo: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // ######## Interfaces 4 | 5 | #[starknet::interface] 6 | trait IInsecureDexLP { 7 | fn add_liquidity(ref self: TContractState, amount0: u256, amount1: u256) -> u256; 8 | } 9 | 10 | #[starknet::interface] 11 | trait IAttacker { 12 | fn exploit(ref self: TContractState); 13 | } 14 | 15 | #[starknet::contract] 16 | mod Deployer { 17 | use super::{IAttackerDispatcher, IAttackerDispatcherTrait}; 18 | use super::{IInsecureDexLPDispatcher, IInsecureDexLPDispatcherTrait}; 19 | use starknet::{get_caller_address, get_contract_address, ContractAddress}; 20 | use starknet::syscalls::deploy_syscall; 21 | use starknet::class_hash::ClassHash; 22 | use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; 23 | 24 | // ######## Constants 25 | const TOKEN_1: u256 = 1000000000000000000; // 1 * 10**18 26 | const TOKEN_10: u256 = 10000000000000000000; // 10 * 10**18 27 | const TOKEN_100: u256 = 100000000000000000000; // 100 * 10**18 28 | 29 | #[storage] 30 | struct Storage { 31 | salt: felt252, 32 | isec_address: ContractAddress, 33 | set_address: ContractAddress, 34 | dex_address: ContractAddress, 35 | } 36 | 37 | #[constructor] 38 | fn constructor(ref self: ContractState) { 39 | let deployer_address = get_contract_address(); 40 | let current_salt = self.salt.read(); 41 | 42 | let calldata = serialize_token_calldata(@deployer_address, @TOKEN_100); 43 | 44 | // Deploy ERC20 ISEC and mint 100 ISEC 45 | let isec_class_hash: ClassHash = starknet::class_hash_const::< 46 | 0x00a8497d9232a1ca3f83cc7abfc04a65e5a30b9445152031a10bd84afae2f1ee 47 | >(); 48 | 49 | let (isec, _) = deploy_syscall(isec_class_hash, current_salt, calldata.span(), false,) 50 | .unwrap(); 51 | self.isec_address.write(isec); 52 | self.salt.write(current_salt + 1); 53 | 54 | // Deploy ERC223 ISET and mint 100 SET 55 | let set_class_hash: ClassHash = starknet::class_hash_const::< 56 | 0x06e3b7cdd61ed896ad5d197164714b077a7da78654c67a9b74a2edd916513bd4 57 | >(); 58 | 59 | let (set, _) = deploy_syscall(set_class_hash, current_salt, calldata.span(), false,) 60 | .unwrap(); 61 | self.set_address.write(set); 62 | self.salt.write(current_salt + 1); 63 | 64 | // Deploy DEX 65 | let calldata2 = serialize_dex_calldata(@self.get_isec_address(), @self.get_set_address()); 66 | 67 | let dex_class_hash: ClassHash = starknet::class_hash_const::< 68 | 0x044b9402d7d37eae047ca955fde48d292985873ec2876f7b0ab92a33c99330a8 69 | >(); 70 | 71 | let (dex, _) = deploy_syscall(dex_class_hash, current_salt, calldata2.span(), false,) 72 | .unwrap(); 73 | self.dex_address.write(dex); 74 | self.salt.write(current_salt + 1); 75 | 76 | //Add liquidity (10ISEC and 10SET) 77 | IERC20Dispatcher { contract_address: self.get_isec_address() } 78 | .approve(self.get_dex_address(), TOKEN_10); 79 | IERC20Dispatcher { contract_address: self.get_set_address() } 80 | .approve(self.get_dex_address(), TOKEN_10); 81 | IInsecureDexLPDispatcher { contract_address: self.get_dex_address() } 82 | .add_liquidity(TOKEN_10, TOKEN_10); 83 | } 84 | 85 | #[abi(per_item)] 86 | #[generate_trait] 87 | impl Main of IMain { 88 | // ######## Getters 89 | #[external(v0)] 90 | fn get_isec_address(self: @ContractState) -> ContractAddress { 91 | self.isec_address.read() 92 | } 93 | 94 | #[external(v0)] 95 | fn get_set_address(self: @ContractState) -> ContractAddress { 96 | self.set_address.read() 97 | } 98 | 99 | #[external(v0)] 100 | fn get_dex_address(self: @ContractState) -> ContractAddress { 101 | self.dex_address.read() 102 | } 103 | 104 | // // Callback to receive ERC223 tokens 105 | #[external(v0)] 106 | fn tokenReceived( 107 | self: @ContractState, 108 | address: ContractAddress, 109 | amount: u256, 110 | calldata_len: usize, 111 | calldata: Span 112 | ) {} 113 | 114 | #[external(v0)] 115 | fn call_exploit(ref self: ContractState, attacker_address: ContractAddress) { 116 | // Transfer 1 SEC to attacker's contract 117 | IERC20Dispatcher { contract_address: self.get_isec_address() } 118 | .transfer(attacker_address, TOKEN_1); 119 | 120 | // Transfer 1 SET to attacker's contract 121 | IERC20Dispatcher { contract_address: self.get_set_address() } 122 | .transfer(attacker_address, TOKEN_1); 123 | 124 | // Call exploit 125 | IAttackerDispatcher { contract_address: attacker_address }.exploit(); 126 | } 127 | 128 | #[external(v0)] 129 | fn isComplete(self: @ContractState) -> bool { 130 | let dex_isec_balance = IERC20Dispatcher { contract_address: self.get_isec_address() } 131 | .balance_of(self.get_dex_address()); 132 | let dex_iset_balance = IERC20Dispatcher { contract_address: self.get_set_address() } 133 | .balance_of(self.get_dex_address()); 134 | 135 | assert( 136 | (dex_isec_balance == 0 && dex_iset_balance == 0), 'Challenge not completed yet.' 137 | ); 138 | 139 | true 140 | } 141 | } 142 | 143 | // Free functions 144 | fn serialize_token_calldata(deployer: @ContractAddress, token_amount: @u256) -> Array { 145 | let mut calldata = array![]; 146 | Serde::serialize(deployer, ref calldata); 147 | Serde::serialize(token_amount, ref calldata); 148 | 149 | calldata 150 | } 151 | 152 | fn serialize_dex_calldata( 153 | token_0: @ContractAddress, token_1: @ContractAddress 154 | ) -> Array { 155 | let mut calldata = array![]; 156 | Serde::serialize(token_0, ref calldata); 157 | Serde::serialize(token_1, ref calldata); 158 | 159 | calldata 160 | } 161 | } 162 | 163 | -------------------------------------------------------------------------------- /src/assets/challenge8_dex.cairo: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // @dev Some ideas for this challenge were taken from 4 | // https://github.com/martriay/scAMM/blob/main/contracts/Exchange.sol 5 | 6 | #[starknet::contract] 7 | mod InsecureDexLP { 8 | use core::traits::TryInto; 9 | use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; 10 | use starknet::{ContractAddress, get_caller_address, get_contract_address}; 11 | 12 | #[storage] 13 | struct Storage { 14 | token0: ContractAddress, 15 | token1: ContractAddress, 16 | // @dev Balance of token0 17 | reserve0: u256, 18 | // @dev Balance of token1 19 | reserve1: u256, 20 | // @dev Total liquidity LP 21 | total_supply: u256, 22 | // @dev Liquidity shares per user 23 | balances: LegacyMap 24 | } 25 | 26 | #[abi(per_item)] 27 | #[generate_trait] 28 | impl InsecureDexLP of IInsecureDexLPTrait { 29 | // @dev token0_addr, token1_addr Addresses of the tokens 30 | // participating in the liquidity pool 31 | #[constructor] 32 | fn constructor( 33 | ref self: ContractState, token0_addr: ContractAddress, token1_addr: ContractAddress 34 | ) { 35 | self.token0.write(token0_addr); 36 | self.token1.write(token1_addr); 37 | } 38 | 39 | // @dev Allows users to add liquidity for token0 and token1 40 | #[external(v0)] 41 | fn add_liquidity(ref self: ContractState, amount0: u256, amount1: u256) -> u256 { 42 | let sender = get_caller_address(); 43 | 44 | IERC20Dispatcher { contract_address: self.token0.read() } 45 | .transfer_from(sender, get_contract_address(), amount0); 46 | 47 | IERC20Dispatcher { contract_address: self.token1.read() } 48 | .transfer_from(sender, get_contract_address(), amount1); 49 | 50 | let total_supply: u256 = self.total_supply.read(); 51 | 52 | // @dev if there is no liquidity, initial liquidity is defined as 53 | // sqrt(amount0 * amount1), following the product-constant rule for AMMs. 54 | if total_supply == 0 { 55 | let m0 = amount0 * amount1; 56 | let liquidity = core::integer::u256_sqrt(m0).into(); 57 | 58 | self._update_reserves(); 59 | 60 | self.total_supply.write(liquidity.into()); 61 | self.balances.write(sender, self.balances.read(sender) + liquidity); 62 | 63 | liquidity 64 | } // @dev If liquidity exists, update shares with supplied amounts 65 | else { 66 | //liquidity = Math.min((amount0 * _totalSupply) / reserve0, (amount1 *_totalSupply) / reserve1); 67 | // a = amount0 * totalSupply / reserve0 68 | // b = amount1 * totalSupply / reserve1 69 | // liquidity = min(a, b) 70 | let reserve0: u256 = self.reserve0.read(); 71 | let reserve1: u256 = self.reserve1.read(); 72 | let a_lhs: u256 = amount0 * total_supply; 73 | let a: u256 = a_lhs / reserve0; // warp_div256(a_lhs, _reserve0); 74 | let b_lhs: u256 = amount1 * total_supply; 75 | let b: u256 = b_lhs / reserve1; // warp_div256(b_lhs, _reserve1); 76 | let liquidity: u256 = if a < b { 77 | a 78 | } else { 79 | b 80 | }; 81 | 82 | self._update_reserves(); 83 | 84 | self.total_supply.write(self.total_supply.read() + liquidity); 85 | self.balances.write(sender, self.balances.read(sender) + liquidity); 86 | 87 | liquidity 88 | } 89 | } 90 | 91 | // @dev Burn LP shares and get token0 and token1 amounts back 92 | #[external(v0)] 93 | fn remove_liquidity(ref self: ContractState, amount: u256) -> (u256, u256) { 94 | let sender = get_caller_address(); 95 | assert(self.balances.read(sender) >= amount, 'Insufficient funds.'); 96 | 97 | let total_supply: u256 = self.total_supply.read(); 98 | assert(total_supply != 0, 'Total supply is 0'); 99 | 100 | let reserve0: u256 = self.reserve0.read(); 101 | let a_lhs: u256 = amount * reserve0; 102 | let amount0 = a_lhs / total_supply; 103 | 104 | let reserve1: u256 = self.reserve1.read(); 105 | let b_lhs: u256 = amount * reserve1; 106 | let amount1 = b_lhs / total_supply; 107 | 108 | assert!(reserve0 > 0 && reserve1 > 0, "InsecureDexLP: INSUFFICIENT_LIQUIDITY_BURNED"); 109 | 110 | IERC20Dispatcher { contract_address: self.token0.read() }.transfer(sender, amount0); 111 | IERC20Dispatcher { contract_address: self.token1.read() }.transfer(sender, amount1); 112 | 113 | let new_supply: u256 = total_supply - amount; 114 | self.total_supply.write(new_supply); 115 | 116 | let curr_balance: felt252 = self.balances.read(sender).try_into().unwrap(); 117 | let new_balance: felt252 = curr_balance - amount.try_into().unwrap(); 118 | self.balances.write(sender, new_balance.into()); 119 | 120 | self._update_reserves(); 121 | 122 | (amount0, amount1) 123 | } 124 | 125 | // @dev Swap amount_in of tokenFrom to tokenTo 126 | #[external(v0)] 127 | fn swap( 128 | ref self: ContractState, 129 | token_from: ContractAddress, 130 | token_to: ContractAddress, 131 | amount_in: u256 132 | ) -> u256 { 133 | let sender = get_caller_address(); 134 | let token0_addr = self.token0.read(); 135 | let token1_addr = self.token1.read(); 136 | 137 | assert( 138 | token_from == token0_addr || token_from == token1_addr, 139 | 'token_from is not supported' 140 | ); 141 | assert( 142 | token_to == token0_addr || token_to == token1_addr, 'token_from is not supported' 143 | ); 144 | 145 | let reserve0 = self.reserve0.read(); 146 | let reserve1 = self.reserve1.read(); 147 | 148 | if token_from == token0_addr { 149 | let amount_out = self._calc_amounts_out(amount_in, reserve0, reserve1); 150 | 151 | IERC20Dispatcher { contract_address: token0_addr } 152 | .transfer_from(sender, get_contract_address(), amount_in); 153 | IERC20Dispatcher { contract_address: token1_addr }.transfer(sender, amount_out); 154 | 155 | self._update_reserves(); 156 | 157 | amount_out 158 | } else { 159 | let amount_out = self._calc_amounts_out(amount_in, reserve1, reserve0); 160 | 161 | IERC20Dispatcher { contract_address: token1_addr } 162 | .transfer_from(sender, get_contract_address(), amount_in); 163 | IERC20Dispatcher { contract_address: token0_addr }.transfer(sender, amount_out); 164 | self._update_reserves(); 165 | 166 | amount_out 167 | } 168 | } 169 | 170 | // @dev Given an amount_in of tokenIn, compute the corresponding output of 171 | // tokenOut 172 | #[external(v0)] 173 | fn calc_amounts_out( 174 | self: @ContractState, token_in: ContractAddress, amount_in: u256 175 | ) -> u256 { 176 | if token_in == self.token0.read() { 177 | return self 178 | ._calc_amounts_out(amount_in, self.reserve0.read(), self.reserve1.read()); 179 | } 180 | 181 | if token_in == self.token1.read() { 182 | return self 183 | ._calc_amounts_out(amount_in, self.reserve1.read(), self.reserve0.read()); 184 | } 185 | 186 | // "Token is not supported" 187 | 0 188 | } 189 | 190 | // @dev See balance of user 191 | fn balance_of(self: @ContractState, user: ContractAddress) -> u256 { 192 | self.balances.read(user) 193 | } 194 | 195 | #[external(v0)] 196 | fn token_received( 197 | ref self: ContractState, 198 | address: ContractAddress, 199 | amount: u256, 200 | calldata_len: usize, 201 | calldata: Span 202 | ) {} 203 | } 204 | 205 | #[generate_trait] 206 | impl InternalInsecureDexLP of InternalInsecureDexLPTrait { 207 | // @dev Updates the balances of the tokens 208 | fn _update_reserves(ref self: ContractState) { 209 | let token0_addr = self.token0.read(); 210 | let token1_addr = self.token1.read(); 211 | 212 | let res0 = IERC20Dispatcher { contract_address: token0_addr } 213 | .balance_of(get_contract_address()); 214 | self.reserve0.write(res0); 215 | 216 | let res1 = IERC20Dispatcher { contract_address: token1_addr } 217 | .balance_of(get_contract_address()); 218 | self.reserve1.write(res1); 219 | } 220 | 221 | // @dev taken from uniswap library; 222 | // https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol#L43 223 | fn _calc_amounts_out( 224 | self: @ContractState, amount_in: u256, reserve_in: u256, reserve_out: u256 225 | ) -> u256 { 226 | let new_amount_in: u256 = amount_in * 1000; 227 | let numerator: u256 = new_amount_in * reserve_out; 228 | let denominator: u256 = reserve_in * 1000 + new_amount_in; 229 | let amount_out = numerator / denominator; 230 | 231 | amount_out 232 | } 233 | } 234 | } -------------------------------------------------------------------------------- /src/assets/challenge8_erc20.cairo: -------------------------------------------------------------------------------- 1 | // // SPDX-License-Identifier: MIT 2 | 3 | #[starknet::contract] 4 | mod InSecureumToken { 5 | use openzeppelin::token::erc20::ERC20Component; 6 | use starknet::ContractAddress; 7 | 8 | component!(path: ERC20Component, storage: erc20, event: ERC20Event); 9 | 10 | #[abi(embed_v0)] 11 | impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; 12 | #[abi(embed_v0)] 13 | impl ERC20Impl = ERC20Component::ERC20Impl; 14 | #[abi(embed_v0)] 15 | impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; 16 | impl ERC20InternalImpl = ERC20Component::InternalImpl; 17 | 18 | #[storage] 19 | struct Storage { 20 | #[substorage(v0)] 21 | erc20: ERC20Component::Storage, 22 | } 23 | 24 | #[event] 25 | #[derive(Drop, starknet::Event)] 26 | enum Event { 27 | #[flat] 28 | ERC20Event: ERC20Component::Event, 29 | } 30 | 31 | #[constructor] 32 | fn constructor(ref self: ContractState, deployer: ContractAddress, supply: u256) { 33 | self.erc20.initializer("InSecureumToken", "ISEC"); 34 | self.erc20._mint(deployer, supply); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/assets/challenge8_erc223.cairo: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use starknet::ContractAddress; 4 | 5 | #[starknet::interface] 6 | trait IERC223 { 7 | fn token_received( 8 | self: @TContractState, 9 | address: ContractAddress, 10 | amount: u256, 11 | calldata_len: usize, 12 | calldata: Span 13 | ); 14 | } 15 | 16 | #[starknet::contract] 17 | mod SimpleERC223Token { 18 | use super::{IERC223Dispatcher, IERC223DispatcherTrait}; 19 | use openzeppelin::token::erc20::ERC20Component; 20 | use openzeppelin::token::erc20::interface::{IERC20, IERC20CamelOnly}; 21 | use starknet::{ContractAddress, get_caller_address}; 22 | 23 | component!(path: ERC20Component, storage: erc20, event: ERC20Event); 24 | 25 | #[abi(embed_v0)] 26 | impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; 27 | impl ERC20InternalImpl = ERC20Component::InternalImpl; 28 | 29 | #[storage] 30 | struct Storage { 31 | #[substorage(v0)] 32 | erc20: ERC20Component::Storage, 33 | } 34 | 35 | #[event] 36 | #[derive(Drop, starknet::Event)] 37 | enum Event { 38 | #[flat] 39 | ERC20Event: ERC20Component::Event, 40 | } 41 | 42 | #[constructor] 43 | fn constructor(ref self: ContractState, deployer: ContractAddress, supply: u256) { 44 | self.erc20.initializer("Simple ERC223 Token", "SET"); 45 | self.erc20._mint(deployer, supply); 46 | } 47 | 48 | #[abi(embed_v0)] 49 | impl ERC223Impl of IERC20 { 50 | fn total_supply(self: @ContractState) -> u256 { 51 | self.erc20.total_supply() 52 | } 53 | 54 | fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { 55 | self.erc20.balance_of(account) 56 | } 57 | 58 | fn allowance( 59 | self: @ContractState, owner: ContractAddress, spender: ContractAddress 60 | ) -> u256 { 61 | self.erc20.allowance(owner, spender) 62 | } 63 | 64 | fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { 65 | self.erc20.approve(spender, amount) 66 | } 67 | 68 | fn transfer_from( 69 | ref self: ContractState, 70 | sender: ContractAddress, 71 | recipient: ContractAddress, 72 | amount: u256 73 | ) -> bool { 74 | self.erc20.transfer_from(sender, recipient, amount) 75 | } 76 | 77 | fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { 78 | self.erc20.transfer(recipient, amount); 79 | _after_token_transfer(@self, recipient, amount) 80 | } 81 | } 82 | 83 | #[abi(embed_v0)] 84 | impl ERC223CamelOnlyImpl of IERC20CamelOnly { 85 | fn totalSupply(self: @ContractState) -> u256 { 86 | self.erc20.total_supply() 87 | } 88 | 89 | fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { 90 | self.erc20.balance_of(account) 91 | } 92 | 93 | fn transferFrom( 94 | ref self: ContractState, 95 | sender: ContractAddress, 96 | recipient: ContractAddress, 97 | amount: u256 98 | ) -> bool { 99 | self.erc20.transfer_from(sender, recipient, amount) 100 | } 101 | } 102 | 103 | fn _after_token_transfer(self: @ContractState, to: ContractAddress, amt: u256) -> bool { 104 | let sender = get_caller_address(); 105 | let calldata = array![].span(); 106 | IERC223Dispatcher { contract_address: to }.token_received(sender, amt, 0, calldata); 107 | 108 | true 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/assets/challenge9.cairo: -------------------------------------------------------------------------------- 1 | use starknet::ContractAddress; 2 | 3 | #[starknet::interface] 4 | trait IERC20 { 5 | fn balanceOf(self: @TState, account: ContractAddress) -> u256; 6 | fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; 7 | fn transfer_from( 8 | ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 9 | ) -> bool; 10 | } 11 | 12 | #[starknet::contract] 13 | mod Fallout { 14 | use super::{IERC20Dispatcher, IERC20DispatcherTrait}; 15 | use starknet::{ContractAddress, get_caller_address, get_contract_address, get_tx_info}; 16 | 17 | // ######## Constants 18 | 19 | const L2_ETHER_ADDRESS: felt252 = 20 | 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; 21 | 22 | #[storage] 23 | struct Storage { 24 | owner: ContractAddress, 25 | allocations: LegacyMap:: 26 | } 27 | 28 | #[abi(per_item)] 29 | #[generate_trait] 30 | impl Fallout of FalloutTrait { 31 | // ######## Constructor 32 | #[external(v0)] 33 | fn constructor(ref self: ContractState, amount: u256) { 34 | let sender: ContractAddress = get_caller_address(); 35 | self.owner.write(sender); 36 | self.allocations.write(sender, amount); 37 | } 38 | 39 | // ######## Getters 40 | #[external(v0)] 41 | fn get_owner(self: @ContractState) -> ContractAddress { 42 | self.owner.read() 43 | } 44 | 45 | #[external(v0)] 46 | fn get_allocations(self: @ContractState, allocator: ContractAddress) -> u256 { 47 | self.allocations.read(allocator) 48 | } 49 | 50 | // ######## External functions 51 | #[external(v0)] 52 | fn allocate(ref self: ContractState, amount: u256) { 53 | let eth_contract = IERC20Dispatcher { 54 | contract_address: L2_ETHER_ADDRESS.try_into().unwrap() 55 | }; 56 | 57 | let success: bool = eth_contract 58 | .transfer_from(get_caller_address(), get_contract_address(), amount); 59 | assert!(success, "transfer failed"); 60 | 61 | let current_allocation: u256 = self.get_allocations(get_caller_address()); 62 | let new_allocation: u256 = current_allocation + amount; 63 | self.allocations.write(get_caller_address(), new_allocation); 64 | } 65 | 66 | #[external(v0)] 67 | fn send_allocation(ref self: ContractState, allocator: ContractAddress) { 68 | let current_allocation: u256 = self.get_allocations(allocator); 69 | assert!(current_allocation != 0, "Allocations required"); 70 | 71 | let eth_contract = IERC20Dispatcher { 72 | contract_address: L2_ETHER_ADDRESS.try_into().unwrap() 73 | }; 74 | 75 | let success: bool = eth_contract.transfer(allocator, current_allocation); 76 | assert!(success, "transfer failed"); 77 | 78 | self.allocations.write(allocator, 0); 79 | } 80 | 81 | #[external(v0)] 82 | fn isComplete(ref self: ContractState) -> bool { 83 | let tx_info = get_tx_info().unbox(); 84 | let tx_origin = tx_info.account_contract_address; 85 | assert!(tx_origin == self.owner.read(), "Caller is not the owner"); 86 | 87 | let eth_contract = IERC20Dispatcher { 88 | contract_address: L2_ETHER_ADDRESS.try_into().unwrap() 89 | }; 90 | let total_balance = eth_contract.balanceOf(get_contract_address()); 91 | 92 | let success: bool = eth_contract.transfer(get_caller_address(), total_balance); 93 | assert!(success, "transfer failed"); 94 | 95 | true 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/assets/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/design.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/main.cairo: -------------------------------------------------------------------------------- 1 | // ######## Main 2 | // When change this contract interface remember update ABI file at react project. 3 | 4 | #[starknet::interface] 5 | trait ITestContract { 6 | fn isComplete(self: @TContractState) -> bool; 7 | } 8 | 9 | #[starknet::interface] 10 | trait INFT { 11 | fn mint(ref self: TContractState, to: felt252, tokenId: u256); 12 | fn setTokenURI( 13 | ref self: TContractState, base_token_uri: Array, token_uri_suffix: felt252 14 | ); 15 | } 16 | 17 | #[starknet::contract] 18 | mod SecurityChallenge { 19 | use super::{ITestContractDispatcher, ITestContractDispatcherTrait}; 20 | use super::{INFTDispatcher, INFTDispatcherTrait}; 21 | 22 | use starknet::class_hash::Felt252TryIntoClassHash; 23 | use starknet::syscalls::{deploy_syscall, replace_class_syscall}; 24 | use starknet::{ 25 | ContractAddress, contract_address_try_from_felt252, get_caller_address, get_tx_info 26 | }; 27 | 28 | // Struct to storage players challenge status. 29 | #[derive(Drop, starknet::Store)] 30 | struct player_challenges_struct { 31 | address: felt252, 32 | resolved: felt252, 33 | minted: felt252, 34 | } 35 | 36 | // Struct for storage players info. 37 | #[derive(Drop, starknet::Store, Serde)] 38 | struct player_struct { 39 | id: felt252, 40 | nickname: felt252, 41 | points: felt252, 42 | address: felt252, 43 | } 44 | 45 | // Struct to storage challenge info. 46 | #[derive(Drop, starknet::Store)] 47 | struct challenge_struct { 48 | class_hash: felt252, 49 | points: felt252, 50 | } 51 | 52 | #[storage] 53 | struct Storage { 54 | Proxy_admin: felt252, 55 | player_challenges: LegacyMap::<(felt252, felt252), player_challenges_struct>, 56 | player: LegacyMap::, 57 | registered_players: LegacyMap::, 58 | player_count: felt252, 59 | challenges: LegacyMap::, 60 | salt: felt252, 61 | nft_address: felt252, 62 | } 63 | 64 | // Events 65 | #[event] 66 | #[derive(Drop, starknet::Event)] 67 | enum Event { 68 | contract_deployed: contract_deployed, 69 | e1: e1, 70 | e2: e2, 71 | } 72 | 73 | #[derive(Drop, starknet::Event)] 74 | struct contract_deployed { 75 | contract_address: felt252, 76 | } 77 | 78 | #[derive(Drop, starknet::Event)] 79 | struct e1 { 80 | res: felt252, 81 | } 82 | 83 | #[derive(Drop, starknet::Event)] 84 | struct e2 { 85 | res: felt252, 86 | } 87 | 88 | #[abi(per_item)] 89 | #[generate_trait] 90 | impl SecurityChallengeImpl of ISecurityChallenge { 91 | #[constructor] 92 | fn constructor(ref self: ContractState) { 93 | //Set proxy admin 94 | self.Proxy_admin.write(get_tx_info().unbox().account_contract_address.into()); 95 | } 96 | 97 | // ######## External functions 98 | 99 | // Function to deploy challenges to players 100 | #[external(v0)] 101 | fn deploy_challenge(ref self: ContractState, _challenge_number: felt252) -> felt252 { 102 | let sender = get_caller_address(); 103 | let current_salt = self.salt.read(); 104 | let current_challenge = self.challenges.read(_challenge_number); 105 | let class_hash = current_challenge.class_hash; 106 | let ctor_calldata: Array = array![]; 107 | 108 | let (new_contract_address, _) = deploy_syscall( 109 | class_hash.try_into().unwrap(), // class hash 110 | current_salt, // salt 111 | ctor_calldata.span(), 112 | false // deploy from zero address 113 | ) 114 | .unwrap(); 115 | self.salt.write(current_salt + 1); 116 | self.emit(contract_deployed { contract_address: new_contract_address.into() }); 117 | 118 | //Assign challenge to player 119 | let new_challenge = player_challenges_struct { 120 | address: new_contract_address.into(), resolved: false.into(), minted: false.into() 121 | }; 122 | self.player_challenges.write((sender.into(), _challenge_number), new_challenge); 123 | 124 | new_contract_address.into() 125 | } 126 | 127 | // Function to test if challenge was completed by player 128 | #[external(v0)] 129 | fn test_challenge(ref self: ContractState, _challenge_number: felt252) -> felt252 { 130 | let sender = get_caller_address(); 131 | let current_player_challenge = self 132 | .player_challenges 133 | .read((sender.into(), _challenge_number)); 134 | 135 | //Check if is already resolved 136 | assert(current_player_challenge.resolved == false.into(), 'Challenge already resolved'); 137 | 138 | //Check if resolved 139 | let challenge_contract = current_player_challenge.address; 140 | let _result: bool = ITestContractDispatcher { 141 | contract_address: challenge_contract.try_into().unwrap() 142 | } 143 | .isComplete(); 144 | assert(_result == true, 'Challenge not resolved'); 145 | 146 | //At this point we know challenge was completed sucessfully 147 | 148 | //Update player resolved challenges 149 | let new_challenge = player_challenges_struct { 150 | address: current_player_challenge.address, 151 | resolved: true.into(), 152 | minted: false.into() 153 | }; 154 | self.player_challenges.write((sender.into(), _challenge_number), new_challenge); 155 | 156 | //Get player info 157 | let current_player = self.player.read(sender.into()); 158 | let current_challenge = self.challenges.read(_challenge_number); 159 | let mut player_id: felt252 = current_player.id; 160 | 161 | // First time, get a new player id to add player to ranking 162 | if current_player.points == 0 { 163 | player_id = self.player_count.read(); 164 | self.player_count.write(self.player_count.read() + 1); 165 | } 166 | 167 | // update player points 168 | let player_points = current_player.points + current_challenge.points; 169 | let player_info = player_struct { 170 | id: player_id, 171 | nickname: current_player.nickname, 172 | points: player_points, 173 | address: sender.into() 174 | }; 175 | self.player.write(sender.into(), player_info); 176 | 177 | //Add to ranking (sort in frontend) 178 | let player_info = player_struct { 179 | id: player_id, 180 | nickname: current_player.nickname, 181 | points: player_points, 182 | address: sender.into() 183 | }; 184 | self.registered_players.write(player_id, player_info); 185 | 186 | _result.into() 187 | } 188 | 189 | // Function to mint an NFT after resolve a challenge 190 | #[external(v0)] 191 | fn mint(ref self: ContractState, _challenge_number: felt252) { 192 | let sender = get_caller_address(); 193 | let current_player_challenge = self 194 | .player_challenges 195 | .read((sender.into(), _challenge_number)); 196 | 197 | //Check if is already resolved 198 | assert(current_player_challenge.resolved == true.into(), 'Challenge not resolved yet'); 199 | 200 | //Check if is already minted 201 | assert(current_player_challenge.minted == false.into(), 'Challenge already minted'); 202 | 203 | // Mint NFT 204 | // warn: libfunc `bytes31_const` is not allowed in the libfuncs list `Default libfunc list` 205 | let _tokenId: u256 = u256 { low: _challenge_number.try_into().unwrap(), high: 0_u128 }; 206 | let nft: felt252 = self.nft_address.read(); 207 | INFTDispatcher { contract_address: nft.try_into().unwrap() } 208 | .mint(sender.into(), _tokenId); 209 | 210 | //Update player minted challenges 211 | let new_challenge = player_challenges_struct { 212 | address: current_player_challenge.address, 213 | resolved: true.into(), 214 | minted: true.into() 215 | }; 216 | self.player_challenges.write((sender.into(), _challenge_number), new_challenge); 217 | } 218 | 219 | // Get player total points 220 | #[external(v0)] 221 | fn get_points(self: @ContractState, _player: felt252) -> felt252 { 222 | let current_player = self.player.read(_player.into()); 223 | current_player.points.into() 224 | } 225 | 226 | // Get if challenge is already completed by player 227 | #[external(v0)] 228 | fn get_challenge_status( 229 | self: @ContractState, _player: felt252, _challenge_number: felt252 230 | ) -> felt252 { 231 | let current_challenge = self 232 | .player_challenges 233 | .read((_player.into(), _challenge_number)); 234 | current_challenge.resolved.into() 235 | } 236 | 237 | // Get if challenge is already completed by player 238 | #[external(v0)] 239 | fn get_mint_status( 240 | self: @ContractState, _player: felt252, _challenge_number: felt252 241 | ) -> felt252 { 242 | let current_challenge = self 243 | .player_challenges 244 | .read((_player.into(), _challenge_number)); 245 | current_challenge.minted.into() 246 | } 247 | 248 | // Get player nickname 249 | #[external(v0)] 250 | fn get_nickname(self: @ContractState, _player: felt252) -> felt252 { 251 | let current_player = self.player.read(_player.into()); 252 | current_player.nickname.into() 253 | } 254 | 255 | //Set player nickname 256 | #[external(v0)] 257 | fn set_nickname(ref self: ContractState, _nickname: felt252) { 258 | let sender = get_caller_address(); 259 | let current_player = self.player.read(sender.into()); 260 | let player_points = current_player.points; 261 | //Check if already resolved 262 | assert(player_points != 0, 'End a challenge before nickname'); 263 | 264 | let player_info = player_struct { 265 | id: current_player.id, 266 | nickname: _nickname, 267 | points: player_points, 268 | address: sender.into() 269 | }; 270 | self.player.write(sender.into(), player_info); 271 | 272 | let player_info = player_struct { 273 | id: current_player.id, 274 | nickname: _nickname, 275 | points: player_points, 276 | address: sender.into() 277 | }; 278 | self.registered_players.write(current_player.id, player_info); 279 | } 280 | 281 | // Get players ranking (not ordered) 282 | #[external(v0)] 283 | fn get_ranking(self: @ContractState) -> Array { 284 | let total = self.player_count.read(); 285 | let mut player_array: Array = ArrayTrait::new(); 286 | let mut i = 0; 287 | while i != total { 288 | let current_player = self.registered_players.read(i); 289 | player_array.append(current_player); 290 | i = i + 1; 291 | }; 292 | 293 | player_array 294 | } 295 | 296 | // Proxy function 297 | #[external(v0)] 298 | fn upgrade( 299 | ref self: ContractState, new_class_hash: core::starknet::class_hash::ClassHash 300 | ) -> felt252 { 301 | //Only owner can access this function 302 | assert( 303 | contract_address_try_from_felt252(self.Proxy_admin.read()) 304 | .unwrap() == get_caller_address(), 305 | 'Only owner can access function.' 306 | ); 307 | 308 | replace_class_syscall(new_class_hash); 309 | 1 310 | } 311 | 312 | // 313 | // Getters 314 | // 315 | #[external(v0)] 316 | fn getPlayerCount(self: @ContractState) -> felt252 { 317 | self.player_count.read() 318 | } 319 | 320 | #[external(v0)] 321 | fn updateChallenge( 322 | ref self: ContractState, 323 | challenge_id: felt252, 324 | new_class_hash: felt252, 325 | new_points: felt252 326 | ) { 327 | //Only owner can access this function 328 | assert( 329 | contract_address_try_from_felt252(self.Proxy_admin.read()) 330 | .unwrap() == get_caller_address(), 331 | 'Only owner can access function.' 332 | ); 333 | 334 | let new_challenge = challenge_struct { class_hash: new_class_hash, points: new_points }; 335 | self.challenges.write(challenge_id, new_challenge); 336 | } 337 | 338 | #[external(v0)] 339 | fn setNFTAddress(ref self: ContractState, new_nft_address: felt252) { 340 | //Only owner can access this function 341 | assert( 342 | contract_address_try_from_felt252(self.Proxy_admin.read()) 343 | .unwrap() == get_caller_address(), 344 | 'Only owner can access function' 345 | ); 346 | 347 | self.nft_address.write(new_nft_address); 348 | } 349 | 350 | // Function to read challenge class hashes 351 | #[external(v0)] 352 | fn get_challenge_class_hash(self: @ContractState, _challenge_number: felt252) -> felt252 { 353 | let current_challenge = self.challenges.read(_challenge_number); 354 | current_challenge.class_hash.into() 355 | } 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/assets/main_abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "members": [ 4 | { 5 | "name": "id", 6 | "offset": 0, 7 | "type": "felt" 8 | }, 9 | { 10 | "name": "nickname", 11 | "offset": 1, 12 | "type": "felt" 13 | }, 14 | { 15 | "name": "points", 16 | "offset": 2, 17 | "type": "felt" 18 | }, 19 | { 20 | "name": "address", 21 | "offset": 3, 22 | "type": "felt" 23 | } 24 | ], 25 | "name": "player_struct", 26 | "size": 4, 27 | "type": "struct" 28 | }, 29 | { 30 | "data": [ 31 | { 32 | "name": "implementation", 33 | "type": "felt" 34 | } 35 | ], 36 | "keys": [], 37 | "name": "Upgraded", 38 | "type": "event" 39 | }, 40 | { 41 | "data": [ 42 | { 43 | "name": "previousAdmin", 44 | "type": "felt" 45 | }, 46 | { 47 | "name": "newAdmin", 48 | "type": "felt" 49 | } 50 | ], 51 | "keys": [], 52 | "name": "AdminChanged", 53 | "type": "event" 54 | }, 55 | { 56 | "data": [ 57 | { 58 | "name": "contract_address", 59 | "type": "felt" 60 | } 61 | ], 62 | "keys": [], 63 | "name": "contract_deployed", 64 | "type": "event" 65 | }, 66 | { 67 | "data": [ 68 | { 69 | "name": "res", 70 | "type": "felt" 71 | } 72 | ], 73 | "keys": [], 74 | "name": "e1", 75 | "type": "event" 76 | }, 77 | { 78 | "data": [ 79 | { 80 | "name": "res", 81 | "type": "felt" 82 | } 83 | ], 84 | "keys": [], 85 | "name": "e2", 86 | "type": "event" 87 | }, 88 | { 89 | "inputs": [ 90 | { 91 | "name": "_challenge_number", 92 | "type": "felt" 93 | } 94 | ], 95 | "name": "deploy_challenge", 96 | "outputs": [ 97 | { 98 | "name": "new_contract_address", 99 | "type": "felt" 100 | } 101 | ], 102 | "type": "function" 103 | }, 104 | { 105 | "inputs": [ 106 | { 107 | "name": "_challenge_number", 108 | "type": "felt" 109 | } 110 | ], 111 | "name": "test_challenge", 112 | "outputs": [ 113 | { 114 | "name": "_result", 115 | "type": "felt" 116 | } 117 | ], 118 | "type": "function" 119 | }, 120 | { 121 | "inputs": [ 122 | { 123 | "name": "_challenge_number", 124 | "type": "felt" 125 | } 126 | ], 127 | "name": "mint", 128 | "outputs": [], 129 | "type": "function" 130 | }, 131 | { 132 | "inputs": [ 133 | { 134 | "name": "_player", 135 | "type": "felt" 136 | } 137 | ], 138 | "name": "get_points", 139 | "outputs": [ 140 | { 141 | "name": "_points", 142 | "type": "felt" 143 | } 144 | ], 145 | "stateMutability": "view", 146 | "type": "function" 147 | }, 148 | { 149 | "inputs": [ 150 | { 151 | "name": "_player", 152 | "type": "felt" 153 | }, 154 | { 155 | "name": "_challenge_number", 156 | "type": "felt" 157 | } 158 | ], 159 | "name": "get_challenge_status", 160 | "outputs": [ 161 | { 162 | "name": "_resolved", 163 | "type": "felt" 164 | } 165 | ], 166 | "stateMutability": "view", 167 | "type": "function" 168 | }, 169 | { 170 | "inputs": [ 171 | { 172 | "name": "_player", 173 | "type": "felt" 174 | }, 175 | { 176 | "name": "_challenge_number", 177 | "type": "felt" 178 | } 179 | ], 180 | "name": "get_mint_status", 181 | "outputs": [ 182 | { 183 | "name": "_minted", 184 | "type": "felt" 185 | } 186 | ], 187 | "stateMutability": "view", 188 | "type": "function" 189 | }, 190 | { 191 | "inputs": [ 192 | { 193 | "name": "_player", 194 | "type": "felt" 195 | } 196 | ], 197 | "name": "get_nickname", 198 | "outputs": [ 199 | { 200 | "name": "_nickname", 201 | "type": "felt" 202 | } 203 | ], 204 | "stateMutability": "view", 205 | "type": "function" 206 | }, 207 | { 208 | "inputs": [ 209 | { 210 | "name": "_nickname", 211 | "type": "felt" 212 | } 213 | ], 214 | "name": "set_nickname", 215 | "outputs": [], 216 | "type": "function" 217 | }, 218 | { 219 | "inputs": [], 220 | "name": "get_ranking", 221 | "outputs": [ 222 | { 223 | "name": "_player_list_len", 224 | "type": "felt" 225 | }, 226 | { 227 | "name": "_player_list", 228 | "type": "player_struct*" 229 | } 230 | ], 231 | "stateMutability": "view", 232 | "type": "function" 233 | }, 234 | { 235 | "inputs": [ 236 | { 237 | "name": "proxy_admin", 238 | "type": "felt" 239 | } 240 | ], 241 | "name": "initializer", 242 | "outputs": [], 243 | "type": "function" 244 | }, 245 | { 246 | "inputs": [ 247 | { 248 | "name": "new_implementation", 249 | "type": "felt" 250 | } 251 | ], 252 | "name": "upgrade", 253 | "outputs": [], 254 | "type": "function" 255 | }, 256 | { 257 | "inputs": [], 258 | "name": "getImplementationHash", 259 | "outputs": [ 260 | { 261 | "name": "implementation", 262 | "type": "felt" 263 | } 264 | ], 265 | "stateMutability": "view", 266 | "type": "function" 267 | }, 268 | { 269 | "inputs": [], 270 | "name": "getAdmin", 271 | "outputs": [ 272 | { 273 | "name": "admin", 274 | "type": "felt" 275 | } 276 | ], 277 | "stateMutability": "view", 278 | "type": "function" 279 | }, 280 | { 281 | "inputs": [], 282 | "name": "getPlayerCount", 283 | "outputs": [ 284 | { 285 | "name": "total", 286 | "type": "felt" 287 | } 288 | ], 289 | "stateMutability": "view", 290 | "type": "function" 291 | }, 292 | { 293 | "inputs": [ 294 | { 295 | "name": "new_admin", 296 | "type": "felt" 297 | } 298 | ], 299 | "name": "setAdmin", 300 | "outputs": [], 301 | "type": "function" 302 | }, 303 | { 304 | "inputs": [ 305 | { 306 | "name": "challenge_id", 307 | "type": "felt" 308 | }, 309 | { 310 | "name": "new_class_hash", 311 | "type": "felt" 312 | }, 313 | { 314 | "name": "new_points", 315 | "type": "felt" 316 | } 317 | ], 318 | "name": "updateChallenge", 319 | "outputs": [], 320 | "type": "function" 321 | } 322 | ] 323 | -------------------------------------------------------------------------------- /src/assets/nft.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::interface] 2 | trait IERC20 { 3 | fn transferFrom( 4 | ref self: TContractState, sender: felt252, recipient: felt252, amount: u256 5 | ) -> bool; 6 | fn balanceOf(self: @TContractState, account: felt252) -> u256; 7 | fn transfer(ref self: TContractState, recipient: felt252, amount: u256) -> bool; 8 | } 9 | 10 | #[starknet::contract] 11 | mod StarknetChallengeNft { 12 | use starknet::syscalls::replace_class_syscall; 13 | use starknet::{ 14 | ContractAddress, get_contract_address, get_caller_address, contract_address_to_felt252, 15 | contract_address_try_from_felt252, get_tx_info 16 | }; 17 | 18 | #[storage] 19 | struct Storage { 20 | Proxy_admin: felt252, 21 | token_uri_1: felt252, 22 | token_uri_2: felt252, 23 | token_uri_3: felt252, 24 | token_uri_4: felt252, 25 | ERC1155_balances: LegacyMap::<(u256, felt252), u256>, //<(tokenId,account_address),balance> 26 | owner: felt252, 27 | } 28 | 29 | #[event] 30 | #[derive(Drop, starknet::Event)] 31 | enum Event { 32 | Transfer: Transfer, 33 | Approval: Approval, 34 | ApprovalForAll: ApprovalForAll, 35 | MetadataUpdate: MetadataUpdate, 36 | BatchMetadataUpdate: BatchMetadataUpdate, 37 | } 38 | 39 | #[derive(Drop, starknet::Event)] 40 | struct Transfer { 41 | from: ContractAddress, 42 | to: ContractAddress, 43 | token_id: u256 44 | } 45 | 46 | #[derive(Drop, starknet::Event)] 47 | struct Approval { 48 | owner: ContractAddress, 49 | approved: ContractAddress, 50 | token_id: u256 51 | } 52 | 53 | #[derive(Drop, starknet::Event)] 54 | struct ApprovalForAll { 55 | owner: ContractAddress, 56 | operator: ContractAddress, 57 | approved: bool 58 | } 59 | 60 | #[derive(Drop, starknet::Event)] 61 | struct MetadataUpdate { 62 | token_id: u256, 63 | } 64 | 65 | #[derive(Drop, starknet::Event)] 66 | struct BatchMetadataUpdate { 67 | from_token_id: u256, 68 | to_token_id: u256, 69 | } 70 | 71 | #[abi(per_item)] 72 | #[generate_trait] 73 | impl IStarknetChallengeNftImpl of IStarknetChallengeNft { 74 | #[constructor] 75 | fn constructor(ref self: ContractState,) { 76 | self 77 | .token_uri_1 78 | .write( 79 | 184555836509371486645351865271880215103735885104792769856590766422418009699 80 | ); // str_to_felt('https://raw.githubusercontent.c') 81 | self 82 | .token_uri_2 83 | .write( 84 | 196873592232662656702780857357828712082600550956565573228678353357572222275 85 | ); // str_to_felt('om/devnet0x/Starknet-Security-C') 86 | self 87 | .token_uri_3 88 | .write( 89 | 184424487222284609723570330230738705782107139797158045865232337081591886693 90 | ); // str_to_felt('hallenges-Factory/main/src/asse') 91 | self.token_uri_4.write(32777744851301423); // str_to_felt('ts/nft/ '); 92 | 93 | // Main core contract is the owner which can mint 94 | let main_address: ContractAddress = starknet::contract_address_const::< 95 | 0x0667b3f486c25a9afc38626706fb83eabf0f8a6c8a9b7393111f63e51a6dd5dd 96 | >(); 97 | self.owner.write(contract_address_to_felt252(main_address)); 98 | // Deployer is the admin 99 | self.Proxy_admin.write(get_tx_info().unbox().account_contract_address.into()); 100 | } 101 | 102 | #[external(v0)] 103 | fn supportsInterface(self: @ContractState, interface_id: felt252) -> bool { 104 | //Adds support for MetadataUpdated as indicated in eip-4906 105 | true 106 | } 107 | 108 | #[external(v0)] 109 | fn name(self: @ContractState) -> felt252 { 110 | let name = 'Starknet Security Challenges'; 111 | name 112 | } 113 | 114 | #[external(v0)] 115 | fn symbol(self: @ContractState) -> felt252 { 116 | let symbol = 'SSC'; 117 | symbol 118 | } 119 | 120 | #[external(v0)] 121 | fn transferOwnership(ref self: ContractState, newOwner: felt252) { 122 | //Only Admin can access this function 123 | assert( 124 | contract_address_try_from_felt252(self.Proxy_admin.read()) 125 | .unwrap() == get_caller_address(), 126 | 'Only admin can access function.' 127 | ); 128 | self.owner.write(newOwner); 129 | } 130 | 131 | #[external(v0)] 132 | fn batchMetadataUpdate(ref self: ContractState, from_token_id: u256, to_token_id: u256) { 133 | //Only Admin can access this function 134 | assert( 135 | contract_address_try_from_felt252(self.Proxy_admin.read()) 136 | .unwrap() == get_caller_address(), 137 | 'Only admin can access function.' 138 | ); 139 | self 140 | .emit( 141 | BatchMetadataUpdate { from_token_id: from_token_id, to_token_id: to_token_id } 142 | ); 143 | } 144 | 145 | #[external(v0)] 146 | fn metadataUpdate(ref self: ContractState, token_id: u256) { 147 | //Only Admin can access this function 148 | assert( 149 | contract_address_try_from_felt252(self.Proxy_admin.read()) 150 | .unwrap() == get_caller_address(), 151 | 'Only admin can access function.' 152 | ); 153 | self.emit(MetadataUpdate { token_id: token_id }); 154 | } 155 | 156 | #[external(v0)] 157 | fn setTokenUri(ref self: ContractState, _token_uri: Array) { 158 | //Only Admin can access this function 159 | assert( 160 | contract_address_try_from_felt252(self.Proxy_admin.read()) 161 | .unwrap() == get_caller_address(), 162 | 'Only admin can access function.' 163 | ); 164 | 165 | let mut token_uri = _token_uri; 166 | self.token_uri_1.write(token_uri.pop_front().unwrap()); 167 | self.token_uri_2.write(token_uri.pop_front().unwrap()); 168 | self.token_uri_3.write(token_uri.pop_front().unwrap()); 169 | self.token_uri_4.write(token_uri.pop_front().unwrap()); 170 | } 171 | 172 | #[external(v0)] 173 | fn tokenURI(self: @ContractState, token_id: u256) -> Array { 174 | let q: u256 = token_id / 10; 175 | let r: u256 = token_id % 10; 176 | let mut token_uri: Array = array![]; 177 | token_uri.append(self.token_uri_1.read()); 178 | token_uri.append(self.token_uri_2.read()); 179 | token_uri.append(self.token_uri_3.read()); 180 | token_uri.append(self.token_uri_4.read()); 181 | 182 | if q > 0 { 183 | token_uri.append(48 + q.try_into().unwrap()); 184 | }; 185 | 186 | token_uri.append(48 + r.try_into().unwrap()); 187 | token_uri.append(199354445678); // str_to_felt('.json') 188 | 189 | token_uri 190 | } 191 | 192 | #[external(v0)] 193 | fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { 194 | // Do nothing 195 | assert(0 == 1, 'Function not implemented.'); 196 | 0_u8.into() 197 | } 198 | 199 | #[external(v0)] 200 | fn ownerOf(self: @ContractState, token_id: u256) -> ContractAddress { 201 | // Do nothing 202 | assert(0 == 1, 'Function not implemented.'); 203 | 0.try_into().unwrap() 204 | } 205 | 206 | #[external(v0)] 207 | fn getApproved(self: @ContractState, token_id: u256) -> ContractAddress { 208 | // Do nothing 209 | assert(0 == 1, 'Function not implemented.'); 210 | 0.try_into().unwrap() 211 | } 212 | 213 | #[external(v0)] 214 | fn isApprovedForAll( 215 | self: @ContractState, owner: ContractAddress, operator: ContractAddress 216 | ) -> bool { 217 | // Do nothing 218 | assert(0 == 1, 'Function not implemented.'); 219 | false 220 | } 221 | 222 | #[external(v0)] 223 | fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { 224 | // Do nothing 225 | assert(0 == 1, 'Function not implemented.'); 226 | } 227 | 228 | #[external(v0)] 229 | fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { 230 | // Do nothing 231 | assert(0 == 1, 'Function not implemented.'); 232 | } 233 | 234 | #[external(v0)] 235 | fn transferFrom( 236 | ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 237 | ) { 238 | // Do nothing 239 | assert(0 == 1, 'Function not implemented.'); 240 | } 241 | 242 | #[external(v0)] 243 | fn safeTransferFrom( 244 | ref self: ContractState, 245 | from: ContractAddress, 246 | to: ContractAddress, 247 | token_id: u256, 248 | data: Span 249 | ) { 250 | // Do nothing 251 | assert(0 == 1, 'Function not implemented.'); 252 | } 253 | 254 | #[external(v0)] 255 | fn mint(ref self: ContractState, to: felt252, tokenId: u256,) { 256 | //Only owner (main contract) can mint 257 | assert( 258 | self.owner.read() == contract_address_to_felt252(get_caller_address()), 259 | 'Only main can mint.' 260 | ); 261 | 262 | self.ERC1155_balances.write((tokenId, to), 1.into()); 263 | 264 | self 265 | .emit( 266 | Transfer { 267 | from: 0.try_into().unwrap(), 268 | to: contract_address_try_from_felt252(to).unwrap(), 269 | token_id: tokenId 270 | } 271 | ); 272 | } 273 | 274 | // Proxy function 275 | #[external(v0)] 276 | fn upgrade( 277 | ref self: ContractState, new_class_hash: core::starknet::class_hash::ClassHash 278 | ) -> felt252 { 279 | //Only admin can access this function 280 | assert( 281 | contract_address_try_from_felt252(self.Proxy_admin.read()) 282 | .unwrap() == get_caller_address(), 283 | 'Only admin can access function.' 284 | ); 285 | 286 | replace_class_syscall(new_class_hash); 287 | 1 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/assets/nft/01.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/01.jpeg -------------------------------------------------------------------------------- /src/assets/nft/02.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/02.jpeg -------------------------------------------------------------------------------- /src/assets/nft/03.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/03.jpeg -------------------------------------------------------------------------------- /src/assets/nft/04.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/04.jpeg -------------------------------------------------------------------------------- /src/assets/nft/05.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/05.jpeg -------------------------------------------------------------------------------- /src/assets/nft/06.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/06.jpeg -------------------------------------------------------------------------------- /src/assets/nft/07.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/07.jpeg -------------------------------------------------------------------------------- /src/assets/nft/08.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/08.jpeg -------------------------------------------------------------------------------- /src/assets/nft/09.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/09.jpeg -------------------------------------------------------------------------------- /src/assets/nft/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Deploy", 3 | "description": "Deploy", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/01.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/nft/10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/10.jpeg -------------------------------------------------------------------------------- /src/assets/nft/10.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CoinFlip", 3 | "description": "CoinFlip", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/10.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } -------------------------------------------------------------------------------- /src/assets/nft/11.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/11.jpeg -------------------------------------------------------------------------------- /src/assets/nft/11.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Telephone", 3 | "description": "Telephone", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/11.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } -------------------------------------------------------------------------------- /src/assets/nft/12.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/12.jpeg -------------------------------------------------------------------------------- /src/assets/nft/12.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vault", 3 | "description": "Vault", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/12.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/nft/13.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/13.jpeg -------------------------------------------------------------------------------- /src/assets/nft/13.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Naught Coin", 3 | "description": "Naught Coin", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/13.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/nft/14.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/nft/14.jpeg -------------------------------------------------------------------------------- /src/assets/nft/14.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Good Samaritan", 3 | "description": "Good Samaritan", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/14.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/nft/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Callme", 3 | "description": "Callme", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/02.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/nft/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Nickname", 3 | "description": "Nickname", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/03.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/nft/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Guess", 3 | "description": "Guess", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/04.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/nft/5.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Secret", 3 | "description": "Secret", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/05.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } -------------------------------------------------------------------------------- /src/assets/nft/6.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Random", 3 | "description": "Random", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/06.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } -------------------------------------------------------------------------------- /src/assets/nft/7.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VToken", 3 | "description": "VToken", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/07.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } -------------------------------------------------------------------------------- /src/assets/nft/8.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "InsecureDex", 3 | "description": "InsecureDex", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/08.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } -------------------------------------------------------------------------------- /src/assets/nft/9.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fallout", 3 | "description": "Fallout", 4 | "image": "https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/main/src/assets/nft/09.jpeg", 5 | "external_link": "https://starknet-challenges.vercel.app/" 6 | } -------------------------------------------------------------------------------- /src/assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/assets/screenshot.png -------------------------------------------------------------------------------- /src/components/Challenge.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useState, useMemo } from 'react' 3 | import { useAccount,useConnect, Connector, useContractWrite,useWaitForTransaction, 4 | useContractRead,useContract } from '@starknet-react/core'; 5 | import { goerli, sepolia, mainnet } from "@starknet-react/chains"; 6 | import { 7 | StarknetConfig, 8 | publicProvider, 9 | argent, 10 | braavos, 11 | useInjectedConnectors, 12 | } from "@starknet-react/core"; 13 | 14 | import '../App.css'; 15 | import mainABI from '../assets/main_abi.json' 16 | import global from '../global.jsx' 17 | 18 | import challengeCode1 from '../assets/challenge1.cairo' 19 | import challengeCode2 from '../assets/challenge2.cairo' 20 | import challengeCode3 from '../assets/challenge3.cairo' 21 | import challengeCode4 from '../assets/challenge4.cairo' 22 | import challengeCode5 from '../assets/challenge5.cairo' 23 | import challengeCode6 from '../assets/challenge6.cairo' 24 | import challengeCode7 from '../assets/challenge7.cairo' 25 | import challengeCode7_erc20 from '../assets/challenge7_erc20.cairo' 26 | import challengeCode8 from '../assets/challenge8.cairo' 27 | import challenge8ERC20Code from '../assets/challenge8_erc20.cairo' 28 | import challenge8ERC223Code from '../assets/challenge8_erc223.cairo' 29 | import challenge8DEXCode from '../assets/challenge8_dex.cairo' 30 | import challengeCode9 from '../assets/challenge9.cairo' 31 | import challengeCode10 from '../assets/challenge10.cairo' 32 | import challengeCode11 from '../assets/challenge11.cairo' 33 | import challengeCode12 from '../assets/challenge12.cairo' 34 | import challengeCode13 from '../assets/challenge13.cairo' 35 | import challengeCode14 from '../assets/challenge14.cairo' 36 | import challengeCode14Wallet from '../assets/challenge14_wallet.cairo' 37 | import challengeCode14Coin from '../assets/challenge14_coin.cairo' 38 | 39 | import { monokaiSublime } from 'react-syntax-highlighter/dist/esm/styles/hljs'; 40 | import SyntaxHighlighter from 'react-syntax-highlighter'; 41 | 42 | import ToggleSwitch from './ToggleSwitch.js'; 43 | 44 | function ChallengeMint({challengeNumber}) { 45 | const [hash, setHash] = useState(undefined) 46 | 47 | const { address } = useAccount(); 48 | 49 | const { contract } = useContract({ 50 | abi: mainABI, 51 | address: global.MAIN_CONTRACT_ADDRESS, 52 | }); 53 | 54 | const calls = useMemo(() => { 55 | if (!address || !contract) return []; 56 | return contract.populateTransaction["mint"](challengeNumber); 57 | }, [contract, address]); 58 | 59 | const { 60 | writeAsync 61 | } = useContractWrite({ 62 | calls, 63 | }); 64 | 65 | const handleClick = () => { 66 | writeAsync().then(tx => setHash(tx.transaction_hash)) 67 | } 68 | 69 | const { isLoading, isError, error, data } = useWaitForTransaction({hash: hash, watch: true}) 70 | 71 | return ( 72 | <> 73 | {!data &&

} 74 | {data &&
Tx.Hash: {hash}
Status: {data.finality_status}
} 75 | {data && ((data.finality_status=="ACCEPTED_ON_L2") || (data.finality_status=="ACCEPTED_ON_L1")) &&
Already Minted (View)
} 76 | 77 | ) 78 | } 79 | 80 | function ClaimNFT({challengeNumber}){ 81 | 82 | const { address } = useAccount() 83 | 84 | const { data, isError, isLoading, error } = useContractRead({ 85 | functionName: "get_mint_status", 86 | abi:mainABI, 87 | address: global.MAIN_CONTRACT_ADDRESS, 88 | args:[address,challengeNumber], 89 | watch: true, 90 | }); 91 | 92 | if (isLoading) return
Loading ...
; 93 | if (isError || !data) return
Error: {error?.message}
; 94 | 95 | return( 96 |
97 | {data && data._minted == 0?
Already Resolved
:
Already Resolved
Already Minted (View)
} 98 |
99 | ) 100 | } 101 | 102 | function Status({challengeNumber}){ 103 | const { address } = useAccount() 104 | 105 | const { data, isError, isLoading, error } = useContractRead({ 106 | functionName: "get_challenge_status", 107 | abi:mainABI, 108 | address: global.MAIN_CONTRACT_ADDRESS, 109 | args:[address,challengeNumber], 110 | watch: true, 111 | }); 112 | 113 | if (isLoading) return
Loading ...
; 114 | if (isError || !data) return
Error: {error?.message}
; 115 | 116 | return( 117 |
118 | {data && data._resolved == 0?():} 119 |
120 | ) 121 | } 122 | 123 | function Points(){ 124 | const { address } = useAccount() 125 | 126 | const { data, isError, isLoading, error } = useContractRead({ 127 | abi:mainABI, 128 | address: global.MAIN_CONTRACT_ADDRESS, 129 | functionName: "get_points", 130 | args:[address], 131 | watch: true, 132 | }); 133 | 134 | if (isLoading) return
Loading ...
; 135 | if (isError || !data) return
Error: {error?.message}
; 136 | 137 | return( 138 | 139 | Your Score:{data._points.toString()} 140 | 141 | ) 142 | } 143 | 144 | function ChallengeDeploy({challengeNumber}) { 145 | 146 | const [hash, setHash] = useState(undefined) 147 | 148 | const { address } = useAccount(); 149 | 150 | const { contract } = useContract({ 151 | abi: mainABI, 152 | address: global.MAIN_CONTRACT_ADDRESS, 153 | }); 154 | 155 | const calls = useMemo(() => { 156 | if (!address || !contract) return []; 157 | return contract.populateTransaction["deploy_challenge"](challengeNumber); 158 | }, [contract, address]); 159 | 160 | const { 161 | writeAsync 162 | } = useContractWrite({ 163 | calls, 164 | }); 165 | 166 | const handleClick = () => { 167 | writeAsync().then(tx => setHash(tx.transaction_hash)) 168 | } 169 | 170 | const { isLoading, isError, error, data } = useWaitForTransaction({hash: hash, watch: true}) 171 | 172 | let newContractAddress="" 173 | 174 | return ( 175 | <> 176 |

177 | {data && (data.finality_status=="ACCEPTED_ON_L2"||data.finality_status=="ACCEPTED_ON_L1") && 178 | data.events.forEach(event => { 179 | let paddedFrom="0x"+event.from_address.substring(2).padStart(64,'0') 180 | let paddedTo="0x"+global.MAIN_CONTRACT_ADDRESS.substring(2).padStart(64,'0') 181 | if (paddedFrom==paddedTo) { 182 | newContractAddress=event.data[0] 183 | } 184 | }) 185 | } 186 | {isError &&
Error: {error?.message}
} 187 | {data &&
Tx.Hash: {hash}
Status: {data.finality_status}
} 188 | {newContractAddress &&
Challenge contract deployed at address: {newContractAddress}
} 189 | {data && (data.finality_status=="ACCEPTED_ON_L2"||data.finality_status=="ACCEPTED_ON_L1")?
:
} 190 | 191 | ) 192 | } 193 | 194 | function ChallengeCheck({challengeNumber}) { 195 | const [hash, setHash] = useState(undefined) 196 | 197 | const { address } = useAccount(); 198 | 199 | const { contract } = useContract({ 200 | abi: mainABI, 201 | address: global.MAIN_CONTRACT_ADDRESS, 202 | }); 203 | 204 | const calls = useMemo(() => { 205 | if (!address || !contract) return []; 206 | return contract.populateTransaction["test_challenge"](challengeNumber); 207 | }, [contract, address]); 208 | 209 | const { 210 | writeAsync 211 | } = useContractWrite({ 212 | calls, 213 | }); 214 | 215 | const handleClick = () => { 216 | writeAsync().then(tx => setHash(tx.transaction_hash)) 217 | } 218 | 219 | const { isLoading, isError, error, data } = useWaitForTransaction({hash: hash, watch: true}) 220 | 221 | return ( 222 | <> 223 |

224 | {isError &&
Error: {error.message}
} 225 | {data &&
Tx.Hash: {hash}
Status: {data.finality_status}
} 226 | {data && (data.finality_status=="ACCEPTED_ON_L2"||data.finality_status=="ACCEPTED_ON_L1") && } 227 | 228 | ) 229 | } 230 | 231 | function ConnectWallet({challengeNumber}) { 232 | const { connect, connectors } = useConnect() 233 | const { address } = useAccount() 234 | const { disconnect } = useConnect() 235 | 236 | if (!address) 237 | return ( 238 |
239 | {connectors.map((connector) => ( 240 |

241 | 244 |

245 | ))} 246 |
247 | ) 248 | return ( 249 | <> 250 |

Connected: {address.substring(0,6)}...{address.substring(address.length - 4)}.

251 | {address &&

} 252 | {address &&

} 253 | 254 | ) 255 | } 256 | 257 | export default function Challenge({ challengeNumber }) { 258 | const [text, setText] = React.useState(); 259 | const [text2, setText2] = React.useState(); 260 | const [text3, setText3] = React.useState(); 261 | const [text4, setText4] = React.useState(); 262 | const [lang, setLang] = React.useState(true); 263 | 264 | const challengeCode = { 265 | 1: challengeCode1, 266 | 2: challengeCode2, 267 | 3: challengeCode3, 268 | 4: challengeCode4, 269 | 5: challengeCode5, 270 | 6: challengeCode6, 271 | 7: challengeCode7, 272 | 8: challengeCode8, 273 | 9: challengeCode9, 274 | 10: challengeCode10, 275 | 11: challengeCode11, 276 | 12: challengeCode12, 277 | 13: challengeCode13, 278 | 14: challengeCode14, 279 | }; 280 | 281 | React.useEffect(() => { 282 | const fetchData = async () => { 283 | try { 284 | const response = await fetch(challengeCode[challengeNumber]); 285 | const textContent = await response.text(); 286 | setText(textContent); 287 | 288 | // Operaciones de recuperación adicionales basadas en challengeNumber se pueden agregar aquí 289 | if (challengeNumber === 7) { 290 | const response2 = await fetch(challengeCode7_erc20); 291 | const textContent2 = await response2.text(); 292 | setText2(textContent2); 293 | } 294 | 295 | if (challengeNumber === 8) { 296 | const response2 = await fetch(challenge8ERC223Code); 297 | const textContent2 = await response2.text(); 298 | setText2(textContent2); 299 | 300 | const response3 = await fetch(challenge8ERC20Code); 301 | const textContent3 = await response3.text(); 302 | setText3(textContent3); 303 | 304 | const response4 = await fetch(challenge8DEXCode); 305 | const textContent4 = await response4.text(); 306 | setText4(textContent4); 307 | } 308 | 309 | if (challengeNumber === 14) { 310 | const response2 = await fetch(challengeCode14Wallet); 311 | const textContent2 = await response2.text(); 312 | setText2(textContent2); 313 | 314 | const response3 = await fetch(challengeCode14Coin); 315 | const textContent3 = await response3.text(); 316 | setText3(textContent3); 317 | } 318 | } catch (error) { 319 | console.error('Error al recuperar el código del desafío:', error); 320 | } 321 | }; 322 | 323 | fetchData(); 324 | }, [challengeNumber]); 325 | 326 | const textOptions = ["EN", "ES"]; 327 | const chkID = "checkboxID"; 328 | const titleChallenge = Array(100); 329 | const descChallengeEn = Array(100); 330 | const descChallengeEs = Array(100); 331 | 332 | titleChallenge[1] = "DEPLOY A CONTRACT"; 333 | descChallengeEn[1] = "Just connect your wallet in starknet SEPOLIA testnet and click the \ 334 | \"Begin Challenge\" button on the bottom to deploy the challenge contract.\n\n \ 335 | You don’t need to do anything with the contract once it’s deployed. \n\n \ 336 | Just press “Check Solution” button to verify that you deployed successfully.\n\n \ 337 | Here’s the code for this challenge:"; 338 | descChallengeEs[1] = "Solo conecta tu wallet en Starknet SEPOLIA Testnet y haz click en el boton \ 339 | \"Begin Challenge\" al fondo para desplegar el contrato inteligente.\n\n \ 340 | No necesitas hacer nada con el contrato una vez que se implementa. \n\n \ 341 | Simplemente presiona el botón \"Check Solution\" para verificar que se implementó correctamente.\n\n \ 342 | Aquí está el código para este reto:"; 343 | 344 | titleChallenge[2] = "CALL ME"; 345 | descChallengeEn[2] = "To complete this challenge, all you need to do is call a function.\n\n \ 346 | The \“Begin Challenge\” button will deploy the following contract, call the function named \ 347 | callme and then click the \“Check Solution\” button.\n\n \ 348 | Here’s the code for this challenge:"; 349 | descChallengeEs[2] = "Para completar este desafío, todo lo que necesita hacer es llamar a una función.\n\n \ 350 | El botón \"Begin Challenge\" desplegará el siguiente contrato, llama a la función denominada \ 351 | callme y luego haz clic en el botón \"Check Solution\".\n\n \ 352 | Aquí está el código para este desafío:"; 353 | 354 | titleChallenge[3] = "CHOOSE A NICKNAME"; 355 | descChallengeEn[3] = "It’s time to set your nickname! \ 356 | This nickname is how you’ll show up on the leaderboard. \n\n \ 357 | The game smart contract keeps track of a nickname for every player. \n\n \ 358 | To complete this challenge, set your nickname to a non-empty string. \ 359 | The smart contract is running on the SEPOLIA test network at the address \ 360 | "+ global.MAIN_CONTRACT_ADDRESS.toString() + ".\n\n \ 361 | Here’s the code for this challenge:"; 362 | descChallengeEs[3] = "¡Es hora de establecer tu nickname! \ 363 | Es la forma en que aparecerás en la tabla de clasificación.\n\n \ 364 | El contrato inteligente del juego tiene un registro de nicknames para cada jugador.\n\n \ 365 | Para completar este reto, establezca su apodo en una cadena no vacía. El contrato inteligente \ 366 | se está ejecutando en Starknet SEPOLIA Testnet en la dirección "+ global.MAIN_CONTRACT_ADDRESS.toString() + ".\n\n \ 367 | Aquí está el código para este reto:"; 368 | 369 | titleChallenge[4] = "GUESS A NUMBER"; 370 | descChallengeEn[4] = "I’m thinking of a number. All you have to do is guess it.\n\n \ 371 | Here’s the code for this challenge:"; 372 | descChallengeEs[4] = "Estoy pensando en un número. Todo lo que tienes que hacer es adivinarlo.\n\n \ 373 | Aquí está el código para este reto:"; 374 | 375 | titleChallenge[5] = "GUESS SECRET NUMBER"; 376 | descChallengeEn[5] = "Putting the answer in the code makes things a little too easy. \ 377 | This time I’ve only stored the hash of the number (between 1 and 5000). \ 378 | Good luck reversing a cryptographic hash!:"; 379 | descChallengeEs[5] = "Poner la respuesta en el código hace que las cosas sean demasiado fáciles. \ 380 | Esta vez solo he almacenado el hash del número (entre 1 y 5000). \ 381 | ¡Buena suerte para revertir un hash criptográfico!:"; 382 | 383 | titleChallenge[6] = "GUESS RANDOM NUMBER"; 384 | descChallengeEn[6] = "This time the number is generated based on a couple fairly random sources:"; 385 | descChallengeEs[6] = "Esta vez, el número generado está basado en un para de fuentes bastante aleatorias:"; 386 | 387 | titleChallenge[7] = "VitaToken seems safe, right?"; 388 | descChallengeEn[7] = "Our beloved Vitalik is the proud owner of 100 $VTLK, which is a token with minimal functions that follows \ 389 | the ERC20 token standard. Or at least that is what it seems...Upon deployment, \ 390 | the VToken contract mints 100 $VTLK to Vitalik's address. \ 391 | Is there a way for you to steal those tokens from him?\n \ 392 | Challenge source code:"; 393 | descChallengeEs[7] = "Nuestro querido Vitalik es el orgulloso propietario de 100 $VTLK, que es un token con funciones mínimas que sigue \ 394 | el estándar de tokens ERC20. O al menos eso es lo que parece... Tras el deploy, \ 395 | el contrato VToken emite (mint) 100 $VTLK a la dirección de Vitalik. \ 396 | ¿Hay alguna manera de que puedas robarle los tokens?\n \ 397 | Código fuente del reto:"; 398 | 399 | titleChallenge[8] = "It's always sunny in decentralized exchanges"; 400 | descChallengeEn[8] = "I bet you are familiar with decentralized exchanges: a magical place where one can exchange different tokens. \ 401 | InsecureDexLP is exactly that: a very insecure Uniswap-kind-of decentralized exchange. \ 402 | Recently, the $ISEC token got listed in this dex and can be traded against a not-so-popular token called $SET.\n\n \ 403 | \ 404 | 📌 Upon deployment, InSecureumToken and SimpleERC223Token mint an initial supply of 100 $ISEC and 100 $SET to the contract deployer.\n \ 405 | 📌 The InsecureDexLP operates with $ISEC and $SET.\n \ 406 | 📌 The dex has an initial liquidity of 10 $ISEC and 10 $SET, provided by deployer. This quantity can be increased by anyone through token deposits.\n \ 407 | 📌 Adding liquidity to the dex rewards liquidity pool tokens (LP tokens), which can be redeemed in any moment for the original funds.\n \ 408 | 📌 Also the deployer graciously airdrops the challenger (you!) 1 $ISEC and 1 $SET.\n\n \ 409 | \ 410 | Will you be able to drain most of InsecureDexLP's $ISEC/$SET liquidity? 😈😈😈\n \ 411 | Build a smart contract to exploit this vulnerability and call it with call_exploit function.\n\n \ 412 | Main deployer contract with challenge setup:"; 413 | descChallengeEs[8] = "Apuesto a que está familiarizado con los exchange descentralizados: un lugar mágico donde se pueden intercambiar diferentes tokens. \ 414 | InsecureDexLP es exactamente eso: un tipo de exchange descentralizado estilo Uniswap pero muy inseguro. \ 415 | Recientemente, el token $ISEC se incluyó en este dex y se puede cambiar por un token no tan popular llamado $SET.\n\n \ 416 | \ 417 | 📌 Tras la implementación, InSecureumToken y SimpleERC223Token emiten un suministro inicial de 100 $ISEC y 100 $SET para el implementador (deployer) del contrato.\n \ 418 | 📌 El InsecureDexLP opera con $ISEC y $SET.\n \ 419 | 📌 El dex tiene una liquidez inicial de 10 $ISEC y 10 $SET, proporcionada por el implementador (deployer). Esta cantidad puede ser incrementada por cualquier persona que deposite tokens.\n \ 420 | 📌 Agregando liquidez al dex recompensa con tokens del pool de liquidez (tokens LP), que se pueden canjear en cualquier momento por los fondos originales.\n \ 421 | 📌 Además, el implementador emite gentilmente al retador (¡a ti!) 1 $ISEC y 1 $SET.\n\n \ 422 | \ 423 | ¿Podrás drenar la mayor parte de la liquidez de $ISEC/$SET de InsecureDexLP? 😈😈😈\n \ 424 | Crea un contrato inteligente para explotar esta vulnerabilidad y llámalo con la función call_exploit.\n\n \ 425 | Main deployer contract with challenge setup:"; 426 | 427 | titleChallenge[9] = "FAL1OUT"; 428 | descChallengeEn[9] = "Claim ownership of the contract below to complete this level:"; 429 | descChallengeEs[9] = "Reclama la propiedad del contrato a continuación para completar este nivel:"; 430 | 431 | titleChallenge[10] = "COIN FLIP"; 432 | descChallengeEn[10] = "This is a coin flipping game where you need to build up your winning streak by guessing the outcome of a coin flip. \ 433 | To complete this level you'll need to use your psychic abilities to guess the correct outcome 6 times in a row:"; 434 | descChallengeEs[10] = "Este es un juego de lanzamiento de monedas en el que debes construir tu racha ganadora adivinando el resultado del lanzamiento. \ 435 | Para completar este nivel necesitarás usar tus habilidades psíquicas para adivinar el resultado correcto 6 veces seguidas:"; 436 | 437 | titleChallenge[11] = "TELEPHONE"; 438 | descChallengeEn[11] = "Claim ownership of the contract below to complete this level:"; 439 | descChallengeEs[11] = "Reclama la propiedad del contrato a continuación para completar este nivel:"; 440 | 441 | titleChallenge[12] = "VAULT"; 442 | descChallengeEn[12] = "Unlock the vault to pass the level!."; 443 | descChallengeEs[12] = "Desbloquea la bóveda para pasar de nivel!."; 444 | 445 | titleChallenge[13] = "NAUGHTY COIN"; 446 | descChallengeEn[13] = "NaughtCoin is an ERC20 token and you're already holding all of them. The catch is that you'll only be able \ 447 | to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer \ 448 | them freely? Complete this level by getting your token balance to 0."; 449 | descChallengeEs[13] = "NaughtCoin es un token ERC20 token y tu ya tienes 10.000 de ellos. El problema es que tu solo serás capaz de \ 450 | transferirlos después de un periodo de 10 años de bloqueo. Te puedes imaginar como conseguir sacarlos a otra cuenta de manera que puedas transferirlos \ 451 | libremente? Completa este nivel dejando el saldo de tu NauthCoin en 0."; 452 | 453 | titleChallenge[14] = "GOOD SAMARITAN"; 454 | descChallengeEn[14] = "This instance represents a Good Samaritan that is wealthy and ready to donate some coins to anyone requesting it. \ 455 | Would you be able to drain all the balance from his Wallet?"; 456 | descChallengeEs[14] = "Esta instancia representa a un Buen Samaritano que es muy rico y está dispuesto a donar a todo aquel que se lo solicite. \ 457 | Serás capaz de vaciar todo el saldo de su wallet?"; 458 | 459 | const { connectors } = useInjectedConnectors({ 460 | // Show these connectors if the user has no connector installed. 461 | recommended: [ 462 | argent(), 463 | braavos(), 464 | ], 465 | // Hide recommended connectors if the user has any connector installed. 466 | includeRecommended: "onlyIfNoConnectors", 467 | // Randomize the order of the connectors. 468 | order: "random" 469 | }); 470 | 471 | 472 | const CodeContainer = ({ text, title }) => ( 473 |
474 | {title &&

{title}

} 475 |
476 | 490 | {text} 491 | 492 |
493 |
494 | ); 495 | 496 | return ( 497 |
498 |
499 |
500 | setLang(checked)} 506 | /> 507 |
508 |
509 | 514 |

515 | {titleChallenge[challengeNumber]} 516 |

517 |
518 | {lang ? ( 519 |
520 |

{descChallengeEn[challengeNumber]}

521 |
522 | ) : ( 523 |
524 |

{descChallengeEs[challengeNumber]}

525 |
526 | )} 527 |
528 |
529 | {challengeNumber === 8 ? ( 530 |
531 | 532 | 533 | 534 | 535 |
536 | ) : challengeNumber === 14 ? ( 537 |
538 | 539 | 540 | 541 |
542 | ) : challengeNumber === 7 ? ( 543 |
544 | 545 | 546 |
547 | ) : ( 548 |
549 | 550 |
551 | )} 552 |
553 | 554 |
555 |
556 |
557 |
558 |
559 | ); 560 | } -------------------------------------------------------------------------------- /src/components/Home.css: -------------------------------------------------------------------------------- 1 | a, 2 | a:visited { 3 | color: white; 4 | } 5 | 6 | body { 7 | display: flex; 8 | flex-direction: column; 9 | min-height: 100vh; 10 | background-color: #343464; 11 | color: #ffffff; 12 | font-size: 17px; 13 | line-height: 1.5; 14 | } 15 | 16 | .center-image { 17 | display: flex; 18 | margin: 10px auto; 19 | max-width: 100%; 20 | max-height: 300px; 21 | } 22 | 23 | .container { 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | justify-content: space-between; 28 | min-height: 80vh; 29 | margin: 120px 20px; 30 | } 31 | 32 | .rounded-box { 33 | background-color: #8c86b6; 34 | border-radius: 15px; 35 | padding: 15px; 36 | margin: 10px; 37 | max-width: 440px; 38 | box-sizing: border-box; 39 | } 40 | 41 | .flex-table { 42 | display: flex; 43 | flex-flow: row wrap; 44 | justify-content: center; 45 | transition: 0.5s; 46 | margin: auto; 47 | } 48 | 49 | .flex-table:hover { 50 | transition: 400ms; 51 | } 52 | 53 | .flex-row, 54 | .flex-row-emp, 55 | .flex-row-wide { 56 | 57 | text-align: center; 58 | padding: 4px; 59 | box-sizing: border-box; 60 | } 61 | 62 | .flex-cell { 63 | 64 | text-align: center; 65 | padding: 4px; 66 | box-sizing: border-box; 67 | 68 | &:last-child { 69 | border-right: 0; 70 | } 71 | } 72 | 73 | /*Challenge Text Style*/ 74 | .text-title { 75 | font-size: 2em; 76 | } 77 | 78 | .text-container { 79 | white-space: pre-line; 80 | max-width: 800px; 81 | margin: 0 auto; 82 | display: auto; 83 | align-items: center; 84 | text-align: center; 85 | font-size: 1.3em; 86 | line-height: 1em; 87 | } 88 | 89 | /*Code Contenier*/ 90 | .code-title { 91 | text-align: center; 92 | margin-bottom: 10px; 93 | } 94 | 95 | .code-container { 96 | width: 100%; 97 | } 98 | 99 | .code-content { 100 | overflow-x: auto; 101 | white-space: pre-wrap; 102 | width: 100%; 103 | max-width: 100%; 104 | padding: 10px; 105 | border-radius: 10px; 106 | background-color: #000000; 107 | font-size: 14px; 108 | line-height: 1.2; 109 | } 110 | 111 | /*Footer*/ 112 | footer { 113 | background-color: transparent; 114 | padding: 0px; 115 | margin-top: 10px; 116 | text-align: center; 117 | } 118 | 119 | footer p a { 120 | color: #f0e833 !important; 121 | text-decoration: none; 122 | } 123 | 124 | footer p a:hover { 125 | text-decoration: underline; 126 | } 127 | 128 | /*Responsive*/ 129 | @media all and (max-width: 767px), 130 | (max-width: 1368px) { 131 | 132 | body{ 133 | font-size: 13px; 134 | } 135 | 136 | .center-image { 137 | max-width: 100%; 138 | max-height: 170px; 139 | margin: 0 auto; 140 | } 141 | 142 | .container, 143 | .rounded-box { 144 | max-width: 340px; 145 | } 146 | 147 | .flex-row, 148 | .flex-row-emp, 149 | .flex-row-wide { 150 | width: 900px; 151 | box-sizing: border-box; 152 | 153 | &.first { 154 | width: 100%; 155 | border-right: 0; 156 | } 157 | } 158 | 159 | .text-container { 160 | text-align: center; 161 | max-width: 100%; 162 | } 163 | 164 | .flex-cell { 165 | width: 100%; 166 | border-right: 0; 167 | } 168 | 169 | .column { 170 | width: 100%; 171 | } 172 | } 173 | 174 | @media all and (max-width: 430px) { 175 | 176 | .flex-row, 177 | .flex-row-emp, 178 | .flex-row-wide { 179 | width: 100%; 180 | 181 | &.first { 182 | width: 100%; 183 | border-right: 0; 184 | } 185 | } 186 | 187 | .text-container { 188 | text-align: center; 189 | max-width: 100%; 190 | } 191 | 192 | .column { 193 | width: 100%; 194 | 195 | .flex-row, 196 | .flex-row-emp, 197 | .flex-row-wide { 198 | border-bottom: solid 1px; 199 | } 200 | } 201 | 202 | .flex-cell { 203 | width: 100%; 204 | border-right: 0; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/components/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './Home.css'; 3 | import ToggleSwitch from './ToggleSwitch'; 4 | import logo from './logossc.png'; 5 | 6 | function Home() { 7 | const text = ["EN", "ES"]; 8 | const chkID = "checkboxID"; 9 | const [lang, setLang] = useState(true); 10 | 11 | if (lang) { 12 | return ( 13 |
14 | 15 | setLang(checked)} 21 | /> 22 | 23 |
24 | logo 25 |
26 | 27 |
28 |
29 |
30 |

What is this?

31 |

32 | Inspired in Ethereum Capture the Ether, this is a game in which you hack Starknet smart contracts to learn about security. 33 | 34 | It's meant to be both fun and educational.

35 | 36 | This game was builded in his Solidity original version by @smarx, who blogs about smart contract development at 37 | Program the Blockchain and now is being adapted to Starknet network by @devnet0x.

38 | The goal behind this project is add custom challenges from community and migrate challenges from other smart contracts CTFs 39 | (Openzeppelin Ethernaut), 40 | (Secureum A-Maze-X), 41 | (Tinchoabbate Damn Vulnerable Defi, etc). 42 |

43 |
44 |
45 | 46 |
47 |
48 |

How do I win?

49 |

50 | The game consists of a series of challenges in different categories. You earn points for every challenge you complete.

51 | Harder challenges are worth more points. Each challenge is in the form of a smart contract with an isComplete function. 52 | The goal is always to make isComplete() return TRUE.

53 | There's a leaderboard too (and dont worry about upgrades because score contract was implemented with a proxy). 54 |

55 |
56 |
57 | 58 |
59 |
60 |

How to contribute?

61 |

PR your own smart contract challenge to the 62 | github reposiroty and i will try to add it as son as possible.

63 | The only requirement is that your Cairo Smart Contract must have a isComplete() external 64 | function with return TRUE if challenge was succesfully completed. 65 |

66 |
67 |
68 | 69 |
70 | 71 | 74 | 75 |
76 | ); 77 | } else { 78 | return ( 79 |
80 | 81 | setLang(checked)} 87 | /> 88 | 89 |
90 | logo 91 |
92 | 93 | 94 |
95 |
96 |
97 |

¿Qué es esto?

98 |

99 | Inspirado en Capture the Ether de Ethereum, este es un juego en el cual hackeas smart contracts en Starknet para aprender de seguridad. 100 | Su objetivo es ser divertido y educacional.

101 | Este juego fue contruido en su version original para Solidity por @smarx, quien escribe sobre el desarrollo de contratos inteligentes en 102 | Program the Blockchain y ahora está siendo adaptado para Starknet @devnet0x.

103 | Los objetivos detrás de este proyecto son agregar retos personalizados de la comunidad y migrar retos desde otros CTF de contratos inteligentes 104 | (Openzeppelin Ethernaut,) 105 | (Secureum A-Maze-X,) 106 | (Tinchoabbate Damn Vulnerable Defi, etc). 107 |

108 |
109 |
110 | 111 |
112 |
113 |

¿Como puedo jugar?

114 |

115 | El juego consiste en una serie de retos en diferentes categorias. Obtienes puntos por cada reto que completas.

116 | Los retos mas difíciles entregan un mayor puntaje. 117 | Cada reto, tiene la forma de un contrato inteligente con una función isComplete.El objetivo siempre es hacer que isComplete() retorne verdadero (TRUE).

118 | Además, hay un tabla de clasificación (leaderboard) (y no te preocupes por los upgrades porque el contrato inteligente principal fue implementado con un proxy). 119 |

120 |
121 |
122 | 123 |
124 |
125 |

¿Cómo contribuir?

126 |

PR tu propio reto con un contrato inteligente en el 127 | repo de github e intentaré agregarlo tan pronto como sea posible.

128 | El único requerimiento es que el contrato en Cairo debe tener una función externa llamada isComplete() que retorne verdadero (TRUE) 129 | si el reto fue completado exitosamente. 130 |

131 |
132 |
133 | 134 |
135 | 136 | 139 | 140 |
141 | ); 142 | } 143 | } 144 | 145 | export default Home; -------------------------------------------------------------------------------- /src/components/Leaderboard.jsx: -------------------------------------------------------------------------------- 1 | import '../App.css'; 2 | import { useAccount,useConnect,Connector, 3 | useContractRead,useContract } from '@starknet-react/core'; 4 | import { useState } from 'react' 5 | 6 | import { goerli, mainnet, sepolia } from "@starknet-react/chains"; 7 | import { 8 | StarknetConfig, 9 | publicProvider, 10 | argent, 11 | braavos, 12 | useInjectedConnectors, 13 | } from "@starknet-react/core"; 14 | 15 | import mainABI from '../assets/main_abi.json' 16 | import global from '../global.jsx' 17 | 18 | import { feltToString } from '../utils/utils.js' 19 | 20 | import ToggleSwitch from './ToggleSwitch.js'; 21 | 22 | function Points(){ 23 | const { data, isError, isLoading, error } = useContractRead({ 24 | functionName: "get_ranking", 25 | abi:mainABI, 26 | address: global.MAIN_CONTRACT_ADDRESS, 27 | watch: true, 28 | }); 29 | 30 | if (isLoading) return
Loading ...
; 31 | if (isError || !data) return
Error: {error?.message}
; 32 | 33 | // Sort the players by points. 34 | data._player_list.sort((a, b) => Number(b.points) - Number(a.points)); 35 | // Convert all bigints to strings and all bytes to hex strings. 36 | const data2 = data._player_list.map((player) => ({ 37 | nickname: player.nickname, 38 | points: player.points.toString(), 39 | address: player.address.toString(16), 40 | })); 41 | 42 | return( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | { 51 | // shortString.decodeShortString(content.nickname).substring(0,12) doesn't display emojis 52 | data2.map (content =>( 53 | 54 | 55 | 56 | 57 | 58 | )) 59 | } 60 |
NicknamePointsAddress
{feltToString(content.nickname).substring(0,12)}{content.points}0x{content.address.substring(0,4)}...{content.address.substring(content.address.length - 4)}
61 |
62 | ) 63 | } 64 | 65 | 66 | function ConnectWallet() { 67 | const { connect, connectors } = useConnect() 68 | const { address } = useAccount() 69 | const { disconnect } = useConnect() 70 | 71 | if (!address) 72 | return ( 73 |
74 | {connectors.map((connector) => ( 75 |

76 | 79 |

80 | ))} 81 |
82 | ) 83 | return ( 84 | <> 85 |

Connected: {address}.

86 | {address &&

} 87 | 88 | ) 89 | } 90 | 91 | 92 | function Leaderboard() { 93 | const text = ["EN", "ES"]; 94 | const chkID = "checkboxID"; 95 | const [lang, setLang] = useState(true); 96 | 97 | const { connectors } = useInjectedConnectors({ 98 | // Show these connectors if the user has no connector installed. 99 | recommended: [ 100 | argent(), 101 | braavos(), 102 | ], 103 | // Hide recommended connectors if the user has any connector installed. 104 | includeRecommended: "onlyIfNoConnectors", 105 | // Randomize the order of the connectors. 106 | order: "random" 107 | }); 108 | 109 | return ( 110 | 111 |
112 |
113 | setLang(checked)} /> 114 |
115 | 116 | 117 | 127 | 128 |
118 | 122 |

LEADERBOARD

123 | {lang?

Connect wallet to access hall of fame

:

Conecta tu wallet para acceder al salón de la fama

} 124 | 125 |
126 |
129 |
130 | ); 131 | } 132 | 133 | export default Leaderboard; -------------------------------------------------------------------------------- /src/components/Nopage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Nopage() { 4 | return ( 5 |

Ups, this page doesn't exist.

6 | ); 7 | } 8 | 9 | export default Nopage; -------------------------------------------------------------------------------- /src/components/ToggleSwitch.css: -------------------------------------------------------------------------------- 1 | .toggle-switch { 2 | position: fixed; 3 | top: 18px; 4 | right: 10px; 5 | width: 100%; 6 | display: inline-block; 7 | vertical-align: top; 8 | text-align: left; 9 | } 10 | 11 | .toggle-switch-checkbox { 12 | display: none; 13 | } 14 | 15 | .toggle-switch-label { 16 | display: block; 17 | overflow: hidden; 18 | cursor: pointer; 19 | border: 0 solid #bbb; 20 | border-radius: 20px; 21 | margin: 0; 22 | } 23 | 24 | .toggle-switch-label:focus { 25 | outline: none; 26 | } 27 | 28 | .toggle-switch-label:focus>span { 29 | box-shadow: 0 0 2px 5px red; 30 | } 31 | 32 | .toggle-switch-label>span:focus { 33 | outline: none; 34 | } 35 | 36 | .toggle-switch-inner { 37 | display: block; 38 | width: 200%; 39 | margin-left: -100%; 40 | position: relative; 41 | transition: margin 0.3s ease-in 0s; 42 | } 43 | 44 | .toggle-switch-inner:before, 45 | .toggle-switch-inner:after { 46 | display: block; 47 | float: left; 48 | width: 50%; 49 | height: 34px; 50 | padding: 0; 51 | line-height: 34px; 52 | font-size: 10px; 53 | color: white; 54 | font-weight: bold; 55 | box-sizing: border-box; 56 | } 57 | 58 | .toggle-switch-inner:before { 59 | content: attr(data-yes); 60 | text-transform: uppercase; 61 | padding-left: 5px; 62 | background-color: #2F855A; 63 | color: #fff; 64 | } 65 | 66 | .toggle-switch-disabled { 67 | background-color: #ddd; 68 | cursor: not-allowed; 69 | } 70 | 71 | .toggle-switch-disabled:before { 72 | background-color: #ddd; 73 | cursor: not-allowed; 74 | } 75 | 76 | .toggle-switch-inner:after { 77 | content: attr(data-no); 78 | text-transform: uppercase; 79 | padding-right: 7px; 80 | background-color: #2F855A; 81 | color: #fff; 82 | text-align: right; 83 | } 84 | 85 | .toggle-switch-switch { 86 | display: block; 87 | width: 24px; 88 | margin: 5px; 89 | background: #fff; 90 | position: absolute; 91 | bottom: 0px; 92 | top: 0px; 93 | right: 30px; 94 | border: 0 solid #bbb; 95 | border-radius: 20px; 96 | transition: all 0.3s ease-in 0s; 97 | } 98 | 99 | .toggle-switch-checkbox:checked+.toggle-switch-label .toggle-switch-inner { 100 | margin-left: 0; 101 | } 102 | 103 | .toggle-switch-checkbox:checked+.toggle-switch-label .toggle-switch-switch { 104 | right: 0px; 105 | } 106 | 107 | .toggle-switch.small-switch { 108 | width: 40px; 109 | } 110 | 111 | .toggle-switch.small-switch .toggle-switch-inner:after, 112 | .toggle-switch.small-switch .toggle-switch-inner:before { 113 | height: 20px; 114 | line-height: 20px; 115 | } 116 | 117 | .toggle-switch.small-switch .toggle-switch-switch { 118 | width: 16px; 119 | right: 20px; 120 | margin: 2px; 121 | } 122 | 123 | @media all and (min-width: 467px) { 124 | .toggle-switch { 125 | position: fixed; 126 | right: 20px; 127 | /* Ajustar este valor según sea necesario */ 128 | } 129 | } 130 | /*# sourceMappingURL=styles.css.map */ -------------------------------------------------------------------------------- /src/components/ToggleSwitch.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import './ToggleSwitch.css'; 4 | 5 | /* 6 | Toggle Switch Component 7 | Note: id, checked and onChange are required for ToggleSwitch component to function. 8 | The props name, small, disabled and optionLabels are optional. 9 | Usage: setValue(checked)}} /> 10 | */ 11 | 12 | const ToggleSwitch = ({ id, name, checked, onChange, optionLabels, small, disabled }) => { 13 | function handleKeyPress(e){ 14 | if (e.keyCode !== 32) return; 15 | 16 | e.preventDefault(); 17 | onChange(!checked) 18 | } 19 | 20 | return ( 21 |
22 | onChange(e.target.checked)} 29 | disabled={disabled} 30 | /> 31 | {id ? ( 32 | 55 | ) : null} 56 |
57 | ); 58 | } 59 | 60 | // Set optionLabels for rendering. 61 | ToggleSwitch.defaultProps = { 62 | optionLabels: ["Yes", "No"], 63 | }; 64 | 65 | ToggleSwitch.propTypes = { 66 | id: PropTypes.string.isRequired, 67 | checked: PropTypes.bool.isRequired, 68 | onChange: PropTypes.func.isRequired, 69 | name: PropTypes.string, 70 | optionLabels: PropTypes.array, 71 | small: PropTypes.bool, 72 | disabled: PropTypes.bool 73 | }; 74 | 75 | export default ToggleSwitch; -------------------------------------------------------------------------------- /src/components/ToggleSwitch.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/components/ToggleSwitch.scss -------------------------------------------------------------------------------- /src/components/logossc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/components/logossc.png -------------------------------------------------------------------------------- /src/global.jsx: -------------------------------------------------------------------------------- 1 | const global = {} 2 | global.MAIN_CONTRACT_ADDRESS = "0x05141d769ce5dffd00a2cbd210c41a443360d68fd19a050c8cba22224d786918"; 3 | export default global 4 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { goerli,sepolia, mainnet } from "@starknet-react/chains"; 7 | import { 8 | StarknetConfig, 9 | publicProvider, 10 | argent, 11 | braavos, 12 | useInjectedConnectors, 13 | } from "@starknet-react/core"; 14 | 15 | const root = ReactDOM.createRoot(document.getElementById('root')); 16 | root.render( 17 | 18 | 21 | 22 | 23 | 24 | ); 25 | 26 | // If you want to start measuring performance in your app, pass a function 27 | // to log results (for example: reportWebVitals(console.log)) 28 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 29 | // reportWebVitals(); 30 | -------------------------------------------------------------------------------- /src/layout/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Sidebar from './components/sidebar/Sidebar.jsx'; 3 | import {Outlet} from "react-router-dom"; 4 | 5 | const Layout = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default Layout; -------------------------------------------------------------------------------- /src/layout/components/sidebar/Navbar.css: -------------------------------------------------------------------------------- 1 | .navbar { 2 | background-color: var(--navBg); 3 | height: 70px; 4 | display: flex; 5 | justify-content: start; 6 | align-items: center; 7 | } 8 | 9 | .menu-bars { 10 | margin-left: 1rem; 11 | margin-right: 1rem; 12 | font-size: 2rem; 13 | background: none; 14 | color: #f5f5f5; 15 | margin-top: 10px; 16 | } 17 | 18 | .nav-menu { 19 | background-color: #2d2d4d; 20 | width: 250px; 21 | height: 100vh; 22 | display: flex; 23 | justify-content: center; 24 | position: fixed; 25 | top: 0; 26 | left: -100%; 27 | transition: 850ms; 28 | overflow-y: auto; 29 | z-index: 2; 30 | } 31 | 32 | ::-webkit-scrollbar { 33 | width: 20px; 34 | } 35 | 36 | .nav-menu.active { 37 | left: 0; 38 | transition: 350ms; 39 | } 40 | 41 | .nav-text { 42 | display: flex; 43 | justify-content: start; 44 | align-items: center; 45 | padding: 8px 0 8px 16px; 46 | list-style: none; 47 | height: 60px; 48 | } 49 | 50 | .nav-text a { 51 | text-decoration: none; 52 | color: #f5f5f5; 53 | font-size: 18px; 54 | width: 95%; 55 | height: 100%; 56 | display: flex; 57 | align-items: center; 58 | padding: 0 16px; 59 | border-radius: 4px; 60 | } 61 | 62 | .nav-text a:hover { 63 | background-color: #1a83ff; 64 | } 65 | 66 | .nav-menu-items { 67 | width: 100%; 68 | padding: 0; 69 | } 70 | 71 | .navbar-toggle { 72 | background-color: var(--navBg); 73 | width: 100%; 74 | height: 40px; 75 | display: flex; 76 | justify-content: start; 77 | align-items: center; 78 | } 79 | 80 | span { 81 | margin-left: 10px; 82 | } 83 | 84 | .starknet { 85 | max-width: 100%; 86 | max-height: 51px; 87 | margin-top: 4px; 88 | } 89 | 90 | @media screen and (max-width: 768px),(max-width: 1368px) { 91 | .navbar { 92 | height: 50px; 93 | } 94 | .starknet { 95 | max-width: 100%; 96 | max-height: 38px; 97 | } 98 | } 99 | 100 | @media all and (max-width: 430px) { 101 | .navbar { 102 | height: 45px; 103 | } 104 | 105 | .starknet { 106 | max-width: 50%; 107 | max-height: auto; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/layout/components/sidebar/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import style from './sidebar.module.css'; 4 | import NavItem from './navItem/NavItem.jsx'; 5 | import { sideMenu } from './menu.config.js'; 6 | import * as FaIcons from "react-icons/fa"; //Now i get access to all the icons 7 | import * as AiIcons from "react-icons/ai"; 8 | import { IconContext } from "react-icons"; 9 | import "./Navbar.css"; 10 | import starknet from './starknet.webp'; 11 | 12 | class Sidebar extends React.Component { 13 | constructor() { 14 | super() 15 | this.state = { sidebar: false } 16 | this.showSidebar = this.showSidebar.bind(this); 17 | } 18 | 19 | showSidebar() { 20 | if (this.state.sidebar) 21 | this.setState({ sidebar: false }) 22 | else 23 | this.setState({ sidebar: true }) 24 | }; 25 | 26 | render() { 27 | return ( 28 | <> 29 | 30 |
31 | 32 | 33 | 34 | Starknet 35 |
36 | 37 | 53 |
54 | 55 | ); 56 | } 57 | } 58 | 59 | export default Sidebar; -------------------------------------------------------------------------------- /src/layout/components/sidebar/menu.config.js: -------------------------------------------------------------------------------- 1 | import * as FaIcons from "react-icons/fa"; 2 | import * as AiIcons from "react-icons/ai"; 3 | import * as IoIcons from "react-icons/io"; 4 | import * as BsIcons from "react-icons/bs"; 5 | import * as GiIcons from "react-icons/gi"; 6 | import * as CiIcons from "react-icons/ci"; 7 | import * as RiIcons from "react-icons/ri"; 8 | import * as SiIcons from "react-icons/si"; 9 | 10 | export const sideMenu = [ 11 | { 12 | label: 'Home', 13 | Icon: AiIcons.AiFillHome, 14 | to: '/', 15 | }, 16 | { 17 | label: 'Leaderboard', 18 | Icon: BsIcons.BsTrophyFill, 19 | to: '/leaderboard', 20 | }, 21 | { 22 | label: 'Capture The Ether(2022)', 23 | Icon: FaIcons.FaEthereum, 24 | to: '/cte22', 25 | children: [ 26 | { 27 | label: 'Deploy(50 pts)', 28 | Icon: FaIcons.FaCloudUploadAlt, 29 | to: 'challenge1', 30 | }, 31 | { 32 | label: 'Call Me(100 pts)', 33 | Icon: IoIcons.IoIosCall, 34 | to: 'challenge2' 35 | }, 36 | { 37 | label: 'Nickname(200 pts)', 38 | Icon: FaIcons.FaMask, 39 | to: 'challenge3' 40 | }, 41 | { 42 | label: 'Guess(200 pts)', 43 | Icon: AiIcons.AiFillFileUnknown, 44 | to: 'challenge4' 45 | }, 46 | { 47 | label: 'Secret(300 pts)', 48 | Icon: FaIcons.FaUserSecret, 49 | to: 'challenge5' 50 | }, 51 | { 52 | label: 'Random(300 pts)', 53 | Icon: FaIcons.FaRecycle, 54 | to: 'challenge6' 55 | }, 56 | ], 57 | }, 58 | 59 | { 60 | label: 'Secureum A-maze-X(2022)', 61 | Icon: GiIcons.GiShield, 62 | to: '/amazex22', 63 | children: [ 64 | { 65 | label: 'Vtoken(500 pts)', 66 | Icon: GiIcons.GiToken, 67 | to: 'challenge7', 68 | }, 69 | { 70 | label: 'Insecure Dex(1500 pts)', 71 | Icon: BsIcons.BsCurrencyExchange, 72 | to: 'challenge8', 73 | }, 74 | ], 75 | }, 76 | 77 | { 78 | label: 'Ethernaut(2022)', 79 | Icon: FaIcons.FaUserAstronaut, 80 | to: '/ethernaut22', 81 | children: [ 82 | { 83 | label: 'Fallout(500 pts)', 84 | Icon: BsIcons.BsSpellcheck, 85 | to: 'challenge9', 86 | }, 87 | { 88 | label: 'CoinFlip(700 pts)', 89 | Icon: GiIcons.GiCoinflip, 90 | to: 'challenge10', 91 | }, 92 | { 93 | label: 'Telephone(300 pts)', 94 | Icon: GiIcons.GiRotaryPhone, 95 | to: 'challenge11', 96 | }, 97 | { 98 | label: 'Vault(700 pts)', 99 | Icon: CiIcons.CiVault, 100 | to: 'challenge12', 101 | }, 102 | { 103 | label: 'Naught Coin(700 pts)', 104 | Icon: RiIcons.RiHandCoinFill, 105 | to: 'challenge13', 106 | }, 107 | { 108 | label: 'Good Samaritan(1000 pts)', 109 | Icon: SiIcons.SiHandshake, 110 | to: 'challenge14', 111 | }, 112 | ], 113 | }, 114 | ]; -------------------------------------------------------------------------------- /src/layout/components/sidebar/navItem/NavItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import style from './navItem.module.css'; 4 | import NavItemHeader from './NavItemHeader.jsx'; 5 | 6 | console.log({ style }); 7 | const NavItem = props => { 8 | const { label, Icon, to, children } = props.item; 9 | 10 | if (children) { 11 | return ; 12 | } 13 | 14 | return ( 15 | 16 | 22 | 23 | {label} 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default NavItem; -------------------------------------------------------------------------------- /src/layout/components/sidebar/navItem/NavItemHeader.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { NavLink, useLocation } from 'react-router-dom'; 3 | import style from './navItem.module.css'; 4 | import { FiChevronDown } from "react-icons/fi"; 5 | 6 | const resolveLinkPath = (childTo, parentTo) => `${parentTo}/${childTo}`; 7 | 8 | const NavItemHeader = props => { 9 | const { item } = props; 10 | const { label, Icon, to: headerToPath, children } = item; 11 | const location = useLocation(); 12 | 13 | const [expanded, setExpand] = useState( 14 | location.pathname.includes(headerToPath) 15 | ); 16 | 17 | const onExpandChange = e => { 18 | e.preventDefault(); 19 | e.stopPropagation(); 20 | setExpand(expanded => !expanded); 21 | }; 22 | 23 | return ( 24 | <> 25 | 37 | 38 | {expanded && ( 39 |
40 | {children.map((item, index) => { 41 | const key = `${item.label}-${index}`; 42 | 43 | const { label, Icon, children } = item; 44 | 45 | if (children) { 46 | return ( 47 |
48 | 54 |
55 | ); 56 | } 57 | 58 | return ( 59 | 65 | 66 | {label} 67 | 68 | ); 69 | })} 70 |
71 | )} 72 | 73 | ); 74 | }; 75 | 76 | export default NavItemHeader; -------------------------------------------------------------------------------- /src/layout/components/sidebar/navItem/navItem.module.css: -------------------------------------------------------------------------------- 1 | .navItem { 2 | padding: 0.8rem 1.25rem; 3 | text-decoration: none; 4 | display: flex; 5 | align-items: center; 6 | } 7 | 8 | .navItem:hover { 9 | background-color: #343464; 10 | } 11 | 12 | .activeNavItem { 13 | color: #dbeafe; 14 | background-color: #8c86b6; 15 | } 16 | 17 | .navIcon { 18 | color: #d1d5db; 19 | width: 1.5rem; 20 | height: 1.5rem; 21 | margin-right: 1rem; 22 | } 23 | 24 | .navLabel { 25 | color: #d1d5db; 26 | font-size: 1rem; 27 | text-align: left; 28 | } 29 | 30 | .navItemHeaderButton { 31 | width: 100%; 32 | outline: none; 33 | border: none; 34 | background: transparent; 35 | cursor: pointer; 36 | } 37 | 38 | .navItemHeaderChevron { 39 | color: #d1d5db; 40 | width: 1.5rem; 41 | height: 1.5rem; 42 | margin-left: auto; 43 | transition: all 0.25s; 44 | } 45 | 46 | .chevronExpanded { 47 | transform: rotate(180deg); 48 | } 49 | 50 | .navChildrenBlock { 51 | background-color: #8c86b6; 52 | } -------------------------------------------------------------------------------- /src/layout/components/sidebar/sidebar.module.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | background-color: #2d2d4d; 3 | height: 100%; 4 | } -------------------------------------------------------------------------------- /src/layout/components/sidebar/starknet.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devnet0x/Starknet-Security-Challenges-Factory/85b1e8f2bde2f2aad8d74f37b2cb0545a88394fc/src/layout/components/sidebar/starknet.webp -------------------------------------------------------------------------------- /src/layout/layout.module.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | display: grid; 3 | grid-template-columns: 18rem 1fr; 4 | grid-template-rows: 80px 1fr 80px; 5 | min-height: 100vh; 6 | position: relative; 7 | z-index: 2; 8 | } 9 | 10 | .header { 11 | grid-area: 1 / 1 / 2 / 3; 12 | } 13 | 14 | .aside { 15 | grid-area: 2 / 1 / 4 / 2; 16 | } 17 | 18 | .main { 19 | grid-area: 2 / 2 / 3 / 3; 20 | } 21 | 22 | .footer { 23 | grid-area: 3 / 2 / 4 / 3; 24 | } -------------------------------------------------------------------------------- /src/lib.cairo: -------------------------------------------------------------------------------- 1 | mod assets; 2 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import {Buffer} from 'buffer' 2 | 3 | export function feltToString(felt) { 4 | const newStrB = Buffer.from(felt.toString(16), 'hex') 5 | return newStrB.toString() 6 | } 7 | export function stringToFelt(str) { 8 | return "0x" + Buffer.from(str).toString('hex') 9 | } --------------------------------------------------------------------------------