diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..442e735 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +export API_KEY_ALCHEMY="YOUR_API_KEY_ALCHEMY" +export API_KEY_ARBISCAN="YOUR_API_KEY_ARBISCAN" +export API_KEY_BSCSCAN="YOUR_API_KEY_BSCSCAN" +export API_KEY_ETHERSCAN="YOUR_API_KEY_ETHERSCAN" +export API_KEY_GNOSISSCAN="YOUR_API_KEY_GNOSISSCAN" +export API_KEY_INFURA="YOUR_API_KEY_INFURA" +export API_KEY_OPTIMISTIC_ETHERSCAN="YOUR_API_KEY_OPTIMISTIC_ETHERSCAN" +export API_KEY_POLYGONSCAN="YOUR_API_KEY_POLYGONSCAN" +export API_KEY_SNOWTRACE="YOUR_API_KEY_SNOWTRACE" +export MNEMONIC="YOUR_MNEMONIC" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d5e5d27 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +lib/** linguist-vendored diff --git a/.github/scripts/setup-packagejson.sh b/.github/scripts/setup-packagejson.sh new file mode 100755 index 0000000..37c060a --- /dev/null +++ b/.github/scripts/setup-packagejson.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# https://gist.github.com/vncsna/64825d5609c146e80de8b1fd623011ca +set -euo pipefail + +# Define the input vars +GITHUB_REPOSITORY=${1?Error: Please pass username/repo, e.g. prb/foundry-template} +GITHUB_REPOSITORY_OWNER=${2?Error: Please pass username, e.g. prb} +GITHUB_REPOSITORY_DESCRIPTION=${3:-""} # If null then replace with empty string + +echo "GITHUB_REPOSITORY: $GITHUB_REPOSITORY" +echo "GITHUB_REPOSITORY_OWNER: $GITHUB_REPOSITORY_OWNER" +echo "GITHUB_REPOSITORY_DESCRIPTION: $GITHUB_REPOSITORY_DESCRIPTION" + +# Create a new package.json file with the new values +JQ_OUTPUT_PACKAGE=$( + jq \ + --arg NAME "@$GITHUB_REPOSITORY" \ + --arg AUTHOR "$GITHUB_REPOSITORY_OWNER https://github.com/$GITHUB_REPOSITORY_OWNER" \ + --arg REPOSITORY "github:@$GITHUB_REPOSITORY" \ + --arg BUGS "https://github.com/$GITHUB_REPOSITORY/issues" \ + --arg HOMEPAGE "https://github.com/$GITHUB_REPOSITORY#readme" \ + --arg VERSION "0.0.1" \ + --arg DESCRIPTION "$GITHUB_REPOSITORY_DESCRIPTION" \ + '.name = $NAME | .author = $AUTHOR | .repository = $REPOSITORY | + .bugs = $BUGS | .homepage = $HOMEPAGE | .version = $VERSION | .description = $DESCRIPTION' \ + package.json +) + +# Create a new package-lock.json file with the new values +JQ_OUTPUTPACKAGE_LOCK=$( + jq \ + --arg VERSION "0.0.1" \ + '.version = $VERSION | .packages."".version = $VERSION' \ + package-lock.json +) + +# Save the new version of the package.json and package-lock.json files +echo "$JQ_OUTPUT_PACKAGE" >package.json +echo "$JQ_OUTPUTPACKAGE_LOCK" >package-lock.json + +# Make sed command compatible in both Mac and Linux environments +# Reference: https://stackoverflow.com/a/38595160/8696958 +sedi() { + sed --version >/dev/null 2>&1 && sed -i -- "$@" || sed -i "" "$@" +} + +# Rename instances of "0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry" to the new repo name in README.md for badges only +sedi "/github-editor-url/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" +sedi "/gha-quality-url/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" +sedi "/gha-quality-badge/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" +sedi "/gha-test-url/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" +sedi "/gha-test-badge/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" +sedi "/gha-static-analysis-url/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" +sedi "/gha-static-analysis-badge/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" +sedi "/gha-release-url/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" +sedi "/gha-release-badge/ s|0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry|"${GITHUB_REPOSITORY}"|;" "README.md" diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml new file mode 100644 index 0000000..9e52522 --- /dev/null +++ b/.github/workflows/quality-checks.yml @@ -0,0 +1,59 @@ +name: Quality checks + +env: + FOUNDRY_PROFILE: "ci" + +# This CI workflow is responsible of running the linter and building the contracts. +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: lts/* + + - name: Install the Node.js dependencies + run: npm ci + + - name: Run the linter and the formatter in check mode + run: make quality + + - name: Add lint summary + run: | + echo "## Lint result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + build: + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Compile the contracts and print their size + run: make compile-s + + - name: Add compile summary + run: | + echo "## Build result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml new file mode 100644 index 0000000..3e26825 --- /dev/null +++ b/.github/workflows/release-package.yml @@ -0,0 +1,48 @@ +name: Release on tag + +# Trigger the create release workflow when a new tag +# respecting the pattern `v*` is pushed to the repository and deploy +# the new version of the documentation on the `gh-pages` branch. +on: + push: + tags: + - "v*" + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Create Release + id: create_release + uses: "marvinpinto/action-automatic-releases@v1.2.1" + with: + # TODO: YOU MUST CONFIGURE THIS TOKEN BY OURSEFL. CHECK THE README FOR MORE INFORMATION. + repo_token: "${{ secrets.RELEASE_TOKEN }}" + prerelease: false + + deploy-doc: + needs: ["release"] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Check out the repo + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Build the documentation + run: make doc + + - name: Deploy the documentation + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: docs/book + branch: gh-pages diff --git a/.github/workflows/setup-template.yml b/.github/workflows/setup-template.yml new file mode 100644 index 0000000..185ed0f --- /dev/null +++ b/.github/workflows/setup-template.yml @@ -0,0 +1,53 @@ +name: "Create" + +# The workflow will run only when the "Use this template" button is used +on: + create: + +jobs: + create: + # We only run this action when the repository isn't the template repository. References: + # - https://docs.github.com/en/actions/learn-github-actions/contexts + # - https://docs.github.com/en/actions/learn-github-actions/expressions + if: ${{ !github.event.repository.is_template }} + permissions: "write-all" + runs-on: "ubuntu-latest" + steps: + - name: "Check out the repo" + uses: "actions/checkout@v3" + + - name: "Update package.json" + env: + GITHUB_REPOSITORY_DESCRIPTION: ${{ github.event.repository.description }} + run: + ./.github/scripts/setup-packagejson.sh "$GITHUB_REPOSITORY" "$GITHUB_REPOSITORY_OWNER" + "$GITHUB_REPOSITORY_DESCRIPTION" + + - name: "Add rename summary" + run: | + echo "## Commit result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + - name: "Remove files not needed in the user's copy of the template" + run: | + rm -f "./.github/FUNDING.yml" + rm -f "./.github/scripts/setup-packagejson.sh" + rm -f "./.github/workflows/setup-template.yml" + + - name: "Add remove summary" + run: | + echo "## Remove result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + - name: "Update commit" + uses: "stefanzweifel/git-auto-commit-action@v4" + with: + commit_message: "🎉 initialize the project using @0x90d2b2b7fb7599eebb6e7a32980857d8 template" + commit_options: "--amend" + push_options: "--force" + skip_fetch: true + + - name: "Add commit summary" + run: | + echo "## Commit result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..c4a4d2c --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,48 @@ +name: Static analysis + +# This CI workflow is responsible of running static analysis on the codebase. +# The workflow will fail if the tool find a medium or high severity issue. +env: + FOUNDRY_PROFILE: "ci" + +on: + workflow_dispatch: + pull_request: + paths: + - src/** + - .github/workflows/static-analysis.yml + - slither.config.json + - foundry.toml + branches: + - main + push: + branches: + - main + paths: + - src/** + - .github/workflows/static-analysis.yml + - slither.config.json + - foundry.toml + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Set up Python3 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Install Slither + run: pip install slither-analyzer + + - name: Run static analysis using Slither + run: slither . --fail-medium diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..3690485 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,98 @@ +name: Tests + +env: + API_KEY_ALCHEMY: ${{ secrets.API_KEY_ALCHEMY }} + API_KEY_ETHERSCAN: ${{ secrets.API_KEY_ETHERSCAN }} + API_KEY_INFURA: ${{ secrets.API_KEY_INFURA }} + FOUNDRY_PROFILE: "ci" + MNEMONIC: ${{ secrets.MNEMONIC }} + +# This CI workflow is responsible of running the tests. +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + # TODO: Make this permission more granular + permissions: write-all + steps: + - name: Check out the repo + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show the Foundry config + run: forge config + + - name: Generate a fuzz seed that changes weekly to avoid burning through RPC allowance + run: > + echo "FOUNDRY_FUZZ_SEED=$( + echo $(($EPOCHSECONDS - $EPOCHSECONDS % 604800)) + )" >> $GITHUB_ENV + + # TODO: Rework the job to correctly print the output of the test command + - name: Run tests with gas reporting + run: forge test --gas-report > gasreport.ansi + + - name: Compare gas reports + uses: Rubilmax/foundry-gas-diff@v3.14 + with: + summaryQuantile: 0.8 # only display the 20% most significant gas diffs in the summary + sortCriteria: avg,max # sort diff rows by criteria + sortOrders: desc,asc # and directions + id: gas_diff + + # Comment on the PR with the gas diff + - name: Add gas diff to sticky comment + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + uses: marocchino/sticky-pull-request-comment@v2 + with: + # delete the comment in case changes no longer impact gas costs + delete: ${{ !steps.gas_diff.outputs.markdown }} + message: ${{ steps.gas_diff.outputs.markdown }} + + coverage: + needs: ["test"] + permissions: write-all + runs-on: "ubuntu-latest" + steps: + - name: "Check out the repo" + uses: "actions/checkout@v3" + with: + submodules: "recursive" + + - name: "Install Foundry" + uses: "foundry-rs/foundry-toolchain@v1" + + - name: Setup lcov + uses: hrishikesh-kadam/setup-lcov@v1 + + - name: "Generate the coverage report" + # contracts in the test/ and script/ directory are excluded fron the report + # the precompute internal version of the library is also excluded from the report as + # it is highly experimental and to meant to be used at all + run: "forge coverage --report lcov && lcov --remove lcov.info \ + -o lcov.info 'test/*' 'script/*'" + + - name: "Add coverage summary" + run: | + echo "## Coverage result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + - name: Report code coverage + uses: zgosalvez/github-actions-report-lcov@v3 + with: + coverage-files: lcov.info + # uncomment the following line to enforce a minimum coverage + # minimum-coverage: 80 + artifact-name: code-coverage-report + github-token: ${{ secrets.GITHUB_TOKEN }} + update-comment: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fd7526 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# directories +cache +node_modules +out + +# files +*.env +*.log +.DS_Store +.pnp.* +lcov.info +yarn.lock + +# broadcasts +!broadcast +broadcast/* +broadcast/*/31337/ + +# code editor config +.vscode +.editorconfig + +# forge +remappings.txt + +# coverage generated by genhtml +report + +# makefile generated files +.make.* + +# autogenerated documentation +docs + +# gas report +gasreport.ansi +.gas-snapshot* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..92e4318 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "lib/forge-std"] + branch = "v1" + path = "lib/forge-std" + url = "https://github.com/foundry-rs/forge-std" +[submodule "lib/prb-test"] + branch = "release-v0" + path = "lib/prb-test" + url = "https://github.com/PaulRBerg/prb-test" diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..3f430af --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..97d1773 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,19 @@ +# directories +broadcast +cache +lib +node_modules +out +.vscode +docs + +# files +*.env +*.log +.DS_Store +.pnp.* +lcov.info +package-lock.json +pnpm-lock.yaml +yarn.lock +README.md diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..a1ecdbb --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,7 @@ +bracketSpacing: true +printWidth: 120 +proseWrap: "always" +singleQuote: false +tabWidth: 2 +trailingComma: "all" +useTabs: false diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..ac7469e --- /dev/null +++ b/.solhint.json @@ -0,0 +1,13 @@ +{ + "extends": "solhint:recommended", + "rules": { + "code-complexity": ["error", 8], + "compiler-version": ["error", ">=0.8.19"], + "func-name-mixedcase": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "named-parameters-mapping": "warn", + "no-console": "off", + "not-rely-on-time": "off" + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e0554c0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,86 @@ +## Welcome to Our Project! + +First off, thank you for considering contributing to our project! We're really glad you're here, as we can always use +help from the open-source community. + +This document provides guidelines for people who want to contribute to our project. Feel free to propose changes to this +document in a pull request. + +## How Can I Contribute? + +Before contributing to this project, please make sure to read the [README.md](./README.md) file and familiarize yourself +with the commands listed in the [package.json](./package.json) file. + +### Reporting Bugs + +This section guides you through submitting a bug report for this project. Following these guidelines helps maintainers +and the community understand your report, reproduce the behavior, and find related reports. + +Please use the following format: + +```markdown +**Short Description:** [Summarize the problem] + +**Steps to Reproduce:** [First Step, Second Step, etc.] + +**Expected Behavior:** [What you expect to happen] + +**Actual Behavior:** [What actually happens] +``` + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for this project, including completely new features +and minor improvements to existing functionality. + +Please use the following format: + +```markdown +**Short Description:** [What would you like to see changed/added and why] + +**Considered Alternatives:** [What other alternatives have you considered] +``` + +### Your First Code Contribution + +Unsure where to begin contributing to the project? You can start by looking through these `Good First Issue` and +`Help Wanted` issues: + +- Good First Issues - issues which should only require a few lines of code, and a test or two. +- Help Wanted Issues - issues which should be a bit more involved than Good First Issues. + +### Pull Requests + +The process described here has several goals: + +- Maintain the project's quality +- Fix problems that are important to users +- Enable a sustainable system for project maintainers to review contributions + +Please follow these steps to have your contribution considered by the maintainers: + +1. Fork the repo and create your branch from main. +2. If you've added code that should be tested, add tests. The configured threshold must be respected. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. Issue that pull request! + +### Style Guide + +Ensure the quality before opening your pull request. You will find all the necessary commands in the `package.json` file +or you can run the git hooks manually to check your code. + +### Git Commit Messages + +1. Use the present tense ("Add feature" not "Added feature") +2. Use the imperative mood ("Move cursor to..." not "Moves cursor to...") +3. Limit the first line to 72 characters or less +4. Reference issues and pull requests liberally after the first line +5. The maintainers of this repository uses [gitmoji](https://gitmoji.dev/). This is not mandatory. + +### Merging Policy + +We require that all contributions show a linear history. This means no merge commits in your pull requests. Rebase your +feature branch onto the main branch and resolve any conflicts. If you are unsure about how to do this, +[Atlassian has a great tutorial](https://www.atlassian.com/git/tutorials/merging-vs-rebasing). diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..88a2b87 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,16 @@ +MIT License + +Copyright (c) 2023 Paul Razvan Berg + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4fab981 --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +# Foundry Template + +[![Open in Github][github-editor-badge]][github-editor-url] [![Github Actions][gha-quality-badge]][gha-quality-url] +[![Github Actions][gha-test-badge]][gha-test-url] +[![Github Actions][gha-static-analysis-badge]][gha-static-analysis-url] +[![Github Actions][gha-release-badge]][gha-release-url] [![Foundry][foundry-badge]][foundry] +[![License: MIT][license-badge]][license] + +[github-editor-url]: https://github.dev/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/tree/main +[github-editor-badge]: https://img.shields.io/badge/Github-Open%20the%20Editor-purple?logo=github +[gha-quality-url]: https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/actions/workflows/quality-checks.yml +[gha-quality-badge]: https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/actions/workflows/quality-checks.yml/badge.svg?branch=main +[gha-test-url]: https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/actions/workflows/tests.yml +[gha-test-badge]: https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/actions/workflows/tests.yml/badge.svg?branch=main +[gha-static-analysis-url]: https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/actions/workflows/static-analysis.yml +[gha-static-analysis-badge]: + https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/actions/workflows/static-analysis.yml/badge.svg?branch=main +[gha-release-url]: https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/actions/workflows/release-package.yml +[gha-release-badge]: https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/actions/workflows/release-package.yml/badge.svg +[foundry]: https://book.getfoundry.sh/ +[foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg +[license]: ./LICENSE.md +[license-badge]: https://img.shields.io/badge/License-MIT-blue.svg + +A Foundry-based template for developing Solidity smart contracts, with sensible defaults. Based on +[@PaulRBerg](https://github.com/PaulRBerg) [template](https://github.com/PaulRBerg/foundry-template). + +## What's Inside + +- [Forge](https://github.com/foundry-rs/foundry/blob/master/forge): compile, test, fuzz, format, and deploy smart + contracts +- [Forge Std](https://github.com/foundry-rs/forge-std): collection of helpful contracts and cheatcodes for testing +- [PRBTest](https://github.com/PaulRBerg/prb-test): modern collection of testing assertions and logging utilities +- [Prettier](https://github.com/prettier/prettier): code formatter for non-Solidity files +- [Solhint Community](https://github.com/solhint-community/solhint-community): linter for Solidity code +- [Make](https://www.gnu.org/software/make/manual/make.html): build automation tool that allows developers to automate + repetitive tasks +- [Lefthook](https://github.com/evilmartians/lefthook): Fast and powerful Git hooks manager for any type of projects + +## Getting Started + +### Prerequisites + +This repository uses [`make`](https://www.gnu.org/software/make/manual/make.html) to automate repetitive tasks. + +`make` is a build automation tool that employs a file known as a makefile to automate the construction of executable +programs and libraries. The makefile details the process of deriving the target program from the source files and other +dependencies. This allows developers to automate repetitive tasks and manage complex build processes efficiently. `make` +is our primary tool in a multi-environment repository. It enables us to centralize all commands into a single file +([the makefile](./makefile)), eliminating the need to deal with `npm` scripts defined in a package.json or remembering +the various commands provided by the `foundry` cli. If you're unfamiliar with `make`, you can read more about it +[here](https://www.gnu.org/software/make/manual/make.html). + +> 💡 Running make at the root of the project will display a list of all the available commands. This can be useful to +> know what you can do with the project. + +#### Make of Linux + +`make` is automatically included in all modern Linux distributions. If you're using Linux, you should be able to use +`make` without any additional steps. If not, you can likely find it in the package tool you usually use. + +#### Make on MacOS + +MacOS users can install `make` using [Homebrew](https://formulae.brew.sh/formula/make) with the following command: + +```sh +brew install make +``` + +### Installation + +Click the [`Use this template`](https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/generate) button at the top of the page to +create a new repository with this repo as the initial state. + +Or, if you prefer to install the template manually: + +```sh +forge init my-project --template https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry +cd my-project +make install # install the forge dependencies and the npm dependencies +``` + +If this is your first time with Foundry, check out the +[installation](https://github.com/foundry-rs/foundry#installation) instructions. + +> ℹ️ As part of the initialization process, a one-time script, which can be found +> [here](./.github/workflows/setup-template.yml), is utilized to tailor the template to your specific project. This +> script will automatically update the [package.json](./package.json) file with details like your project's name, the +> author's name, the homepage, the repository URL, etc. Additionally, it will remove unnecessary files, such as the +> FUNDING.yml file and the initialization script itself. + +### Git hooks + +This project uses `Lefthook` to manage Git hooks, which are scripts that run automatically when certain Git events +occur, such as committing code or pushing changes to a remote repository. `Lefthook` simplifies the management and +execution of these scripts. + +After installing the dependencies, you can configure the Git hooks by running the following command in the project +directory: + +```sh +make hooks-i +``` + +This command installs a Git hook that runs Lefthook before pushing code to a remote repository. If Lefthook fails, the +push is aborted. + +If you wish to run Lefthook manually, you can use the following command: + +```sh +make hooks +``` + +Executing this will activate all the Git hooks specified in the [lefthook](./lefthook.yml) file, including commands for +linting, formatting, testing, and compiling. + +#### Skipping git hooks + +If you need to intentionally skip Lefthook, you can pass the `--no-verify` flag to the git push command. For example to +bypass Lefthook when pushing code, use the following command: + +```sh +git push origin --no-verify +``` + +## Features + +This template builds upon the frameworks and libraries mentioned above, so for details about their specific features, +please consult their respective documentation. + +For example, if you're interested in exploring Foundry in more detail, you should look at the +[Foundry Book](https://book.getfoundry.sh/). In particular, you may be interested in reading the +[Writing Tests](https://book.getfoundry.sh/forge/writing-tests.html) tutorial. + +### Sensible Defaults + +This template comes with a set of sensible default configurations for you to use. These defaults can be found in the +following files: + +```text +├── .gitignore +├── .prettierignore +├── .prettierrc.yml +├── .solhint.json +├── .nvmrc +├── lefthook.yml +├── makefile +├── slither.config.json +└── foundry.toml +``` + +### GitHub Actions + +This template comes with GitHub Actions pre-configured. + +- [quality-checks.yml](./.github/workflows/quality-checks.yml): runs the compilation command, the linter and the + formatter on every push and pull request made to the `main` branch. The size of the contracts is printed in the logs. +- [static-analysis.yml](./.github/workflows/static-analysis.yml): runs the static analysis tool on every push and pull + request made to the `main` branch. This action uses [slither](https://github.com/crytic/slither) and is only triggered + when specific files are modified. +- [tests.yml](./.github/workflows/tests.yml): runs the tests on every push and pull request made to the `main` branch. This action also compare the gas cost between the `main` branch and the pull request branch and post the difference as a comment on the pull request. Finally this action check the code coverage and post the result as a comment on the pull request. A threshold can be configured by unchecking the concerned line in the workflow file. This workflow uses [lcov](https://github.com/linux-test-project/lcov) for the coverage. +- [release-package.yml](./.github/workflows/release-package.yml): creates a new release every time you push a new tag to + the repository. This action is only triggered on tags starting with `v`. Once the release is created, the action is + also in charge of deploying the documentation to the `gh-pages` branch. **THIS ACTION NEEDS AN ACTION FROM YOUR SIDE + TO WORK** + +You can edit the CI scripts in the [workflows directory](./.github/workflows). + +#### Configure the release action + +The release action is in charge of deploying the documentation to the `gh-pages` branch. To do so, it needs to have a +personal access token with the right permissions. To create this token, go to the +[settings of your Github account](https://github.com/settings/tokens?type=beta). Make sure to select the permissions +listed below. Once create, copy the token, go to the Github repository of this project and create a secret named +`RELEASE_TOKEN` with the value of the token you just created. Here are the **repositories** permissions required by the +token: + +- Actions: Read and write +- Contents: Read and write +- Commit statuses: Read-only +- Metadata: Read-only +- Pull requests: Read-only + +## Writing Tests + +To write a new test contract, you start by importing [PRBTest](https://github.com/PaulRBerg/prb-test) and inherit from +it in your test contract. PRBTest comes with a pre-instantiated [cheatcodes](https://book.getfoundry.sh/cheatcodes/) +environment accessible via the `vm` property. If you would like to view the logs in the terminal output you can run the +dedicated verbose command and use +[console.log](https://book.getfoundry.sh/faq?highlight=console.log#how-do-i-use-consolelog). + +This template comes with an example test contract [Foo.t.sol](./test/Foo.t.sol) + +## Usage + +You can access a list of all available commands by running `make` in the project's root directory. + +```sh +make +``` + +These commands are outlined in the [makefile](./Makefile). + +## Scripts + +### Deploy + +This script is located in the [script](./script) directory. It deploys the contract to a network. For example, to deploy +to [Anvil](https://book.getfoundry.sh/anvil/), you can run the following command: + +```sh +forge script script/Deploy.s.sol --broadcast --fork-url http://localhost:8545 +``` + +For this script to work, you need to have a `MNEMONIC` environment variable set to a valid +[BIP39 mnemonic](https://iancoleman.io/bip39/). + +For instructions on how to deploy to a testnet or mainnet, check out the +[Solidity Scripting](https://book.getfoundry.sh/tutorials/solidity-scripting.html) tutorial. + +## Notes + +1. Foundry uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to manage dependencies. For + detailed instructions on working with dependencies, please refer to the + [guide](https://book.getfoundry.sh/projects/dependencies.html) in the book +2. You don't have to create a `.env` file, but filling in the environment variables may be useful when debugging and + testing against a fork. +3. This template uses [npm](https://www.npmjs.com/) to manage JavaScript dependencies. +4. This template only uses [slither](https://github.com/crytic/slither) in the CI pipeline. If you want to run it + locally, you need to install it for yourself by following the instructions in the + [documentation](https://github.com/crytic/slither#how-to-install). +5. This template includes a opiniated [contributing guide](./.github/CONTRIBUTING.md) you free to update. +6. Remappings are configured in the [foundry.toml file](./foundry.toml) file in order to centralize the configuration. + Feel free to update them. + +## Related Efforts + +- [abigger87/femplate](https://github.com/abigger87/femplate) +- [cleanunicorn/ethereum-smartcontract-template](https://github.com/cleanunicorn/ethereum-smartcontract-template) +- [foundry-rs/forge-template](https://github.com/foundry-rs/forge-template) +- [FrankieIsLost/forge-template](https://github.com/FrankieIsLost/forge-template) +- [PaulRBerg/foundry-template](https://github.com/PaulRBerg/foundry-template) + +## License + +This project is licensed under MIT. + +## Acknowledgements + +This template has been boostrapped using [@PaulRBerg](https://github.com/PaulRBerg) +[template](https://github.com/PaulRBerg/foundry-template). This version is a bit more opinionated (`make`...) and comes +with a few more features. Thanks to him for his valuable contributions to the community. diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..29bc253 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,60 @@ +# Full reference https://github.com/foundry-rs/foundry/tree/master/config + +[profile.default] + auto_detect_solc = false + block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT + bytecode_hash = "none" + cbor_metadata = false + evm_version = "paris" + fuzz = { runs = 1_000 } + gas_reports = ["*"] + libs = ["lib"] + optimizer = true + optimizer_runs = 10_000 + out = "out" + script = "script" + solc = "0.8.19" + src = "src" + test = "test" + remappings = [ + "ds-test/=lib/forge-std/lib/ds-test/src/", + "forge-std/=lib/forge-std/src/", + "prb-test/=lib/prb-test/src/" + ] + +[profile.ci] + fuzz = { runs = 10_000 } + verbosity = 4 + +[etherscan] + arbitrum_one = { key = "${API_KEY_ARBISCAN}" } + avalanche = { key = "${API_KEY_SNOWTRACE}" } + bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" } + gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" } + goerli = { key = "${API_KEY_ETHERSCAN}" } + mainnet = { key = "${API_KEY_ETHERSCAN}" } + optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" } + polygon = { key = "${API_KEY_POLYGONSCAN}" } + sepolia = { key = "${API_KEY_ETHERSCAN}" } + +[fmt] + bracket_spacing = true + int_types = "long" + line_length = 120 + multiline_func_header = "all" + number_underscore = "thousands" + quote_style = "double" + tab_width = 4 + wrap_comments = true + +[rpc_endpoints] + arbitrum_one = "https://arbitrum-mainnet.infura.io/v3/${API_KEY_INFURA}" + avalanche = "https://avalanche-mainnet.infura.io/v3/${API_KEY_INFURA}" + bnb_smart_chain = "https://bsc-dataseed.binance.org" + gnosis_chain = "https://rpc.gnosischain.com" + goerli = "https://goerli.infura.io/v3/${API_KEY_INFURA}" + localhost = "http://localhost:8545" + mainnet = "https://eth-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}" + optimism = "https://optimism-mainnet.infura.io/v3/${API_KEY_INFURA}" + polygon = "https://polygon-mainnet.infura.io/v3/${API_KEY_INFURA}" + sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}" diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..b9db6f8 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,13 @@ +pre-push: + parallel: true + commands: + lint: + tags: quality + run: make lint + format: + tags: quality + run: make format + test: + run: make test + build: + run: make compile diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..e8a047e --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit e8a047e3f40f13fa37af6fe14e6e06283d9a060e diff --git a/lib/prb-test b/lib/prb-test new file mode 160000 index 0000000..1e9ead2 --- /dev/null +++ b/lib/prb-test @@ -0,0 +1 @@ +Subproject commit 1e9ead2f7bfaedda3038081c16484b0d7d0b2712 diff --git a/makefile b/makefile new file mode 100644 index 0000000..16f18a4 --- /dev/null +++ b/makefile @@ -0,0 +1,154 @@ +.SILENT: + +## define the shell function that is used to run commands defined in this file +define shell-functions +: BEGIN +runcmd() { + _cmd=$@; + + script_cmd="script -q /dev/null ${_cmd[@]} >&1"; + script -q /dev/null -c echo 2> /dev/null > /dev/null && script_cmd="script -q /dev/null -c \"${_cmd[@]}\" >&1"; + + printf "\e[90;1m[\e[90;1mmake: \e[0;90;1mcmd\e[0;90;1m]\e[0m \e[0;93;1m➔ \e[97;1m$_cmd\e[0m\n" \ + && ( \ + cmd_output=$(eval "$script_cmd" | tee /dev/tty; exit ${PIPESTATUS[0]}); cmd_exit_code=$?; \ + [ -z "$cmd_output" ] || ([ -z "$(tr -d '[:space:]' <<< $cmd_output)" ] && printf "\e[1A"); \ + [[ "$cmd_exit_code" -eq 0 ]] || return $cmd_exit_code \ + ) \ + && printf "\e[032;1m[✔︎] success\e[0m\n\n" \ + || (_test_exit=$? \ + && printf "\e[031;1m[✖︎] fail (exit code: $_test_exit)\e[0m\n\n" \ + && return $_test_exit) \ + && [ $? -eq 0 ] \ + || return $? +} +: END +endef + +# write the shell function in a git ignored file named .make.functions.sh +$(shell sed -n '/^: BEGIN/,/^: END/p' $(lastword $(MAKEFILE_LIST)) > .make.functions.sh) +SHELL := /bin/bash --init-file .make.functions.sh -i + +# print when running `make` without arguments +default: + printf """\e[37musage:\e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1minstall \e[0;90m➔ \e[32;3minstall the git submodules and the npm dependencies \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mcompile \e[0;90m➔ \e[32;3mcompile the contracts \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mcompile-s \e[0;90m➔ \e[32;3mcompile the contracts and print their size \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mtest \e[0;90m➔ \e[32;3mrun the tests \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mtest-v \e[0;90m➔ \e[32;3mrun the tests in verbose mode \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mgas \e[0;90m➔ \e[32;3mrun the tests and print the gas report \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mcoverage \e[0;90m➔ \e[32;3mrun the tests and print the coverage report \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mclean \e[0;90m➔ \e[32;3mremove the build artifacts and cache directories \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mupdate \e[0;90m➔ \e[32;3mupdate the git submodules and the npm dependencies \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mhooks \e[0;90m➔ \e[32;3mrun the installed git hooks \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mhooks-i \e[0;90m➔ \e[32;3minstall the git hooks defined in lefthook.yml \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mhooks-u \e[0;90m➔ \e[32;3muninstall the git hooks defined in lefthook.yml \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mlint \e[0;90m➔ \e[32;3mrun the linter in check mode \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mlint-fix \e[0;90m➔ \e[32;3mrun the linter in write mode \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mprettier \e[0;90m➔ \e[32;3mrun the formatter in read mode \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mprettier-fix \e[0;90m➔ \e[32;3mrun the formatter in write mode \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mquality \e[0;90m➔ \e[32;3mrun both the linter and the formatter in read mode \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mdoc \e[0;90m➔ \e[32;3mgenerate the documentation of the project \e[0m\n \ + \e[90m$$ \e[0;97;1mmake \e[0;92;1mtree \e[0;90m➔ \e[32;3mdisplay a tree visualization of the project's dependency graph \e[0m\n \ + """ | sed -e 's/^[ \t ]\{1,\}\(.\)/ \1/' + + +########################################## +################ COMMANDS ################ +########################################## +.PHONY: forge-compile +forge-compile: + @runcmd forge compile + +.PHONY: forge-compile-size +forge-compile-size: + @runcmd forge compile --sizes + +.PHONY: forge-test +forge-test: + @runcmd forge test + +.PHONY: forge-test-verbose +forge-test-verbose: + @runcmd forge test -vvvv + +.PHONY: forge-coverage +forge-coverage: + @runcmd forge coverage + +.PHONY: forge-test-gas +forge-test-gas: + @runcmd forge test --gas-report --no-match-test "test(Fuzz)?_RevertWhen_\\w{1,}?" + +.PHONY: forge-clean +clean: + @runcmd forge clean + +.PHONY: forge-doc +forge-doc: + @runcmd forge doc && forge doc --build + +.PHONY: forge-tree +forge-tree: + @runcmd forge tree --no-dedupe + +.PHONY: update-dependencies +update-dependencies: + @runcmd git submodule update --init --recursive && npm update + +.PHONY: install-dependencies +install-dependencies: + @runcmd forge install && npm install + +.PHONY: lefthok-run +lefthok-run: + @runcmd npx lefthook run pre-push + +.PHONY: lefthok-install +lefthok-install: + @runcmd npx lefthook install + +.PHONY: lefthok-uninstall +lefthok-uninstall: + @runcmd npx lefthook uninstall + +.PHONY: linter +linter: + @runcmd forge fmt --check && npx solhint "{script,src,test}/**/*.sol" + +.PHONY: linter-fix +linter-fix: + @runcmd forge fmt && npx solhint "{script,src,test}/**/*.sol" --fix + +.PHONY: prettier +prettier: + @runcmd npx prettier --check \"**/*.{json,md,yml}\" + +.PHONY: prettier-fix +prettier-fix: + @runcmd npx prettier --write \"**/*.{json,md,yml}\" + +########################################## +################ ALIASES ################ +########################################## +build: forge-compile +compile: forge-compile +compile-s: forge-compile-size +test: forge-test +test-v: forge-test-verbose +gas: forge-test-gas +coverage: forge-coverage +clean: forge-clean +update: update-dependencies +doc: forge-doc +tree: forge-tree +hooks: lefthok-run +hooks-i: lefthok-install +hooks-u: lefthok-uninstall +lint: linter +lint-fix: linter-fix +format: prettier +format-fix: prettier-fix +quality: lint format +install: install-dependencies lefthok-install diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4d1f4fb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,856 @@ +{ + "name": "@0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry", + "version": "1.0.0", + "devDependencies": { + "lefthook": "^1.4.1", + "prettier": "^2.8.7", + "solhint-community": "^3.5.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@solidity-parser/parser": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.0.tgz", + "integrity": "sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==", + "dev": true, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antlr4": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.0.tgz", + "integrity": "sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/ast-parents": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/ast-parents/-/ast-parents-0.0.1.tgz", + "integrity": "sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/cosmiconfig": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "dev": true, + "dependencies": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/lefthook": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook/-/lefthook-1.4.1.tgz", + "integrity": "sha512-05jULu8iGCOfUnONImG3MJOtmMG9W1ihlnWWDJ6vmmC6eZqtb4yFBw5Dt/fD0H3ZFeYa9Yb2quvaU3y88W5GCw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "lefthook": "bin/index.js" + }, + "optionalDependencies": { + "lefthook-darwin-arm64": "1.4.1", + "lefthook-darwin-x64": "1.4.1", + "lefthook-freebsd-arm64": "1.4.1", + "lefthook-freebsd-x64": "1.4.1", + "lefthook-linux-arm64": "1.4.1", + "lefthook-linux-x64": "1.4.1", + "lefthook-windows-arm64": "1.4.1", + "lefthook-windows-x64": "1.4.1" + } + }, + "node_modules/lefthook-darwin-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook-darwin-arm64/-/lefthook-darwin-arm64-1.4.1.tgz", + "integrity": "sha512-j3Egr/oNu8JKZDtwtKZrU+34c4DDNy8sKuagZrfFpmwhZEqSFUMxjxUC/J8QnX0Bc60Wo+UAUfv/Oe6g+s4QSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/lefthook-darwin-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook-darwin-x64/-/lefthook-darwin-x64-1.4.1.tgz", + "integrity": "sha512-gGzltha7Fo3CDavDUF9l8VVcrMCb8Dm3x/Bax4+Pjs3ZUTTgIxZZe89ORYqVlet0egh9GUx/L20r8+kwdTDwAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/lefthook-freebsd-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-1.4.1.tgz", + "integrity": "sha512-OzpkXzOeRjTjXT+f+AjZxxBhaSA6QTqWMlrkgnFJuNZOVKT6bfgkusvpyC1KAYlKxlRGHnvfTRPAv3Ouub/vaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/lefthook-freebsd-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook-freebsd-x64/-/lefthook-freebsd-x64-1.4.1.tgz", + "integrity": "sha512-6befhXE64XOyt1XLg5Wf1/rVMvwzOf0BaEvQoBXX8fn6/3q1e+86fcejUi4doMngeagpm03XWMqNiy592NqfZw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/lefthook-linux-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook-linux-arm64/-/lefthook-linux-arm64-1.4.1.tgz", + "integrity": "sha512-j8p2BukrsFAC4fGQVqCFVE6CfwE7d21nh3WUMWqISk8qMO5+OTkdohZViPeOPk9sxxC05aXvFDgWDKW0VXs1og==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/lefthook-linux-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook-linux-x64/-/lefthook-linux-x64-1.4.1.tgz", + "integrity": "sha512-0cYddhIjT8FtrFB47rKLYi7FLmYRi3NRexpyj6FRiTWkN8Joz10MpCqFPnZOfZRPgUm4sg5HqoTdxiDmaYV2kQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/lefthook-windows-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook-windows-arm64/-/lefthook-windows-arm64-1.4.1.tgz", + "integrity": "sha512-jgY/m6+g9ofr+ePx0tUoo63ItunVBYamCY16wLnXVI6xyYmP5wEy83u0lnBS4wQ8zcH22YUCDrL8KtrQFfLsjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/lefthook-windows-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lefthook-windows-x64/-/lefthook-windows-x64-1.4.1.tgz", + "integrity": "sha512-lvTpayQkBmOp5czolntESM2uWRG0I5r0HkOnFfhGEaqEFm45sUjKZsxNj/EPQLAXlWlXqLe363UNcSnmUf/g3g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/solhint-community": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/solhint-community/-/solhint-community-3.5.1.tgz", + "integrity": "sha512-JqNG8C+VwL/REjAc3M0+vXzrfduvKfTUwcYBkdI9F/OC3CvQiP02zLuUruqgkzQbF+ih7pTwITFvJzIGyEIodg==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.16.0", + "ajv": "^6.12.6", + "antlr4": "^4.11.0", + "ast-parents": "^0.0.1", + "chalk": "^4.1.2", + "commander": "^10.0.0", + "cosmiconfig": "^8.0.0", + "fast-diff": "^1.2.0", + "glob": "^8.0.3", + "ignore": "^5.2.4", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "semver": "^6.3.0", + "strip-ansi": "^6.0.1", + "table": "^6.8.1", + "text-table": "^0.2.0" + }, + "bin": { + "solhint": "solhint.js" + }, + "optionalDependencies": { + "prettier": "^2.8.3" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c6bef39 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "@0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry", + "description": "Foundry-based template for developing Solidity smart contracts. Based on PaulRBerg template.", + "version": "1.0.0", + "author": "0x90d2b2b7fb7599eebb6e7a32980857d8 https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8", + "repository": "github:@0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry", + "bugs": "https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry/issues", + "homepage": "https://github.com/0x90d2b2b7fb7599eebb6e7a32980857d8/template-foundry#readme", + "engines": { + "node": ">= 18" + }, + "devDependencies": { + "lefthook": "^1.4.1", + "prettier": "^2.8.7", + "solhint-community": "^3.5.0" + }, + "keywords": [ + "blockchain", + "ethereum", + "forge", + "evm", + "foundry", + "smart-contracts", + "solidity" + ] +} diff --git a/script/Base.s.sol b/script/Base.s.sol new file mode 100644 index 0000000..b9c6976 --- /dev/null +++ b/script/Base.s.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.19 <0.9.0; + +import { Script } from "forge-std/Script.sol"; + +abstract contract BaseScript is Script { + /// @dev Included to enable compilation of the script without a $MNEMONIC environment variable. + string internal constant TEST_MNEMONIC = "test test test test test test test test test test test junk"; + + /// @dev Needed for the deterministic deployments. + bytes32 internal constant ZERO_SALT = bytes32(0); + + /// @dev The address of the contract deployer. + address internal deployer; + + /// @dev Used to derive the deployer's address. + string internal mnemonic; + + constructor() { + mnemonic = vm.envOr("MNEMONIC", TEST_MNEMONIC); + (deployer,) = deriveRememberKey({ mnemonic: mnemonic, index: 0 }); + } + + modifier broadcaster() { + vm.startBroadcast(deployer); + _; + vm.stopBroadcast(); + } +} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..a333b59 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19 <0.9.0; + +import { Foo } from "../src/Foo.sol"; + +import { BaseScript } from "./Base.s.sol"; + +/// @dev See the Solidity Scripting tutorial: https://book.getfoundry.sh/tutorials/solidity-scripting +contract Deploy is BaseScript { + function run() public broadcaster returns (Foo foo) { + foo = new Foo(); + } +} diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 0000000..c88d669 --- /dev/null +++ b/slither.config.json @@ -0,0 +1,3 @@ +{ + "filter_paths": "lib|test" +} diff --git a/src/Foo.sol b/src/Foo.sol new file mode 100644 index 0000000..b4dff69 --- /dev/null +++ b/src/Foo.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.19 <0.9.0; + +contract Foo { + function id(uint256 value) external pure returns (uint256) { + return value; + } +} diff --git a/test/Foo.t.sol b/test/Foo.t.sol new file mode 100644 index 0000000..1bf1433 --- /dev/null +++ b/test/Foo.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.19 <0.9.0; + +import { PRBTest } from "prb-test/PRBTest.sol"; +import { console2 } from "forge-std/console2.sol"; +import { StdCheats } from "forge-std/StdCheats.sol"; + +import { Foo } from "../src/Foo.sol"; + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); +} + +/// @dev If this is your first time with Forge, read this tutorial in the Foundry Book: +/// https://book.getfoundry.sh/forge/writing-tests +contract FooTest is PRBTest, StdCheats { + Foo internal foo; + + /// @dev A function invoked before each test case is run. + function setUp() public virtual { + // Instantiate the contract-under-test. + foo = new Foo(); + } + + /// @dev Basic test. Run it with `forge test -vvv` to see the console log. + function test_Example() external { + console2.log("Hello World"); + uint256 x = 42; + assertEq(foo.id(x), x, "value mismatch"); + } + + /// @dev Fuzz test that provides random values for an unsigned integer, but which rejects zero as an input. + /// If you need more sophisticated input validation, you should use the `bound` utility instead. + /// See https://twitter.com/PaulRBerg/status/1622558791685242880 + function testFuzz_Example(uint256 x) external { + vm.assume(x != 0); // or x = bound(x, 1, 100) + assertEq(foo.id(x), x, "value mismatch"); + } + + /// @dev Fork test that runs against an Ethereum Mainnet fork. For this to work, you need to set `API_KEY_ALCHEMY` + /// in your environment You can get an API key for free at https://alchemy.com. + function testFork_Example() external { + // Silently pass this test if there is no API key. + string memory alchemyApiKey = vm.envOr("API_KEY_ALCHEMY", string("")); + if (bytes(alchemyApiKey).length == 0) { + return; + } + + // Otherwise, run the test against the mainnet fork. + vm.createSelectFork({ urlOrAlias: "mainnet", blockNumber: 16_428_000 }); + address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address holder = 0x7713974908Be4BEd47172370115e8b1219F4A5f0; + uint256 actualBalance = IERC20(usdc).balanceOf(holder); + uint256 expectedBalance = 196_307_713.810457e6; + assertEq(actualBalance, expectedBalance); + } +}