├── .editorconfig ├── .env.example ├── .github └── workflows │ ├── ci.yml │ └── use-template.yml ├── .gitignore ├── .gitpod.yml ├── .prettierignore ├── .prettierrc.yml ├── .solhint.json ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── bun.lockb ├── foundry.toml ├── package.json ├── remappings.txt ├── script ├── Base.s.sol └── Deploy.s.sol ├── src └── Foo.sol └── test └── Foo.t.sol /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # All files 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.sol] 16 | indent_size = 4 17 | 18 | [*.tree] 19 | indent_size = 1 20 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | export API_KEY_ALCHEMY="YOUR_API_KEY_ALCHEMY" 2 | export API_KEY_ARBISCAN="YOUR_API_KEY_ARBISCAN" 3 | export API_KEY_BSCSCAN="YOUR_API_KEY_BSCSCAN" 4 | export API_KEY_ETHERSCAN="YOUR_API_KEY_ETHERSCAN" 5 | export API_KEY_GNOSISSCAN="YOUR_API_KEY_GNOSISSCAN" 6 | export API_KEY_INFURA="YOUR_API_KEY_INFURA" 7 | export API_KEY_OPTIMISTIC_ETHERSCAN="YOUR_API_KEY_OPTIMISTIC_ETHERSCAN" 8 | export API_KEY_POLYGONSCAN="YOUR_API_KEY_POLYGONSCAN" 9 | export API_KEY_SNOWTRACE="YOUR_API_KEY_SNOWTRACE" 10 | export MNEMONIC="YOUR_MNEMONIC" 11 | export FOUNDRY_PROFILE="default" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | env: 4 | API_KEY_ALCHEMY: ${{ secrets.API_KEY_ALCHEMY }} 5 | FOUNDRY_PROFILE: "ci" 6 | 7 | on: 8 | workflow_dispatch: 9 | pull_request: 10 | push: 11 | branches: 12 | - "main" 13 | 14 | jobs: 15 | lint: 16 | runs-on: "ubuntu-latest" 17 | steps: 18 | - name: "Check out the repo" 19 | uses: "actions/checkout@v4" 20 | 21 | - name: "Install Foundry" 22 | uses: "foundry-rs/foundry-toolchain@v1" 23 | 24 | - name: "Install Bun" 25 | uses: "oven-sh/setup-bun@v1" 26 | 27 | - name: "Install the Node.js dependencies" 28 | run: "bun install" 29 | 30 | - name: "Lint the code" 31 | run: "bun run lint" 32 | 33 | - name: "Add lint summary" 34 | run: | 35 | echo "## Lint result" >> $GITHUB_STEP_SUMMARY 36 | echo "✅ Passed" >> $GITHUB_STEP_SUMMARY 37 | 38 | build: 39 | runs-on: "ubuntu-latest" 40 | steps: 41 | - name: "Check out the repo" 42 | uses: "actions/checkout@v4" 43 | 44 | - name: "Install Foundry" 45 | uses: "foundry-rs/foundry-toolchain@v1" 46 | 47 | - name: "Install Bun" 48 | uses: "oven-sh/setup-bun@v1" 49 | 50 | - name: "Install the Node.js dependencies" 51 | run: "bun install" 52 | 53 | - name: "Build the contracts and print their size" 54 | run: "forge build --sizes" 55 | 56 | - name: "Add build summary" 57 | run: | 58 | echo "## Build result" >> $GITHUB_STEP_SUMMARY 59 | echo "✅ Passed" >> $GITHUB_STEP_SUMMARY 60 | 61 | test: 62 | needs: ["lint", "build"] 63 | runs-on: "ubuntu-latest" 64 | steps: 65 | - name: "Check out the repo" 66 | uses: "actions/checkout@v4" 67 | 68 | - name: "Install Foundry" 69 | uses: "foundry-rs/foundry-toolchain@v1" 70 | 71 | - name: "Install Bun" 72 | uses: "oven-sh/setup-bun@v1" 73 | 74 | - name: "Install the Node.js dependencies" 75 | run: "bun install" 76 | 77 | - name: "Show the Foundry config" 78 | run: "forge config" 79 | 80 | - name: "Generate a fuzz seed that changes weekly to avoid burning through RPC allowance" 81 | run: > 82 | echo "FOUNDRY_FUZZ_SEED=$( 83 | echo $(($EPOCHSECONDS - $EPOCHSECONDS % 604800)) 84 | )" >> $GITHUB_ENV 85 | 86 | - name: "Run the tests" 87 | run: "forge test" 88 | 89 | - name: "Add test summary" 90 | run: | 91 | echo "## Tests result" >> $GITHUB_STEP_SUMMARY 92 | echo "✅ Passed" >> $GITHUB_STEP_SUMMARY 93 | -------------------------------------------------------------------------------- /.github/workflows/use-template.yml: -------------------------------------------------------------------------------- 1 | name: "Create" 2 | 3 | # The workflow will run when the "Use this template" button is used 4 | on: 5 | push: 6 | 7 | jobs: 8 | create: 9 | # We only run this action when the repository isn't the template repository. References: 10 | # - https://docs.github.com/en/actions/learn-github-actions/contexts 11 | # - https://docs.github.com/en/actions/learn-github-actions/expressions 12 | if: ${{ !github.event.repository.is_template }} 13 | permissions: "write-all" 14 | runs-on: "ubuntu-latest" 15 | steps: 16 | - name: "Check out the repo" 17 | uses: "actions/checkout@v4" 18 | 19 | - name: "Update package.json" 20 | env: 21 | GITHUB_REPOSITORY_DESCRIPTION: ${{ github.event.repository.description }} 22 | run: 23 | ./.github/scripts/rename.sh "$GITHUB_REPOSITORY" "$GITHUB_REPOSITORY_OWNER" "$GITHUB_REPOSITORY_DESCRIPTION" 24 | 25 | - name: "Add rename summary" 26 | run: | 27 | echo "## Commit result" >> $GITHUB_STEP_SUMMARY 28 | echo "✅ Passed" >> $GITHUB_STEP_SUMMARY 29 | 30 | - name: "Remove files not needed in the user's copy of the template" 31 | run: | 32 | rm -f "./.github/FUNDING.yml" 33 | rm -f "./.github/scripts/rename.sh" 34 | rm -f "./.github/workflows/create.yml" 35 | 36 | - name: "Add remove summary" 37 | run: | 38 | echo "## Remove result" >> $GITHUB_STEP_SUMMARY 39 | echo "✅ Passed" >> $GITHUB_STEP_SUMMARY 40 | 41 | - name: "Update commit" 42 | uses: "stefanzweifel/git-auto-commit-action@v4" 43 | with: 44 | commit_message: "feat: initial commit" 45 | commit_options: "--amend" 46 | push_options: "--force" 47 | skip_fetch: true 48 | 49 | - name: "Add commit summary" 50 | run: | 51 | echo "## Commit result" >> $GITHUB_STEP_SUMMARY 52 | echo "✅ Passed" >> $GITHUB_STEP_SUMMARY 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # directories 2 | cache 3 | coverage 4 | node_modules 5 | out 6 | 7 | # files 8 | *.env 9 | *.log 10 | .DS_Store 11 | .pnp.* 12 | lcov.info 13 | package-lock.json 14 | pnpm-lock.yaml 15 | yarn.lock 16 | 17 | # broadcasts 18 | !broadcast 19 | broadcast/* 20 | broadcast/*/31337/ 21 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: "gitpod/workspace-bun" 2 | 3 | tasks: 4 | - name: "Install dependencies" 5 | before: | 6 | curl -L https://foundry.paradigm.xyz | bash 7 | source ~/.bashrc 8 | foundryup 9 | init: "bun install" 10 | 11 | vscode: 12 | extensions: 13 | - "esbenp.prettier-vscode" 14 | - "NomicFoundation.hardhat-solidity" 15 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # directories 2 | broadcast 3 | cache 4 | coverage 5 | node_modules 6 | out 7 | 8 | # files 9 | *.env 10 | *.log 11 | .DS_Store 12 | .pnp.* 13 | bun.lockb 14 | lcov.info 15 | package-lock.json 16 | pnpm-lock.yaml 17 | yarn.lock 18 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | bracketSpacing: true 2 | printWidth: 120 3 | proseWrap: "always" 4 | singleQuote: false 5 | tabWidth: 2 6 | trailingComma: "all" 7 | useTabs: false 8 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "code-complexity": ["error", 8], 5 | "compiler-version": ["error", ">=0.8.25"], 6 | "func-name-mixedcase": "off", 7 | "func-visibility": ["error", { "ignoreConstructors": true }], 8 | "max-line-length": ["error", 120], 9 | "named-parameters-mapping": "warn", 10 | "no-console": "off", 11 | "not-rely-on-time": "off", 12 | "one-contract-per-file": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[solidity]": { 3 | "editor.defaultFormatter": "NomicFoundation.hardhat-solidity" 4 | }, 5 | "[toml]": { 6 | "editor.defaultFormatter": "tamasfe.even-better-toml" 7 | }, 8 | "solidity.formatter": "forge" 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Paul Razvan Berg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 8 | persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 11 | Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Foundry Template [![Open in Gitpod][gitpod-badge]][gitpod] [![Github Actions][gha-badge]][gha] [![Foundry][foundry-badge]][foundry] [![License: MIT][license-badge]][license] 2 | 3 | [gitpod]: https://gitpod.io/#https://github.com/unionmini/foundry-template 4 | [gitpod-badge]: https://img.shields.io/badge/Gitpod-Open%20in%20Gitpod-FFB45B?logo=gitpod 5 | [gha]: https://github.com/unionmini/foundry-template/actions 6 | [gha-badge]: https://github.com/unionmini/foundry-template/actions/workflows/ci.yml/badge.svg 7 | [foundry]: https://getfoundry.sh/ 8 | [foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg 9 | [license]: https://opensource.org/licenses/MIT 10 | [license-badge]: https://img.shields.io/badge/License-MIT-blue.svg 11 | 12 | A Foundry-based template for developing Solidity smart contracts, with sensible defaults. 13 | 14 | ## What's Inside 15 | 16 | - [Forge](https://github.com/foundry-rs/foundry/blob/master/forge): compile, test, fuzz, format, and deploy smart 17 | contracts 18 | - [Forge Std](https://github.com/foundry-rs/forge-std): collection of helpful contracts and utilities for testing 19 | - [Prettier](https://github.com/prettier/prettier): code formatter for non-Solidity files 20 | - [Solhint](https://github.com/protofire/solhint): linter for Solidity code 21 | 22 | ## Getting Started 23 | 24 | Click the [`Use this template`](https://github.com/PaulRBerg/foundry-template/generate) button at the top of the page to 25 | create a new repository with this repo as the initial state. 26 | 27 | Or, if you prefer to install the template manually: 28 | 29 | ```sh 30 | $ forge init --template PaulRBerg/foundry-template my-project 31 | $ cd my-project 32 | $ bun install # install Solhint, Prettier, and other Node.js deps 33 | ``` 34 | 35 | If this is your first time with Foundry, check out the 36 | [installation](https://github.com/foundry-rs/foundry#installation) instructions. 37 | 38 | ## Features 39 | 40 | This template builds upon the frameworks and libraries mentioned above, so please consult their respective documentation 41 | for details about their specific features. 42 | 43 | For example, if you're interested in exploring Foundry in more detail, you should look at the 44 | [Foundry Book](https://book.getfoundry.sh/). In particular, you may be interested in reading the 45 | [Writing Tests](https://book.getfoundry.sh/forge/writing-tests.html) tutorial. 46 | 47 | ### Sensible Defaults 48 | 49 | This template comes with a set of sensible default configurations for you to use. These defaults can be found in the 50 | following files: 51 | 52 | ```text 53 | ├── .editorconfig 54 | ├── .gitignore 55 | ├── .prettierignore 56 | ├── .prettierrc.yml 57 | ├── .solhint.json 58 | ├── foundry.toml 59 | └── remappings.txt 60 | ``` 61 | 62 | ### VSCode Integration 63 | 64 | This template is IDE agnostic, but for the best user experience, you may want to use it in VSCode alongside Nomic 65 | Foundation's [Solidity extension](https://marketplace.visualstudio.com/items?itemName=NomicFoundation.hardhat-solidity). 66 | 67 | For guidance on how to integrate a Foundry project in VSCode, please refer to this 68 | [guide](https://book.getfoundry.sh/config/vscode). 69 | 70 | ### GitHub Actions 71 | 72 | This template comes with GitHub Actions pre-configured. Your contracts will be linted and tested on every push and pull 73 | request made to the `main` branch. 74 | 75 | You can edit the CI script in [.github/workflows/ci.yml](./.github/workflows/ci.yml). 76 | 77 | ## Installing Dependencies 78 | 79 | Foundry typically uses git submodules to manage dependencies, but this template uses Node.js packages because 80 | [submodules don't scale](https://twitter.com/PaulRBerg/status/1736695487057531328). 81 | 82 | This is how to install dependencies: 83 | 84 | 1. Install the dependency using your preferred package manager, e.g. `bun install dependency-name` 85 | - Use this syntax to install from GitHub: `bun install github:username/repo-name` 86 | 2. Add a remapping for the dependency in [remappings.txt](./remappings.txt), e.g. 87 | `dependency-name=node_modules/dependency-name` 88 | 89 | Note that OpenZeppelin Contracts is pre-installed, so you can follow that as an example. 90 | 91 | ## Writing Tests 92 | 93 | To write a new test contract, you start by importing `Test` from `forge-std`, and then you inherit it in your test 94 | contract. Forge Std comes with a pre-instantiated [cheatcodes](https://book.getfoundry.sh/cheatcodes/) environment 95 | accessible via the `vm` property. If you would like to view the logs in the terminal output, you can add the `-vvv` flag 96 | and use [console.log](https://book.getfoundry.sh/faq?highlight=console.log#how-do-i-use-consolelog). 97 | 98 | This template comes with an example test contract [Foo.t.sol](./test/Foo.t.sol) 99 | 100 | ## Usage 101 | 102 | This is a list of the most frequently needed commands. 103 | 104 | ### Build 105 | 106 | Build the contracts: 107 | 108 | ```sh 109 | $ forge build 110 | ``` 111 | 112 | ### Clean 113 | 114 | Delete the build artifacts and cache directories: 115 | 116 | ```sh 117 | $ forge clean 118 | ``` 119 | 120 | ### Compile 121 | 122 | Compile the contracts: 123 | 124 | ```sh 125 | $ forge build 126 | ``` 127 | 128 | ### Coverage 129 | 130 | Get a test coverage report: 131 | 132 | ```sh 133 | $ forge coverage 134 | ``` 135 | 136 | ### Deploy 137 | 138 | Deploy to Anvil: 139 | 140 | ```sh 141 | $ forge script script/Deploy.s.sol --broadcast --fork-url http://localhost:8545 142 | ``` 143 | 144 | For this script to work, you need to have a `MNEMONIC` environment variable set to a valid 145 | [BIP39 mnemonic](https://iancoleman.io/bip39/). 146 | 147 | For instructions on how to deploy to a testnet or mainnet, check out the 148 | [Solidity Scripting](https://book.getfoundry.sh/tutorials/solidity-scripting.html) tutorial. 149 | 150 | ### Format 151 | 152 | Format the contracts: 153 | 154 | ```sh 155 | $ forge fmt 156 | ``` 157 | 158 | ### Gas Usage 159 | 160 | Get a gas report: 161 | 162 | ```sh 163 | $ forge test --gas-report 164 | ``` 165 | 166 | ### Lint 167 | 168 | Lint the contracts: 169 | 170 | ```sh 171 | $ bun run lint 172 | ``` 173 | 174 | ### Test 175 | 176 | Run the tests: 177 | 178 | ```sh 179 | $ forge test 180 | ``` 181 | 182 | Generate test coverage and output result to the terminal: 183 | 184 | ```sh 185 | $ bun run test:coverage 186 | ``` 187 | 188 | Generate test coverage with lcov report (you'll have to open the `./coverage/index.html` file in your browser, to do so 189 | simply copy paste the path): 190 | 191 | ```sh 192 | $ bun run test:coverage:report 193 | ``` 194 | 195 | ## Related Efforts 196 | 197 | - [abigger87/femplate](https://github.com/abigger87/femplate) 198 | - [cleanunicorn/ethereum-smartcontract-template](https://github.com/cleanunicorn/ethereum-smartcontract-template) 199 | - [foundry-rs/forge-template](https://github.com/foundry-rs/forge-template) 200 | - [FrankieIsLost/forge-template](https://github.com/FrankieIsLost/forge-template) 201 | 202 | ## License 203 | 204 | This project is licensed under MIT. 205 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unionmini/foundry-template/e26538d5db702fd229d96f4b7feb4a8b454d31c4/bun.lockb -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | # Full reference https://github.com/foundry-rs/foundry/tree/master/crates/config 2 | 3 | [profile.default] 4 | auto_detect_solc = false 5 | block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT 6 | bytecode_hash = "none" 7 | evm_version = "shanghai" 8 | fuzz = { runs = 1_000 } 9 | gas_reports = ["*"] 10 | optimizer = true 11 | optimizer_runs = 10_000 12 | out = "out" 13 | script = "script" 14 | solc = "0.8.25" 15 | src = "src" 16 | test = "test" 17 | 18 | [profile.ci] 19 | fuzz = { runs = 10_000 } 20 | verbosity = 4 21 | 22 | [etherscan] 23 | arbitrum = { key = "${API_KEY_ARBISCAN}" } 24 | avalanche = { key = "${API_KEY_SNOWTRACE}" } 25 | base = { key = "${API_KEY_BASESCAN}" } 26 | bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" } 27 | gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" } 28 | goerli = { key = "${API_KEY_ETHERSCAN}" } 29 | mainnet = { key = "${API_KEY_ETHERSCAN}" } 30 | optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" } 31 | polygon = { key = "${API_KEY_POLYGONSCAN}" } 32 | sepolia = { key = "${API_KEY_ETHERSCAN}" } 33 | 34 | [fmt] 35 | bracket_spacing = true 36 | int_types = "long" 37 | line_length = 120 38 | multiline_func_header = "all" 39 | number_underscore = "thousands" 40 | quote_style = "double" 41 | tab_width = 4 42 | wrap_comments = true 43 | 44 | [rpc_endpoints] 45 | arbitrum = "https://arbitrum-mainnet.infura.io/v3/${API_KEY_INFURA}" 46 | avalanche = "https://avalanche-mainnet.infura.io/v3/${API_KEY_INFURA}" 47 | base = "https://mainnet.base.org" 48 | bnb_smart_chain = "https://bsc-dataseed.binance.org" 49 | gnosis_chain = "https://rpc.gnosischain.com" 50 | goerli = "https://goerli.infura.io/v3/${API_KEY_INFURA}" 51 | localhost = "http://localhost:8545" 52 | mainnet = "https://eth-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}" 53 | optimism = "https://optimism-mainnet.infura.io/v3/${API_KEY_INFURA}" 54 | polygon = "https://polygon-mainnet.infura.io/v3/${API_KEY_INFURA}" 55 | sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}" 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@unionmini/foundry-template", 3 | "description": "", 4 | "version": "1.0.0", 5 | "author": { 6 | "name": "unionmini", 7 | "url": "https://github.com/unionmini" 8 | }, 9 | "dependencies": { 10 | "@openzeppelin/contracts": "^5.0.1" 11 | }, 12 | "devDependencies": { 13 | "forge-std": "github:foundry-rs/forge-std#v1.8.1", 14 | "prettier": "^3.0.0", 15 | "solhint": "^3.6.2" 16 | }, 17 | "keywords": [ 18 | "blockchain", 19 | "ethereum", 20 | "forge", 21 | "foundry", 22 | "smart-contracts", 23 | "solidity", 24 | "template" 25 | ], 26 | "private": true, 27 | "scripts": { 28 | "clean": "rm -rf cache out", 29 | "build": "forge build", 30 | "lint": "bun run lint:sol && bun run prettier:check", 31 | "lint:sol": "forge fmt --check && bun solhint \"{script,src,test}/**/*.sol\"", 32 | "prettier:check": "prettier --check \"**/*.{json,md,yml}\" --ignore-path \".prettierignore\"", 33 | "prettier:write": "prettier --write \"**/*.{json,md,yml}\" --ignore-path \".prettierignore\"", 34 | "test": "forge test", 35 | "test:coverage": "forge coverage", 36 | "test:coverage:report": "forge coverage --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ 2 | forge-std/=node_modules/forge-std/ 3 | -------------------------------------------------------------------------------- /script/Base.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.25 <0.9.0; 3 | 4 | import { Script } from "forge-std/src/Script.sol"; 5 | 6 | abstract contract BaseScript is Script { 7 | /// @dev Included to enable compilation of the script without a $MNEMONIC environment variable. 8 | string internal constant TEST_MNEMONIC = "test test test test test test test test test test test junk"; 9 | 10 | /// @dev Needed for the deterministic deployments. 11 | bytes32 internal constant ZERO_SALT = bytes32(0); 12 | 13 | /// @dev The address of the transaction broadcaster. 14 | address internal broadcaster; 15 | 16 | /// @dev Used to derive the broadcaster's address if $ETH_FROM is not defined. 17 | string internal mnemonic; 18 | 19 | /// @dev Initializes the transaction broadcaster like this: 20 | /// 21 | /// - If $ETH_FROM is defined, use it. 22 | /// - Otherwise, derive the broadcaster address from $MNEMONIC. 23 | /// - If $MNEMONIC is not defined, default to a test mnemonic. 24 | /// 25 | /// The use case for $ETH_FROM is to specify the broadcaster key and its address via the command line. 26 | constructor() { 27 | address from = vm.envOr({ name: "ETH_FROM", defaultValue: address(0) }); 28 | if (from != address(0)) { 29 | broadcaster = from; 30 | } else { 31 | mnemonic = vm.envOr({ name: "MNEMONIC", defaultValue: TEST_MNEMONIC }); 32 | (broadcaster,) = deriveRememberKey({ mnemonic: mnemonic, index: 0 }); 33 | } 34 | } 35 | 36 | modifier broadcast() { 37 | vm.startBroadcast(broadcaster); 38 | _; 39 | vm.stopBroadcast(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /script/Deploy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.25 <0.9.0; 3 | 4 | import { Foo } from "../src/Foo.sol"; 5 | 6 | import { BaseScript } from "./Base.s.sol"; 7 | 8 | /// @dev See the Solidity Scripting tutorial: https://book.getfoundry.sh/tutorials/solidity-scripting 9 | contract Deploy is BaseScript { 10 | function run() public broadcast returns (Foo foo) { 11 | foo = new Foo(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Foo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.25; 3 | 4 | contract Foo { 5 | function id(uint256 value) external pure returns (uint256) { 6 | return value; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/Foo.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.25 <0.9.0; 3 | 4 | import { Test } from "forge-std/src/Test.sol"; 5 | import { console2 } from "forge-std/src/console2.sol"; 6 | 7 | import { Foo } from "../src/Foo.sol"; 8 | 9 | interface IERC20 { 10 | function balanceOf(address account) external view returns (uint256); 11 | } 12 | 13 | /// @dev If this is your first time with Forge, read this tutorial in the Foundry Book: 14 | /// https://book.getfoundry.sh/forge/writing-tests 15 | contract FooTest is Test { 16 | Foo internal foo; 17 | 18 | /// @dev A function invoked before each test case is run. 19 | function setUp() public virtual { 20 | // Instantiate the contract-under-test. 21 | foo = new Foo(); 22 | } 23 | 24 | /// @dev Basic test. Run it with `forge test -vvv` to see the console log. 25 | function test_Example() external view { 26 | console2.log("Hello World"); 27 | uint256 x = 42; 28 | assertEq(foo.id(x), x, "value mismatch"); 29 | } 30 | 31 | /// @dev Fuzz test that provides random values for an unsigned integer, but which rejects zero as an input. 32 | /// If you need more sophisticated input validation, you should use the `bound` utility instead. 33 | /// See https://twitter.com/PaulRBerg/status/1622558791685242880 34 | function testFuzz_Example(uint256 x) external view { 35 | vm.assume(x != 0); // or x = bound(x, 1, 100) 36 | assertEq(foo.id(x), x, "value mismatch"); 37 | } 38 | 39 | /// @dev Fork test that runs against an Ethereum Mainnet fork. For this to work, you need to set `API_KEY_ALCHEMY` 40 | /// in your environment You can get an API key for free at https://alchemy.com. 41 | function testFork_Example() external { 42 | // Silently pass this test if there is no API key. 43 | string memory alchemyApiKey = vm.envOr("API_KEY_ALCHEMY", string("")); 44 | if (bytes(alchemyApiKey).length == 0) { 45 | return; 46 | } 47 | 48 | // Otherwise, run the test against the mainnet fork. 49 | vm.createSelectFork({ urlOrAlias: "mainnet", blockNumber: 16_428_000 }); 50 | address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 51 | address holder = 0x7713974908Be4BEd47172370115e8b1219F4A5f0; 52 | uint256 actualBalance = IERC20(usdc).balanceOf(holder); 53 | uint256 expectedBalance = 196_307_713.810457e6; 54 | assertEq(actualBalance, expectedBalance); 55 | } 56 | } 57 | --------------------------------------------------------------------------------