From 06e8fb995a04032e751b8e41ea2d71a7dce28722 Mon Sep 17 00:00:00 2001 From: qdqd Date: Thu, 16 Nov 2023 15:39:54 +0100 Subject: [PATCH] Initial commit --- .env.example | 10 + .gitattributes | 1 + .github/scripts/setup-packagejson.sh | 57 ++ .github/workflows/quality-checks.yml | 59 ++ .github/workflows/release-package.yml | 48 ++ .github/workflows/setup-template.yml | 53 ++ .github/workflows/static-analysis.yml | 48 ++ .github/workflows/tests.yml | 98 +++ .gitignore | 37 ++ .gitmodules | 8 + .nvmrc | 1 + .prettierignore | 19 + .prettierrc.yml | 7 + .solhint.json | 13 + CONTRIBUTING.md | 86 +++ LICENSE.md | 16 + README.md | 252 ++++++++ foundry.toml | 60 ++ lefthook.yml | 13 + lib/forge-std | 1 + lib/prb-test | 1 + makefile | 154 +++++ package-lock.json | 856 ++++++++++++++++++++++++++ package.json | 26 + script/Base.s.sol | 29 + script/Deploy.s.sol | 13 + slither.config.json | 3 + src/Foo.sol | 8 + test/Foo.t.sol | 57 ++ 29 files changed, 2034 insertions(+) create mode 100644 .env.example create mode 100644 .gitattributes create mode 100755 .github/scripts/setup-packagejson.sh create mode 100644 .github/workflows/quality-checks.yml create mode 100644 .github/workflows/release-package.yml create mode 100644 .github/workflows/setup-template.yml create mode 100644 .github/workflows/static-analysis.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.yml create mode 100644 .solhint.json create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 foundry.toml create mode 100644 lefthook.yml create mode 160000 lib/forge-std create mode 160000 lib/prb-test create mode 100644 makefile create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 script/Base.s.sol create mode 100644 script/Deploy.s.sol create mode 100644 slither.config.json create mode 100644 src/Foo.sol create mode 100644 test/Foo.t.sol 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); + } +}