├── .dapprc ├── .env.example ├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── remappings.txt └── src ├── DapptoolsDemo.sol ├── DapptoolsDemo.t.sol └── NFT.sol /.dapprc: -------------------------------------------------------------------------------- 1 | export DAPP_REMAPPINGS=$(cat remappings.txt) 2 | export DAPP_LINK_TEST_LIBRARIES=0 3 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | export ALCHEMY_API_KEY=YOUR_API_KEY 2 | export ETH_FROM=YOUR_DEFAULT_SENDER_ACCOUNT 3 | export ETH_RPC_URL=http://localhost:8545 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | .env 3 | .notes.md 4 | cache 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include .env 2 | 3 | # dapp deps 4 | update:; dapp update 5 | 6 | all :; dapp build 7 | clean :; dapp clean 8 | test :; dapp test 9 | deploy :; dapp create DapptoolsDemo 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DappTools Demo 2 | - [DappTools Demo](#dapptools-demo) 3 | - [Intro](#intro) 4 | - [QuickStart](#quickstart) 5 | - [Setup](#setup) 6 | - [Build it from scratch](#build-it-from-scratch) 7 | - [Install Dapptools](#install-dapptools) 8 | - [Create a new dapptools project](#create-a-new-dapptools-project) 9 | - [Run Tests](#run-tests) 10 | - [Fuzzing](#fuzzing) 11 | - [Importing from Openzeppelin or external contracts](#importing-from-openzeppelin-or-external-contracts) 12 | - [The NFT Contract](#the-nft-contract) 13 | - [Remappings](#remappings) 14 | - [Deploying to a testnet](#deploying-to-a-testnet) 15 | - [Interacting with contracts](#interacting-with-contracts) 16 | - [Verify your contract on Etherscan](#verify-your-contract-on-etherscan) 17 | - [And finally...](#and-finally) 18 | - [Resources](#resources) 19 | 20 | 21 | ## Intro 22 | 23 | For a more full repo with more good code and examples, checkout the [dapptools-starter-kit](https://github.com/smartcontractkit/dapptools-starter-kit). 24 | 25 | You can work with this repo one of two ways. 26 | 1. [Just copy it](#quickstart) (git clone it), and go from there 27 | 2. [Build it from scratch yourself](#build-it-from-scratch) 28 | 29 | We will teach both. 30 | 31 | Video: https://www.youtube.com/watch?v=ZurrDzuurQs 32 | Blog: https://medium.com/@patrick.collins_58673/how-to-use-dapptools-code-like-makerdao-fed9909d055b 33 | 34 | https://www.youtube.com/watch?v=ZurrDzuurQs 35 | 36 | # QuickStart 37 | 38 | ## Setup 39 | 40 | 1. [Install Dapptools](#install-dapptools) and [git](https://git-scm.com/downloads) 41 | 42 | Also see the [official instructions](https://github.com/dapphub/dapptools). 43 | 44 | 2. Clone this repo 45 | 46 | ``` 47 | git clone 48 | cd dapptools-demo 49 | make # This installs the project's dependencies. 50 | ``` 51 | 52 | 3. Run tests 53 | 54 | ``` 55 | dapp test 56 | ``` 57 | 58 | 59 | # Build it from scratch 60 | 61 | We will show you how to make this exact repo from nothing. 62 | 63 | ## Install Dapptools 64 | 65 | To make sure you're using the most up-to-date install method, be sure to read the [dapptools](https://github.com/dapphub/dapptools) repo. 66 | 67 | These instructions only work for Unix based systems (For example, MacOS, Linux). For windows users, please check out [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) to run linux commands on your windows. 68 | 69 | 0. Instsall git 70 | 71 | You'll need to install [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) if you haven't already. You can run the `git --version` command to see if it's already installed, and it should print something like: `git version 2.32.0` 72 | 73 | 1. Install Nix 74 | 75 | ```bash 76 | # user must be in sudoers 77 | curl -L https://nixos.org/nix/install | sh 78 | 79 | # Run this or login again to use Nix 80 | . "$HOME/.nix-profile/etc/profile.d/nix.sh" 81 | ``` 82 | 83 | 2. Install `dapptools`: 84 | 85 | ```bash 86 | curl https://dapp.tools/install | sh 87 | ``` 88 | This configures the dapphub binary cache and installs the `dapp`, `solc`, `ethsign`, `seth` and `hevm` executables. 89 | 90 | ## Create a new dapptools project 91 | 92 | ```bash 93 | dapp init 94 | ``` 95 | 96 | This will give you a basic file layout that should look like this: 97 | 98 | ``` 99 | . 100 | ├── Makefile 101 | ├── lib 102 | │ └── ds-test 103 | │ ├── LICENSE 104 | │ ├── Makefile 105 | │ ├── default.nix 106 | │ ├── demo 107 | │ │ └── demo.sol 108 | │ └── src 109 | │ └── test.sol 110 | ├── out 111 | │ └── dapp.sol.json 112 | └── src 113 | ├── DapptoolsDemo.sol 114 | └── DapptoolsDemo.t.sol 115 | ``` 116 | 117 | `Makefile`: Where you put your "scripts". Dapptools is command line based, and our makefile helps us run large commands with a few characters. 118 | 119 | `lib`: This folder is for external dependencies, like [Openzeppelin](https://openzeppelin.com/contracts/) or [ds-test](https://github.com/dapphub/ds-test). 120 | 121 | `out`: Where your compiled code goes. Similar to the `build` folder in `brownie` or the `artifacts` folder in `hardhat`. 122 | 123 | `src`: This is where your smart contracts are. Similar to the `contracts` folder in `brownie` and `hardhat`. 124 | 125 | 126 | ## Run Tests 127 | 128 | ``` 129 | dapp test 130 | ``` 131 | and you'll see an output like: 132 | 133 | ``` 134 | Running 2 tests for src/DapptoolsDemo.t.sol:DapptoolsDemoTest 135 | [PASS] test_basic_sanity() (gas: 190) 136 | [PASS] testFail_basic_sanity() (gas: 2355) 137 | ``` 138 | 139 | ## Fuzzing 140 | 141 | Dapptools comes built in with an emphasis on [fuzzing](https://en.wikipedia.org/wiki/Fuzzing). An incredibly powerful tool for testing our contracts with random data. 142 | 143 | Let's update our `DapptoolsDemo.sol` with a function called `play`. Here is what our new file should look like: 144 | 145 | ```javascript 146 | // SPDX-License-Identifier: GPL-3.0-or-later 147 | pragma solidity ^0.8.6; 148 | 149 | contract DapptoolsDemo { 150 | 151 | function play(uint8 password) public pure returns(bool){ 152 | if(password == 55){ 153 | return false; 154 | } 155 | return true; 156 | } 157 | } 158 | ``` 159 | 160 | And we will add a new test in our `DappToolsDemo.t.sol` that is fuzzing compatible called `test_basic_fuzzing`. The file will then look like this: 161 | 162 | ```javascript 163 | // SPDX-License-Identifier: GPL-3.0-or-later 164 | pragma solidity ^0.8.6; 165 | 166 | import "ds-test/test.sol"; 167 | 168 | import "./DapptoolsDemo.sol"; 169 | 170 | contract DapptoolsDemoTest is DSTest { 171 | DapptoolsDemo demo; 172 | 173 | function setUp() public { 174 | demo = new DapptoolsDemo(); 175 | } 176 | 177 | function testFail_basic_sanity() public { 178 | assertTrue(false); 179 | } 180 | 181 | function test_basic_sanity() public { 182 | assertTrue(true); 183 | } 184 | 185 | function test_basic_fuzzing(uint8 value) public { 186 | bool response = demo.play(value); 187 | assertTrue(response); 188 | } 189 | } 190 | ``` 191 | 192 | We can now give our contract random data, and we will expect it to error out if our code gives it the number `55`. Let's run our tests now with the fuzzing flag: 193 | ```bash 194 | dapp test --fuzz-runs 1000 195 | ``` 196 | And we will see an output like: 197 | 198 | ```bash 199 | Running 3 tests for src/DapptoolsDemo.t.sol:DapptoolsDemoTest 200 | [PASS] test_basic_sanity() (gas: 190) 201 | [PASS] testFail_basic_sanity() (gas: 2355) 202 | [FAIL] test_basic_fuzzing(uint8). Counterexample: (55) 203 | Run: 204 | dapp test --replay '("test_basic_fuzzing(uint8)","0x0000000000000000000000000000000000000000000000000000000000000037")' 205 | to test this case again, or 206 | dapp debug --replay '("test_basic_fuzzing(uint8)","0x0000000000000000000000000000000000000000000000000000000000000037")' 207 | to debug it. 208 | 209 | Failure: 210 | 211 | Error: Assertion Failed 212 | ``` 213 | 214 | And our fuzzing tests picked up the outlier! 215 | 216 | ## Importing from Openzeppelin or external contracts 217 | 218 | Let's say we want to create an NFT using the Openzeppelin standard. To install external contracts or packages, we can use the `dapp install` command. We need to name the github repo organization and the repo name to install. 219 | 220 | First, we need to commit our changes so far! dapptools brings external packages in as [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules), so we need to commit first. 221 | 222 | Run: 223 | 224 | ```bash 225 | git add . 226 | git commit -m 'initial commit' 227 | ``` 228 | 229 | Then, we can install our external packages. For example, for https://github.com/OpenZeppelin/openzeppelin-contracts, we'd use: 230 | 231 | ```bash 232 | dapp install OpenZeppelin/openzeppelin-contracts 233 | ``` 234 | 235 | You should see a new folder in your `lib` folder now labeled `openzeppelin-contracts`. 236 | 237 | ### The NFT Contract 238 | 239 | Create a new file in the `src` folder called `NFT.sol`. And add this code: 240 | 241 | ```javascript 242 | // SPDX-License-Identifier: MIT 243 | pragma solidity ^0.8.0; 244 | 245 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 246 | 247 | contract NFT is ERC721 { 248 | uint256 public tokenCounter; 249 | constructor () ERC721 ("NFT", "NFT"){ 250 | tokenCounter = 0; 251 | } 252 | 253 | function createCollectible() public returns (uint256) { 254 | uint256 newItemId = tokenCounter; 255 | _safeMint(msg.sender, newItemId); 256 | tokenCounter = tokenCounter + 1; 257 | return newItemId; 258 | } 259 | } 260 | ``` 261 | If you try to `dapp build` now, you'll get a big error! 262 | 263 | ### Remappings 264 | 265 | We need to tell dapptools that `import "@openzeppelin/contracts/token/ERC721/ERC721.sol";` is pointing to our `lib` folder. So we make a file called `remappings.txt` and add: 266 | 267 | ``` 268 | @openzeppelin/=lib/openzeppelin-contracts/ 269 | ds-test/=lib/ds-test/src/ 270 | ``` 271 | 272 | Then, we make a file called `.dapprc` and add the following line: 273 | 274 | ```bash 275 | export DAPP_REMAPPINGS=$(cat remappings.txt) 276 | ``` 277 | 278 | Dapptools looks into our `.dapprc` for different configurtion variables, sort of like `hardhat.config.js` in `hardhat`. In this configuration file, we tell it to read the output of `remappings.txt` and use those as "remappings". Remappings are how we tell our imports in solidity where we should import the files from. For example in our `remapping.txt` we see: 279 | 280 | ``` 281 | @openzeppelin/=lib/openzeppelin-contracts/ 282 | ``` 283 | 284 | This means we are telling dapptools that when it compiles a file, and it sees `@openzeppelin/` in an import statement, it should look for files in `lib/openzeppelin-contracts/`. So if we do 285 | 286 | ``` 287 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 288 | ``` 289 | 290 | We are really saying: 291 | 292 | ``` 293 | import "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; 294 | ``` 295 | 296 | Then, so that we don't compile the whole library, we need to add the following to our `.dapprc` file: 297 | 298 | ``` 299 | export DAPP_LINK_TEST_LIBRARIES=0 300 | ``` 301 | 302 | This tells dapptools to not compile everything in `lib` when we run tests. 303 | 304 | 305 | ## Deploying to a testnet 306 | 307 | > Note: If you want to setup your own local network, you can run `dapp testnet`. 308 | 309 | 0. Add `.env` to your `.gitignore` file. 310 | 311 | Please do this. 312 | 313 | 1. Set an `ETH_RPC_URL` environment variable 314 | 315 | Create a file called `.env`. Then, add the http endpoint for you blockchain of choice to an environment variable named `ETH_RPC_URL`. You can get a free 3rd party http endpoint from [Alchemy](https://alchemy.com/?a=673c802981). 316 | 317 | If you're new to networks, I'd recommend getting a kovan ETH http endpoint. 318 | 319 | For example, your `.env` might look like: 320 | 321 | ``` 322 | export ETH_RPC_URL=http://alchemy.io/adfsasdfasdf 323 | ``` 324 | 325 | 2. Create a default sender 326 | 327 | Get an [eth wallet](https://metamask.io/) if you haven't already. You can [see some instructions here](https://docs.chain.link/docs/deploy-your-first-contract/#install-and-fund-your-metamask-wallet). 328 | 329 | Once you have a wallet, set the address of that wallet as a `ETH_FROM` environment variable. 330 | 331 | ``` 332 | export ETH_FROM=YOUR_DEFAULT_SENDER_ACCOUNT 333 | ``` 334 | 335 | Additionally, if using Kovan, [fund your wallet with testnet ETH](https://faucets.chain.link/). 336 | 337 | 3. Add your private key 338 | 339 | > NOTE: I HIGHLY RECOMMEND USING A METAMASK THAT DOESNT HAVE ANY REAL MONEY IN IT FOR DEVELOPMENT. 340 | > If you push your private key to github with real money in it, people can steal your funds. 341 | 342 | Dapptools comes with a tool called `ethsign`, and this is where we are going to store and encrypt our key. To add our private key (needed to send transactions) get the private key of your wallet, and run: 343 | 344 | ``` 345 | ethsign import 346 | ``` 347 | 348 | Then add your private key, and a password to encrypt it. This encrypts your private key in `ethsign`. You'll need your password anytime you want to send a transaction moving forward. 349 | 350 | 4. Update our `Makefile` 351 | 352 | The command we can use to deploy our contracts is `dapp create DapptoolsDemo` and then some flags to add in environment variables. To make our lives easier, we can add our deploy command to a Makefile, and just tell the Makefile to use our environment variables. 353 | 354 | Add the following to our `Makefile` 355 | 356 | ``` 357 | -include .env 358 | ``` 359 | 360 | 5. Deploy the contract! 361 | 362 | In our `Makefile`, we have a command called `deploy`, this will run `dapp create DapptoolsDemo` and include our environment variables. To run it, just run: 363 | 364 | ``` 365 | make deploy 366 | ``` 367 | 368 | And you'll be prompted for you password. After, it'll deploy your contract! 369 | 370 | ``` 371 | dapp create DapptoolsDemo 372 | ++ seth send --create 608060405234801561001057600080fd5b50610158806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c806353a04b0514610030575b600080fd5b61004a60048036038101906100459190610096565b610060565b60405161005791906100d2565b60405180910390f35b600060378260ff161415610077576000905061007c565b600190505b919050565b6000813590506100908161010b565b92915050565b6000602082840312156100ac576100ab610106565b5b60006100ba84828501610081565b91505092915050565b6100cc816100ed565b82525050565b60006020820190506100e760008301846100c3565b92915050565b60008115159050919050565b600060ff82169050919050565b600080fd5b610114816100f9565b811461011f57600080fd5b5056fea2646970667358221220b1f0848a7865f42db510abbc5322e1c1bdcdcdc59100106a5b1e01590f92655464736f6c63430008060033 'DapptoolsDemo()' 373 | seth-send: warning: `ETH_GAS' not set; using default gas amount 374 | Ethereum account passphrase (not echoed): seth-send: Published transaction with 376 bytes of calldata. 375 | seth-send: 0xa91d9ee86311eea260e1999b537fc05a941988fc28da63982d00d755904cd902 376 | seth-send: Waiting for transaction receipt.......... 377 | seth-send: Transaction included in block 29244645. 378 | 0xf8bEca9A4CC470d72387E03B801f36623141A4C5 379 | ``` 380 | 381 | And you can see it on Etherscan. 382 | 383 | ### Interacting with contracts 384 | 385 | To interact with deployed contracts, we can use `seth call` and `seth send`. 386 | 387 | To *read* data from the blockchain, we could do something like: 388 | 389 | ``` 390 | ETH_RPC_URL= seth call "FUNCTION_NAME()" 391 | ``` 392 | 393 | Like: 394 | 395 | ``` 396 | ETH_RPC_URL= seth call 0x12345 "play(uint8)" 55 397 | ``` 398 | 399 | To which you'll get `0x0000000000000000000000000000000000000000000000000000000000000000` which means false. 400 | 401 | To *write* data to the blockchain, we could do something like: 402 | 403 | ``` 404 | ETH_RPC_URL= ETH_FROM= seth send "FUNCTION_NAME()" 405 | ``` 406 | 407 | 408 | ## Verify your contract on Etherscan 409 | 410 | After you've deployed a contract to etherscan, you can verify it by: 411 | 412 | 1. Getting an [Etherscan API Key](https://etherscan.io/apis). 413 | 414 | 2. Then running 415 | 416 | 417 | ``` 418 | ETHERSCAN_API_KEY= dapp verify-contract /: 419 | ``` 420 | 421 | For example: 422 | 423 | ``` 424 | ETHERSCAN_API_KEY=123456765 dapp verify-contract ./src/DapptoolsDemo.sol:DapptoolsDemo 0x23456534212536435424 425 | ``` 426 | 427 | ## And finally... 428 | 429 | 1. Add `cache` to your `.gitignore` 430 | 2. Add `update:; dapp update` to your `Makefile` 431 | 1. This will update and download the files in `.gitmodules` and `lib` 432 | 3. Add a LICENSE 433 | 1. You can just copy this one if you don't know how! 434 | 435 | And you're done! 436 | 437 | # Resources 438 | 439 | If you liked this, consider donating! 440 | 💸 ETH Wallet address: 0x9680201d9c93d65a3603d2088d125e955c73BD65 441 | 442 | * [DappTools](https://dapp.tools) 443 | * [Hevm Docs](https://github.com/dapphub/dapptools/blob/master/src/hevm/README.md) 444 | * [Dapp Docs](https://github.com/dapphub/dapptools/tree/master/src/dapp/README.md) 445 | * [Seth Docs](https://github.com/dapphub/dapptools/tree/master/src/seth/README.md) 446 | * [DappTools Overview](https://www.youtube.com/watch?v=ZurrDzuurQs) 447 | * [Chainlink](https://docs.chain.link) 448 | 449 | 450 | 451 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=lib/openzeppelin-contracts/ 2 | ds-test/=lib/ds-test/src/ 3 | -------------------------------------------------------------------------------- /src/DapptoolsDemo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity ^0.8.6; 3 | 4 | contract DapptoolsDemo { 5 | 6 | function play(uint8 password) public pure returns(bool){ 7 | if(password == 55){ 8 | return false; 9 | } 10 | return true; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/DapptoolsDemo.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity ^0.8.6; 3 | 4 | import "ds-test/test.sol"; 5 | 6 | import "./DapptoolsDemo.sol"; 7 | 8 | contract DapptoolsDemoTest is DSTest { 9 | DapptoolsDemo demo; 10 | 11 | function setUp() public { 12 | demo = new DapptoolsDemo(); 13 | } 14 | 15 | function testFail_basic_sanity() public { 16 | assertTrue(false); 17 | } 18 | 19 | function test_basic_sanity() public { 20 | assertTrue(true); 21 | } 22 | 23 | function test_basic_fuzzing(uint8 value) public { 24 | bool response = demo.play(value); 25 | // assertTrue(response); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/NFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | 6 | contract NFT is ERC721 { 7 | uint256 public tokenCounter; 8 | constructor () ERC721 ("NFT", "NFT"){ 9 | tokenCounter = 0; 10 | } 11 | 12 | function createCollectible() public returns (uint256) { 13 | uint256 newItemId = tokenCounter; 14 | _safeMint(msg.sender, newItemId); 15 | tokenCounter = tokenCounter + 1; 16 | return newItemId; 17 | } 18 | 19 | } 20 | --------------------------------------------------------------------------------