├── .changeset ├── README.md ├── config.json └── smart-garlics-lick.md ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── config ├── eslint │ └── eslintrc.js └── typescript │ ├── @types │ ├── adm-zip │ │ └── index.d.ts │ ├── ethereumjs-abi │ │ └── index.d.ts │ ├── solc │ │ └── index.d.ts │ ├── solhint │ │ └── index.d.ts │ ├── solpp │ │ └── index.d.ts │ └── tabtab │ │ └── index.d.ts │ └── tsconfig.json ├── package.json ├── packages ├── easy-foundryup │ ├── .eslintrc.js │ ├── .gitignore │ ├── .mocharc.json │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── binary.ts │ │ ├── foundryup.ts │ │ ├── index.ts │ │ └── tsconfig.json │ ├── test │ │ └── foundryup.test.ts │ └── tsconfig.json ├── hardhat-anvil │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .mocharc.json │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── anvil-options-ti.ts │ │ ├── anvil-provider-adapter.ts │ │ ├── anvil-server.ts │ │ ├── anvil-service.ts │ │ ├── constants.ts │ │ ├── deploy.ts │ │ ├── fixtures.ts │ │ ├── index.ts │ │ ├── link.ts │ │ ├── matchers.ts │ │ ├── type-extensions.ts │ │ └── waffle-chai.ts │ ├── test │ │ ├── .eslintrc.js │ │ ├── fixture-projects │ │ │ ├── hardhat-project-with-configs │ │ │ │ ├── hardhat.config.ts │ │ │ │ └── scripts │ │ │ │ │ └── custom-accounts-sample.js │ │ │ └── hardhat-project │ │ │ │ ├── .gitignore │ │ │ │ ├── contracts │ │ │ │ └── EVMInspector.sol │ │ │ │ ├── hardhat.config.ts │ │ │ │ ├── scripts │ │ │ │ ├── accounts-sample.js │ │ │ │ └── delayed-sample.js │ │ │ │ └── test │ │ │ │ └── test.js │ │ ├── helpers.ts │ │ └── index.ts │ └── tsconfig.json ├── hardhat-forge │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .mocharc.json │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── forge │ │ │ ├── artifacts.ts │ │ │ ├── build │ │ │ │ ├── build-ti.ts │ │ │ │ ├── build.ts │ │ │ │ └── index.ts │ │ │ ├── common │ │ │ │ ├── compiler-ti.ts │ │ │ │ ├── compiler.ts │ │ │ │ ├── env-ti.ts │ │ │ │ ├── env.ts │ │ │ │ ├── evm-ti.ts │ │ │ │ ├── evm.ts │ │ │ │ ├── index.ts │ │ │ │ ├── projectpaths-ti.ts │ │ │ │ └── projectpaths.ts │ │ │ ├── config │ │ │ │ ├── config.ts │ │ │ │ └── index.ts │ │ │ ├── constants.ts │ │ │ ├── index.ts │ │ │ ├── test │ │ │ │ ├── index.ts │ │ │ │ ├── test-ti.ts │ │ │ │ └── test.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ └── task-names.ts │ ├── test │ │ ├── .eslintrc.js │ │ ├── fixture-projects │ │ │ └── hardhat-project │ │ │ │ ├── foundry.toml │ │ │ │ ├── hardhat.config.ts │ │ │ │ └── src │ │ │ │ ├── Contract.sol │ │ │ │ ├── ContractTest.t.sol │ │ │ │ └── foo │ │ │ │ └── Contract2.sol │ │ ├── helpers.ts │ │ └── project.test.ts │ └── tsconfig.json └── hardhat │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .mocharc.json │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── test │ ├── .eslintrc.js │ ├── fixture-projects │ │ ├── hardhat-project │ │ │ ├── .gitignore │ │ │ ├── foundry.toml │ │ │ ├── hardhat.config.ts │ │ │ └── src │ │ │ │ └── Contract.sol │ │ └── no-anvil-config │ │ │ ├── foundry.toml │ │ │ ├── hardhat.config.ts │ │ │ └── src │ │ │ └── Contract.sol │ ├── helpers.ts │ └── project.test.ts │ └── tsconfig.json ├── tslint.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "develop", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/smart-garlics-lick.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@foundry-rs/hardhat-anvil": patch 3 | "@foundry-rs/hardhat": patch 4 | --- 5 | 6 | convert error to warning if not localhost 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | trim_trailing_whitespace = false 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | # prevent github actions to checkout files with crlf line endings 3 | * -text 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test/Lint 2 | 3 | on: 4 | push: 5 | branches: [master, pre-release-testing-branch] 6 | pull_request: 7 | branches: 8 | - "*" 9 | 10 | env: 11 | # Needed to use the ::add-path command 12 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 13 | # Needed to make foundryup not complain 14 | SHELL: /bin/bash 15 | 16 | concurrency: 17 | group: ${{github.workflow}}-${{github.ref}} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | lint: 22 | name: Lint (JavaScript) 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: actions/setup-node@v2 27 | with: 28 | node-version: 12 29 | cache: yarn 30 | - name: Set PATH 31 | run: echo ::add-path::$HOME/.foundry/bin 32 | - name: Install 33 | run: yarn --frozen-lockfile 34 | - name: Build 35 | run: yarn build 36 | - name: Lint 37 | run: yarn lint 38 | prettier: 39 | name: Lint (.md, .yml) 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v3 43 | - uses: actions/setup-node@v2 44 | with: 45 | node-version: 12 46 | cache: yarn 47 | - name: Set PATH 48 | run: echo ::add-path::$HOME/.foundry/bin 49 | - name: Install 50 | run: yarn --frozen-lockfile 51 | - name: Build 52 | run: yarn build 53 | - name: Lint 54 | run: yarn prettier 55 | test: 56 | name: Test 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v3 60 | - uses: actions/setup-node@v2 61 | with: 62 | node-version: 12 63 | cache: yarn 64 | - name: Set PATH 65 | run: echo ::add-path::$HOME/.foundry/bin 66 | - name: Install 67 | run: yarn --frozen-lockfile 68 | - name: Build 69 | run: yarn build 70 | - name: Test 71 | run: yarn test 72 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | jobs: 10 | release: 11 | name: Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Repo 15 | uses: actions/checkout@master 16 | with: 17 | fetch-depth: 0 18 | 19 | - uses: actions/setup-node@v1 20 | with: 21 | node-version: "14.x" 22 | 23 | - name: Cache Yarn dependencies 24 | uses: actions/cache@v2 25 | with: 26 | path: | 27 | node_modules 28 | */*/node_modules 29 | key: ${{ runner.OS }}-14.x-yarn-cache-${{ hashFiles('**/yarn.lock') }} 30 | restore-keys: ${{ runner.OS }}-14.x-yarn-cache- 31 | 32 | - name: WHAT IS MY SHELL 33 | run: echo $SHELL 34 | 35 | - name: Install 36 | run: yarn --no-progress --non-interactive --frozen-lockfile 37 | 38 | - name: Create Release Pull Request or Publish to NPM 39 | uses: changesets/action@v1 40 | with: 41 | publish: yarn release 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 44 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | cache/ 3 | 4 | # Allow a dummy project in this directory for testing purposes 5 | myproject 6 | 7 | /node_modules 8 | /.idea 9 | *.tsbuildinfo 10 | 11 | # VS Code workspace config 12 | workspace.code-workspace 13 | 14 | .DS_Store 15 | 16 | # Below is Github's node gitignore template, 17 | # ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing 18 | 19 | # Logs 20 | logs 21 | *.log 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | lerna-debug.log* 26 | 27 | # Diagnostic reports (https://nodejs.org/api/report.html) 28 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 29 | 30 | # Runtime data 31 | pids 32 | *.pid 33 | *.seed 34 | *.pid.lock 35 | 36 | # Directory for instrumented libs generated by jscoverage/JSCover 37 | lib-cov 38 | 39 | # Coverage directory used by tools like istanbul 40 | coverage 41 | 42 | # nyc test coverage 43 | .nyc_output 44 | 45 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 46 | .grunt 47 | 48 | # Bower dependency directory (https://bower.io/) 49 | bower_components 50 | 51 | # node-waf configuration 52 | .lock-wscript 53 | 54 | # Compiled binary addons (https://nodejs.org/api/addons.html) 55 | build/Release 56 | 57 | # Dependency directories 58 | #node_modules/ 59 | jspm_packages/ 60 | 61 | # TypeScript v1 declaration files 62 | typings/ 63 | 64 | # Optional npm cache directory 65 | .npm 66 | 67 | # Optional eslint cache 68 | .eslintcache 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # next.js build output 83 | .next 84 | 85 | # nuxt.js build output 86 | .nuxt 87 | 88 | # vuepress build output 89 | .vuepress/dist 90 | 91 | # Serverless directories 92 | .serverless/ 93 | 94 | # FuseBox cache 95 | .fusebox/ 96 | 97 | # DynamoDB Local files 98 | .dynamodb/ 99 | 100 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "proseWrap": "never" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | SEE LICENSE IN EACH PACKAGE'S LICENSE FILE 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | This repo contains [hardhat](https://github.com/NomicFoundation/hardhat) plugins to use [foundry](https://github.com/foundry-rs/foundry/) tools in hardhat environments. 4 | 5 | ## Installation 6 | 7 | See in each plugin 8 | 9 | - [anvil](./packages/hardhat-anvil/README.md) 10 | - [forge](./packages/hardhat-forge/README.md) 11 | - [foundryup](./packages/easy-foundryup/README.md) 12 | 13 | ## Documentation 14 | 15 | - [Foundry repo](https://github.com/foundry-rs/foundry/) 16 | - [Foundry book](https://book.getfoundry.sh/) 17 | - [Anvil](https://github.com/foundry-rs/foundry/tree/master/anvil) 18 | 19 | ## Releases 20 | 21 | ### The develop branch 22 | 23 | _Adapted from Optimism's [release process](https://github.com/ethereum-optimism/optimism#overview)_ 24 | 25 | Our primary development branch is [`develop`](https://github.com/foundry-rs/hardhat/tree/develop/). 26 | 27 | Developers can release new versions of the software by adding changesets to their pull requests using `yarn changeset`. Changesets will persist over time on the `develop` branch without triggering new version bumps to be proposed by the Changesets bot. Once changesets are merged into `master`, the bot will create a new pull request called "Version Packages" which bumps the versions of packages. The correct flow for triggering releases is to update the base branch of these pull requests onto `develop` and merge them, and then create a new pull request to merge `develop` into `master`. Then, the `release` workflow will trigger the actual publishing to `npm` and Docker hub. 28 | 29 | Be sure to not merge other pull requests into `develop` if partially through the release process. This can cause problems with Changesets doing releases and will require manual intervention to fix it. 30 | 31 | ## Contributing 32 | 33 | See [contributing guidelines](https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md) 34 | -------------------------------------------------------------------------------- /config/eslint/eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: ["plugin:prettier/recommended"], 8 | parser: "@typescript-eslint/parser", 9 | plugins: [ 10 | "@nomiclabs/eslint-plugin-hardhat-internal-rules", 11 | "eslint-plugin-import", 12 | "@typescript-eslint", 13 | ], 14 | rules: { 15 | "@typescript-eslint/adjacent-overload-signatures": "error", 16 | "@typescript-eslint/array-type": [ 17 | "error", 18 | { 19 | default: "array-simple", 20 | }, 21 | ], 22 | "@typescript-eslint/await-thenable": "error", 23 | "@typescript-eslint/ban-types": [ 24 | "error", 25 | { 26 | types: { 27 | Object: { 28 | message: "Avoid using the `Object` type. Did you mean `object`?", 29 | }, 30 | Boolean: { 31 | message: "Avoid using the `Boolean` type. Did you mean `boolean`?", 32 | }, 33 | Function: { 34 | message: 35 | "Avoid using the `Function` type. Prefer a specific function type, like `() => void`.", 36 | }, 37 | Number: { 38 | message: "Avoid using the `Number` type. Did you mean `number`?", 39 | }, 40 | String: { 41 | message: "Avoid using the `String` type. Did you mean `string`?", 42 | }, 43 | Symbol: { 44 | message: "Avoid using the `Symbol` type. Did you mean `symbol`?", 45 | }, 46 | }, 47 | extendDefaults: false, 48 | }, 49 | ], 50 | "@typescript-eslint/consistent-type-assertions": "error", 51 | "@typescript-eslint/consistent-type-definitions": "error", 52 | "@typescript-eslint/dot-notation": "error", 53 | "@typescript-eslint/explicit-member-accessibility": [ 54 | "error", 55 | { 56 | accessibility: "explicit", 57 | overrides: { 58 | constructors: "no-public", 59 | }, 60 | }, 61 | ], 62 | "@typescript-eslint/naming-convention": [ 63 | "error", 64 | { 65 | selector: "default", 66 | format: ["camelCase"], 67 | leadingUnderscore: "allow", 68 | trailingUnderscore: "allow", 69 | }, 70 | { 71 | selector: ["variable", "parameter"], 72 | format: ["camelCase", "UPPER_CASE", "PascalCase"], 73 | leadingUnderscore: "allow", 74 | trailingUnderscore: "allow", 75 | }, 76 | { 77 | selector: "classProperty", 78 | format: ["camelCase", "UPPER_CASE"], 79 | leadingUnderscore: "allow", 80 | }, 81 | { 82 | selector: "enumMember", 83 | format: ["UPPER_CASE"], 84 | }, 85 | { 86 | selector: "memberLike", 87 | modifiers: ["private"], 88 | format: ["camelCase"], 89 | leadingUnderscore: "require", 90 | }, 91 | { 92 | selector: ["objectLiteralProperty", "objectLiteralMethod"], 93 | format: ["camelCase", "PascalCase", "snake_case", "UPPER_CASE"], 94 | leadingUnderscore: "allow", 95 | }, 96 | { 97 | selector: "typeProperty", 98 | format: ["camelCase", "PascalCase"], 99 | leadingUnderscore: "allow", 100 | }, 101 | { 102 | selector: "typeLike", 103 | format: ["PascalCase"], 104 | }, 105 | { 106 | selector: "typeProperty", 107 | filter: "__hardhatContext", 108 | format: null, 109 | }, 110 | ], 111 | "@typescript-eslint/no-empty-interface": "error", 112 | "@typescript-eslint/no-floating-promises": "error", 113 | "@typescript-eslint/no-misused-new": "error", 114 | "@typescript-eslint/no-namespace": "error", 115 | "@typescript-eslint/no-redeclare": "error", 116 | "@typescript-eslint/no-shadow": [ 117 | "error", 118 | { 119 | hoist: "all", 120 | }, 121 | ], 122 | "@typescript-eslint/no-this-alias": "error", 123 | "@typescript-eslint/no-unused-expressions": "error", 124 | "@typescript-eslint/no-unused-vars": [ 125 | "error", 126 | { 127 | argsIgnorePattern: "^_", 128 | varsIgnorePattern: "^_", 129 | }, 130 | ], 131 | "@typescript-eslint/prefer-for-of": "error", 132 | "@typescript-eslint/prefer-function-type": "error", 133 | "@typescript-eslint/prefer-namespace-keyword": "error", 134 | "@typescript-eslint/restrict-plus-operands": "error", 135 | "@typescript-eslint/restrict-template-expressions": [ 136 | "error", 137 | { 138 | allowAny: true, 139 | }, 140 | ], 141 | "@typescript-eslint/strict-boolean-expressions": [ 142 | "error", 143 | { 144 | allowAny: true, 145 | }, 146 | ], 147 | "@typescript-eslint/triple-slash-reference": [ 148 | "error", 149 | { 150 | path: "always", 151 | types: "prefer-import", 152 | lib: "always", 153 | }, 154 | ], 155 | "@typescript-eslint/unified-signatures": "error", 156 | "constructor-super": "error", 157 | eqeqeq: ["error", "always"], 158 | "guard-for-in": "error", 159 | "id-blacklist": "error", 160 | "id-match": "error", 161 | "import/no-extraneous-dependencies": [ 162 | "error", 163 | { 164 | devDependencies: false, 165 | }, 166 | ], 167 | "import/order": [ 168 | "error", 169 | { 170 | groups: [ 171 | "type", 172 | "object", 173 | ["builtin", "external"], 174 | "parent", 175 | "sibling", 176 | "index", 177 | ], 178 | }, 179 | ], 180 | "import/no-default-export": "error", 181 | "no-bitwise": "error", 182 | "no-caller": "error", 183 | "no-cond-assign": "error", 184 | "no-debugger": "error", 185 | "no-duplicate-case": "error", 186 | "no-duplicate-imports": "error", 187 | "no-eval": "error", 188 | "no-extra-bind": "error", 189 | "no-new-func": "error", 190 | "no-new-wrappers": "error", 191 | "no-return-await": "off", 192 | "@typescript-eslint/return-await": "error", 193 | "no-sequences": "error", 194 | "no-sparse-arrays": "error", 195 | "no-template-curly-in-string": "error", 196 | "no-throw-literal": "error", 197 | "no-undef-init": "error", 198 | "no-unsafe-finally": "error", 199 | "no-unused-labels": "error", 200 | "no-unused-vars": "off", 201 | "no-var": "error", 202 | "object-shorthand": "error", 203 | "one-var": ["error", "never"], 204 | "prefer-const": "error", 205 | "prefer-object-spread": "error", 206 | "prefer-template": "error", 207 | radix: "error", 208 | "spaced-comment": [ 209 | "error", 210 | "always", 211 | { 212 | markers: ["/"], 213 | }, 214 | ], 215 | "use-isnan": "error", 216 | "no-restricted-imports": [ 217 | "error", 218 | { 219 | patterns: ["hardhat/src", "@nomiclabs/*/src"], 220 | }, 221 | ], 222 | }, 223 | }; 224 | -------------------------------------------------------------------------------- /config/typescript/@types/adm-zip/index.d.ts: -------------------------------------------------------------------------------- 1 | // there is a @types/adm-zip in definitely-typed, 2 | // but it breaks the build 3 | declare module "adm-zip"; 4 | -------------------------------------------------------------------------------- /config/typescript/@types/ethereumjs-abi/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "ethereumjs-abi"; 2 | -------------------------------------------------------------------------------- /config/typescript/@types/solc/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "solc"; 2 | declare module "solc/wrapper"; -------------------------------------------------------------------------------- /config/typescript/@types/solhint/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "solhint/lib/index"; 2 | declare module "solhint/lib/config/config-file"; 3 | -------------------------------------------------------------------------------- /config/typescript/@types/solpp/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "solpp"; 2 | -------------------------------------------------------------------------------- /config/typescript/@types/tabtab/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@fvictorio/tabtab"; 2 | -------------------------------------------------------------------------------- /config/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "sourceMap": true, 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "typeRoots": ["../../node_modules/@types", "./@types"], 11 | "noEmitOnError": true, 12 | "skipDefaultLibCheck": true, 13 | "skipLibCheck": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "version": "0.0.0", 4 | "author": "foundry-rs authors", 5 | "license": "SEE LICENSE IN EACH PACKAGE'S LICENSE FILE", 6 | "private": true, 7 | "workspaces": [ 8 | "packages/*", 9 | "docs" 10 | ], 11 | "devDependencies": { 12 | "@changesets/cli": "^2.22.0", 13 | "prettier": "2.4.1", 14 | "shelljs": "^0.8.5", 15 | "typescript": "~4.5.2", 16 | "wsrun": "^5.2.2" 17 | }, 18 | "scripts": { 19 | "build": "tsc --build packages/hardhat-forge packages/hardhat-anvil packages/hardhat packages/easy-foundryup", 20 | "watch": "tsc --build --watch packages/hardhat-anvil packages/hardhat-forgepackages/hardhat packages/easy-foundryup", 21 | "clean": "wsrun --exclude-missing clean", 22 | "test": "wsrun --exclude-missing test", 23 | "lint": "wsrun --exclude-missing --stages lint && yarn prettier --check", 24 | "lint:fix": "wsrun --exclude-missing --stages lint:fix && yarn prettier --write", 25 | "prettier": "prettier *.md \"{docs,.github}/**/*.{md,yml}\"", 26 | "release": "yarn build && yarn changeset publish" 27 | 28 | }, 29 | "dependencies": {}, 30 | "resolutions": { 31 | "**/antlr4": "4.7.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/easy-foundryup/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [`${__dirname}/../../config/eslint/eslintrc.js`], 3 | parserOptions: { 4 | project: `${__dirname}/tsconfig.json`, 5 | sourceType: "module", 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/easy-foundryup/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | build/ 4 | -------------------------------------------------------------------------------- /packages/easy-foundryup/.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register/files", 3 | "ignore": ["test/fixture-projects/**/*"], 4 | "timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /packages/easy-foundryup/.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | -------------------------------------------------------------------------------- /packages/easy-foundryup/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @foundry-rs/easy-foundryup 2 | 3 | ## 0.1.3 4 | 5 | ### Patch Changes 6 | 7 | - a7bb4be: fix: catch clause can't have any type annotation 8 | 9 | ## 0.1.2 10 | 11 | ### Patch Changes 12 | 13 | - 22f5a65: initial release 14 | 15 | ## 0.1.1 16 | 17 | ### Patch Changes 18 | 19 | - 24c943a: initial release 20 | - 2923385: initial release 21 | -------------------------------------------------------------------------------- /packages/easy-foundryup/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matthias Seitz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/easy-foundryup/README.md: -------------------------------------------------------------------------------- 1 | # easy-foundryup 2 | 3 | install and manage [foundryup](https://github.com/foundry-rs/foundry/#installation). 4 | -------------------------------------------------------------------------------- /packages/easy-foundryup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@foundry-rs/easy-foundryup", 3 | "version": "0.1.3", 4 | "description": "Install and manage foundryup", 5 | "homepage": "https://github.com/foundry-rs/hardhat", 6 | "repository": "github:foundry-rs/hardhat", 7 | "license": "MIT", 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "keywords": [ 11 | "ethereum", 12 | "smart-contracts", 13 | "anvil", 14 | "foundry", 15 | "foundryup" 16 | ], 17 | "scripts": { 18 | "install-foundry": "ts-node ./src/foundryup.ts", 19 | "lint": "yarn prettier --check && yarn eslint", 20 | "lint:fix": "yarn prettier --write && yarn eslint --fix", 21 | "eslint": "eslint 'src/**/*.ts'", 22 | "prettier": "prettier \"**/*.{js,md}\"", 23 | "build": "tsc --build .", 24 | "clean": "rimraf dist", 25 | "test": "mocha --recursive \"test/**/*.ts\" --exit" 26 | }, 27 | "files": [ 28 | "dist/", 29 | "src/", 30 | "LICENSE", 31 | "README.md" 32 | ], 33 | "dependencies": { 34 | "command-exists": "^1.2.9", 35 | "ts-interface-checker": "^0.1.9" 36 | }, 37 | "devDependencies": { 38 | "@types/chai": "^4.2.0", 39 | "@types/debug": "^4.1.4", 40 | "@types/fs-extra": "^5.1.0", 41 | "@types/mocha": "^9.1.0", 42 | "@types/node": "^12.0.0", 43 | "@typescript-eslint/eslint-plugin": "4.29.2", 44 | "@typescript-eslint/parser": "4.29.2", 45 | "chai": "^4.2.0", 46 | "eslint": "^7.29.0", 47 | "eslint-config-prettier": "8.3.0", 48 | "eslint-plugin-import": "2.24.1", 49 | "eslint-plugin-prettier": "3.4.0", 50 | "mocha": "^9.2.0", 51 | "prettier": "2.4.1", 52 | "rimraf": "^3.0.2", 53 | "ts-node": "^8.1.0", 54 | "typescript": "~4.5.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/easy-foundryup/src/binary.ts: -------------------------------------------------------------------------------- 1 | import { exec, execSync, spawn } from "child_process"; 2 | 3 | const os = require("os"); 4 | const path = require("path"); 5 | const commandExists = require("command-exists"); 6 | 7 | const FOUNDRYUP_INSTALLER = 'curl -sSL "https://foundry.paradigm.xyz" | sh'; 8 | 9 | /** 10 | * @returns the path to the anvil path to use, if `anvil` is in path then this will be returned 11 | * 12 | */ 13 | export async function getAnvilCommand(): Promise { 14 | try { 15 | return commandExists("anvil"); 16 | } catch (e) { 17 | const cmd = foundryAnvilBinPath(); 18 | await checkCommand(`${cmd} --version`); 19 | return cmd; 20 | } 21 | } 22 | 23 | /** 24 | * @returns the path to the cast path to use, if `cast` is in path then this will be returned 25 | * 26 | */ 27 | export async function getCastCommand(): Promise { 28 | try { 29 | return commandExists("cast"); 30 | } catch (e) { 31 | const cmd = foundryCastBinPath(); 32 | await checkCommand(`${cmd} --version`); 33 | return cmd; 34 | } 35 | } 36 | 37 | /** 38 | * @returns the path to the forge path to use, if `forge` is in path then this will be returned 39 | * 40 | */ 41 | export async function getForgeCommand(): Promise { 42 | try { 43 | return commandExists("forge"); 44 | } catch (e) { 45 | const cmd = foundryForgeBinPath(); 46 | await checkCommand(`${cmd} --version`); 47 | return cmd; 48 | } 49 | } 50 | 51 | /** 52 | * @returns the path to the forge path to use, if `forge` is in path then this will be returned 53 | * 54 | */ 55 | export function getForgeCommandSync(): string { 56 | if (commandExists.sync("forge")) { 57 | return "forge"; 58 | } else { 59 | const cmd = foundryForgeBinPath(); 60 | checkCommandSync(`${cmd} --version`); 61 | return cmd; 62 | } 63 | } 64 | 65 | /** 66 | * @returns the path to the foundry directory: `$HOME/.foundry` 67 | */ 68 | export function foundryDir(): string { 69 | return path.join(os.homedir(), ".foundry"); 70 | } 71 | 72 | /** 73 | * @returns the path to the foundry directory that stores the tool binaries: `$HOME/.foundry/bin` 74 | */ 75 | export function foundryBinDir(): string { 76 | return path.join(foundryDir(), "bin"); 77 | } 78 | 79 | /** 80 | * @returns the path to the anvil binary in the foundry dir: `$HOME/.foundry/bin/anvil` 81 | */ 82 | export function foundryAnvilBinPath(): string { 83 | return path.join(foundryDir(), "anvil"); 84 | } 85 | 86 | /** 87 | * @returns the path to the cast binary in the foundry dir: `$HOME/.foundry/bin/cast` 88 | */ 89 | export function foundryCastBinPath(): string { 90 | return path.join(foundryDir(), "cast"); 91 | } 92 | 93 | /** 94 | * @returns the path to the anvil forge in the foundry dir: `$HOME/.foundry/bin/forge` 95 | */ 96 | export function foundryForgeBinPath(): string { 97 | return path.join(foundryDir(), "forge"); 98 | } 99 | 100 | /** 101 | * Installs foundryup via subprocess 102 | */ 103 | export async function selfInstall(): Promise { 104 | return new Promise((resolve) => { 105 | const process = spawn("/bin/sh", ["-c", FOUNDRYUP_INSTALLER], { 106 | stdio: "inherit", 107 | }); 108 | process.on("exit", (code) => { 109 | resolve(code === 0); 110 | }); 111 | }); 112 | } 113 | 114 | /** 115 | * Optional target location `foundryup` accepts 116 | */ 117 | export interface FoundryupTarget { 118 | branch?: string; 119 | commit?: string; 120 | repo?: string; 121 | path?: string; 122 | } 123 | 124 | /** 125 | * Executes `foundryup` 126 | * 127 | * @param install whether to install `foundryup` itself 128 | * @param _target additional `foundryup` params 129 | */ 130 | export async function run( 131 | install: boolean = true, 132 | _target: FoundryupTarget = {} 133 | ): Promise { 134 | if (install) { 135 | if (!(await checkFoundryUp())) { 136 | if (!(await selfInstall())) { 137 | return false; 138 | } 139 | } 140 | } 141 | return checkCommand("foundryup"); 142 | } 143 | 144 | /** 145 | * Checks if foundryup exists 146 | * 147 | * @return true if `foundryup` exists 148 | */ 149 | export async function checkFoundryUp(): Promise { 150 | return checkCommand("foundryup --version"); 151 | } 152 | 153 | /** 154 | * Checks if anvil exists 155 | * 156 | * @return true if `anvil` exists 157 | */ 158 | export async function checkAnvil(): Promise { 159 | return checkCommand("anvil --version"); 160 | } 161 | 162 | /** 163 | * Checks if cast exists 164 | * 165 | * @return true if `cast` exists 166 | */ 167 | export async function checkCast(): Promise { 168 | return checkCommand("cast --version"); 169 | } 170 | 171 | /** 172 | * Checks if cast exists 173 | * 174 | * @return true if `cast` exists 175 | */ 176 | export async function checkForge(): Promise { 177 | return checkCommand("forge --version"); 178 | } 179 | 180 | /** 181 | * Executes the given command 182 | * 183 | * @param cmd the command to run 184 | * @return returns true if the command succeeded, false otherwise 185 | */ 186 | async function checkCommand(cmd: string): Promise { 187 | return new Promise((resolve) => { 188 | const process = exec(cmd); 189 | process.on("exit", (code) => { 190 | if (code !== 0) { 191 | console.error( 192 | "Command failed. Is Foundry not installed? Consider installing via `curl -L https://foundry.paradigm.xyz | bash` and then running `foundryup` on a new terminal. For more context, check the installation instructions in the book: https://book.getfoundry.sh/getting-started/installation.html." 193 | ); 194 | } 195 | resolve(code === 0); 196 | }); 197 | }); 198 | } 199 | 200 | /** 201 | * Executes the given command 202 | * 203 | * @param cmd the command to run 204 | * @return returns true if the command succeeded, false otherwise 205 | */ 206 | function checkCommandSync(cmd: string): boolean { 207 | try { 208 | execSync(cmd); 209 | return true; 210 | } catch (error) { 211 | const status = (error as any).status === 0; 212 | if (!status) { 213 | console.error( 214 | "Command failed. Is Foundry not installed? Consider installing via `curl -L https://foundry.paradigm.xyz | bash` and then running `foundryup` on a new terminal. For more context, check the installation instructions in the book: https://book.getfoundry.sh/getting-started/installation.html." 215 | ); 216 | } 217 | return status; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /packages/easy-foundryup/src/foundryup.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | import { run } from "./index"; 5 | 6 | void (async () => { 7 | const success = await run(); 8 | if (success) { 9 | console.log("successfully setup foundryup"); 10 | } else { 11 | console.log("failed to setup foundryup"); 12 | process.exit(1); 13 | } 14 | })(); 15 | -------------------------------------------------------------------------------- /packages/easy-foundryup/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./binary"; 2 | -------------------------------------------------------------------------------- /packages/easy-foundryup/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../config/typescript/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../dist", 5 | "rootDirs": ["."], 6 | "composite": true 7 | }, 8 | "include": ["./**/*.ts"], 9 | "exclude": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/easy-foundryup/test/foundryup.test.ts: -------------------------------------------------------------------------------- 1 | import * as foundryup from "../src"; 2 | 3 | describe("Foundryup command tests", function () { 4 | const installed = false; 5 | 6 | before(async function () { 7 | try { 8 | this.installed = await foundryup.run(true); 9 | } catch (e) { 10 | this.installed = false; 11 | } 12 | }); 13 | 14 | it("Should get commands if installed", async function () { 15 | if (this.installed) { 16 | const _anvil = await foundryup.getAnvilCommand(); 17 | const _cast = await foundryup.getCastCommand(); 18 | const _forge = await foundryup.getForgeCommand(); 19 | } 20 | }); 21 | 22 | it("Should get commands sync", function () { 23 | if (this.installed) { 24 | const _forge = foundryup.getForgeCommandSync(); 25 | } 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/easy-foundryup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../config/typescript/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "exclude": ["./dist", "./node_modules"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/.eslintignore: -------------------------------------------------------------------------------- 1 | test/fixture-projects/**/* 2 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [`${__dirname}/../../config/eslint/eslintrc.js`], 3 | parserOptions: { 4 | project: `${__dirname}/tsconfig.json`, 5 | sourceType: "module", 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | /node_modules 3 | 4 | # Compilation output 5 | /build-test/ 6 | /dist 7 | 8 | # Code coverage artifacts 9 | /coverage 10 | /.nyc_output 11 | 12 | # Below is Github's node gitignore template, 13 | # ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing 14 | 15 | # Logs 16 | logs 17 | *.log 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | lerna-debug.log* 22 | 23 | # Diagnostic reports (https://nodejs.org/api/report.html) 24 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | coverage 37 | 38 | # nyc test coverage 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | .grunt 43 | 44 | # Bower dependency directory (https://bower.io/) 45 | bower_components 46 | 47 | # node-waf configuration 48 | .lock-wscript 49 | 50 | # Compiled binary addons (https://nodejs.org/api/addons.html) 51 | build/Release 52 | 53 | # Dependency directories 54 | #node_modules/ 55 | jspm_packages/ 56 | 57 | # TypeScript v1 declaration files 58 | typings/ 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # next.js build output 79 | .next 80 | 81 | # nuxt.js build output 82 | .nuxt 83 | 84 | # vuepress build output 85 | .vuepress/dist 86 | 87 | # Serverless directories 88 | .serverless/ 89 | 90 | # FuseBox cache 91 | .fusebox/ 92 | 93 | # DynamoDB Local files 94 | .dynamodb/ 95 | 96 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register/files", 3 | "ignore": ["test/fixture-projects/**/*"], 4 | "timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /test/fixture-projects/**/artifacts 4 | /test/fixture-projects/**/cache 5 | CHANGELOG.md 6 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @foundry-rs/hardhat-anvil 2 | 3 | ## 0.1.6 4 | 5 | ### Patch Changes 6 | 7 | - ba96df2: fix flaky tests 8 | 9 | ## 0.1.5 10 | 11 | ### Patch Changes 12 | 13 | - e02aee0: fix flaky tests 14 | 15 | ## 0.1.4 16 | 17 | ### Patch Changes 18 | 19 | - 9ba6d04: use correct output dir for main entry 20 | - 36c829b: fix dependency and anvil command issues 21 | - a7bb4be: fix: catch clause can't have any type annotation 22 | - Updated dependencies [a7bb4be] 23 | - @foundry-rs/easy-foundryup@0.1.3 24 | 25 | ## 0.1.3 26 | 27 | ### Patch Changes 28 | 29 | - 373974f: use correct output dir for main entry 30 | 31 | ## 0.1.2 32 | 33 | ### Patch Changes 34 | 35 | - 22f5a65: initial release 36 | - Updated dependencies [22f5a65] 37 | - @foundry-rs/easy-foundryup@0.1.2 38 | 39 | ## 0.1.1 40 | 41 | ### Patch Changes 42 | 43 | - 24c943a: initial release 44 | - 2923385: initial release 45 | - Updated dependencies [24c943a] 46 | - Updated dependencies [2923385] 47 | - @foundry-rs/easy-foundryup@0.1.1 48 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nomic Labs LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/README.md: -------------------------------------------------------------------------------- 1 | # hardhat-anvil 2 | 3 | This Hardhat plugin automatically starts and stops [Anvil](https://github.com/foundry-rs/foundry/tree/master/anvil) when running tests or scripts. 4 | 5 | ## What 6 | 7 | This plugin creates a network named `anvil`. When this network is used, it can either connect to a running `anvil` instance or launch a new server automatically before running tests and scripts. 8 | 9 | ## Installation 10 | 11 | ### Anvil binary 12 | 13 | See [anvil installation](https://github.com/foundry-rs/foundry/tree/master/anvil#installation) 14 | 15 | ### Plugin 16 | 17 | ```bash 18 | npm install --save-dev @foundry-rs/hardhat-anvil 19 | ``` 20 | 21 | And add the following statement to your `hardhat.config.js`: 22 | 23 | ```js 24 | require("@foundry-rs/hardhat-anvil"); 25 | ``` 26 | 27 | Or, if you are using TypeScript, add this to your `hardhat.config.ts`: 28 | 29 | ```js 30 | import "@foundry-rs/hardhat-anvil"; 31 | ``` 32 | 33 | ## Tasks 34 | 35 | This plugin hooks into the `test`. 36 | 37 | ## Environment extensions 38 | 39 | This plugin adds a `waffle` object to the Hardhat Runtime Environment. This object has all the Waffle functionality, already adapted to work with Hardhat. 40 | 41 | This is a slightly modified, `anvil` compatible version of [`@nomiclabs/hardhat-waffle`](https://github.com/NomicFoundation/hardhat/tree/master/packages/hardhat-waffle). 42 | 43 | ## Usage 44 | 45 | Once `anvil` is installed you can simply run it and configure it via the CLI. 46 | 47 | Once installed, you can build your tests almost like in Waffle. 48 | 49 | Instead of importing things from `ethereum-waffle`, you access them from the `waffle` property of the Hardhat Runtime Environment. 50 | 51 | ## Configuration 52 | 53 | You can set any of the [Anvil's options](https://github.com/foundry-rs/foundry/tree/master/anvil) (or `anvil --help`) through the `anvil` network config. 54 | 55 | **Note: currently only default settings are used if `launch: true`. All other configs are currently ignored** 56 | 57 | It's recommend to spawn `anvil` manually in a separate shell, see also [Foundry repo](https://github.com/foundry-rs/foundry/tree/master/anvil) 58 | 59 | This example sets a larger block gas limit and the default balance of Anvil's accounts. 60 | 61 | ```js 62 | module.exports = { 63 | defaultNetwork: "anvil", 64 | anvil: { 65 | url: "http://127.0.0.1:8545/", 66 | launch: false, // if set to `true`, it will spawn a new instance if the plugin is initialized, if set to `false` it expects an already running anvil instance 67 | } 68 | } 69 | }; 70 | ``` 71 | 72 | By default, the `anvil` object will be configured as 73 | 74 | ```js 75 | { 76 | hdPath: "m/44'/60'/0'/0/", 77 | mnemonic: 'test test test test test test test test test test test junk', 78 | url: 'http://127.0.0.1:8545/', 79 | launch: true, 80 | accounts: { 81 | mnemonic: 'test test test test test test test test test test test junk', 82 | path: "m/44'/60'/0'/0/" 83 | } 84 | } 85 | ``` 86 | 87 | ## LICENSE 88 | 89 | - MIT license ([LICENSE](LICENSE) or https://opensource.org/licenses/MIT) 90 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@foundry-rs/hardhat-anvil", 3 | "version": "0.1.6", 4 | "description": "Hardhat plugin for managing Anvil", 5 | "homepage": "https://github.com/foundry-rs/hardhat", 6 | "repository": "github:foundry-rs/hardhat", 7 | "license": "MIT", 8 | "main": "dist/src/index.js", 9 | "types": "dist/src/index.d.ts", 10 | "keywords": [ 11 | "ethereum", 12 | "smart-contracts", 13 | "hardhat", 14 | "hardhat-plugin", 15 | "anvil", 16 | "foundry", 17 | "testing-network" 18 | ], 19 | "scripts": { 20 | "lint": "yarn prettier --check && yarn eslint", 21 | "lint:fix": "yarn prettier --write && yarn eslint --fix", 22 | "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", 23 | "prettier": "prettier \"**/*.{js,md,json}\"", 24 | "test": "mocha --recursive \"test/**/*.ts\" --exit", 25 | "build": "tsc --build .", 26 | "clean": "rimraf dist" 27 | }, 28 | "files": [ 29 | "dist/", 30 | "src/", 31 | "LICENSE", 32 | "README.md" 33 | ], 34 | "dependencies": { 35 | "@foundry-rs/easy-foundryup": "^0.1.3", 36 | "@nomiclabs/hardhat-ethers": "^2.0.0", 37 | "@nomiclabs/hardhat-waffle": "^2.0.0", 38 | "@types/sinon-chai": "^3.2.3", 39 | "@types/web3": "1.0.19", 40 | "chalk": "^2.4.2", 41 | "debug": "^4.1.1", 42 | "fs-extra": "^7.0.1", 43 | "ethers": "^5.0.0", 44 | "ts-interface-checker": "^0.1.9" 45 | }, 46 | "devDependencies": { 47 | "@nomiclabs/eslint-plugin-hardhat-internal-rules": "^1.0.0", 48 | "@nomiclabs/hardhat-ethers": "^2.0.0", 49 | "@types/chai": "^4.2.0", 50 | "@types/debug": "^4.1.4", 51 | "@types/fs-extra": "^5.1.0", 52 | "@types/mocha": "^9.1.0", 53 | "@types/node": "^12.0.0", 54 | "@typescript-eslint/eslint-plugin": "4.29.2", 55 | "@typescript-eslint/parser": "4.29.2", 56 | "chai": "^4.2.0", 57 | "eslint": "^7.29.0", 58 | "eslint-config-prettier": "8.3.0", 59 | "eslint-plugin-import": "2.24.1", 60 | "eslint-plugin-prettier": "3.4.0", 61 | "ethereum-waffle": "^3.2.0", 62 | "hardhat": "^2.0.0", 63 | "mocha": "^9.2.0", 64 | "prettier": "2.4.1", 65 | "rimraf": "^3.0.2", 66 | "ts-interface-builder": "^0.2.0", 67 | "ts-node": "^8.1.0", 68 | "typescript": "~4.5.2" 69 | }, 70 | "peerDependencies": { 71 | "@nomiclabs/hardhat-ethers": "^2.0.0", 72 | "ethereum-waffle": "^3.2.0", 73 | "ethers": "^5.0.0", 74 | "hardhat": "^2.0.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/anvil-options-ti.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module was automatically generated by `ts-interface-builder` 3 | */ 4 | import * as t from "ts-interface-checker"; 5 | 6 | export const AnvilOptionsTi = t.iface([], { 7 | url: "string", 8 | keepAliveTimeout: t.opt("number"), 9 | accountKeysPath: t.opt("string"), 10 | hostname: t.opt("string"), 11 | accounts: t.opt(t.union(t.array("object"), "object")), 12 | allowUnlimitedContractSize: t.opt("boolean"), 13 | blockTime: t.opt("number"), 14 | dbPath: t.opt("string"), 15 | defaultBalanceEther: t.opt("number"), 16 | forkUrl: t.opt(t.union("string", "object")), 17 | forkBlockNumber: t.opt(t.union("string", "number")), 18 | gasLimit: t.opt("number"), 19 | gasPrice: t.opt(t.union("string", "number")), 20 | hdPath: t.opt("string"), 21 | path: t.opt("string"), 22 | locked: t.opt("boolean"), 23 | noStorageCaching: t.opt("boolean"), 24 | hardfork: t.opt("string"), 25 | logger: t.opt( 26 | t.iface([], { 27 | log: t.func("void", t.param("msg", "string")), 28 | }) 29 | ), 30 | mnemonic: t.opt("string"), 31 | network_id: t.opt("number"), 32 | chainId: t.opt("number"), 33 | port: t.opt("number"), 34 | totalAccounts: t.opt("number"), 35 | unlockedAccounts: t.opt(t.array("string")), 36 | silent: t.opt("boolean"), 37 | vmErrorsOnRPCResponse: t.opt("boolean"), 38 | ws: t.opt("boolean"), 39 | }); 40 | 41 | const exportedTypeSuite: t.ITypeSuite = { 42 | AnvilOptionsTi, 43 | }; 44 | // eslint-disable-next-line import/no-default-export 45 | export default exportedTypeSuite; 46 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/anvil-provider-adapter.ts: -------------------------------------------------------------------------------- 1 | import { providers, Wallet } from "ethers"; 2 | import { normalizeHardhatNetworkAccountsConfig } from "hardhat/internal/core/providers/util"; 3 | import { HardhatNetworkConfig, Network } from "hardhat/types"; 4 | import { NomicLabsHardhatPluginError } from "hardhat/plugins"; 5 | import { pluginName } from "./constants"; 6 | 7 | // This class is an extension of hardhat-ethers' wrapper. 8 | export class AnvilProviderAdapter extends providers.JsonRpcProvider { 9 | constructor(private _hardhatNetwork: Network) { 10 | super(); 11 | } 12 | 13 | public getWallets() { 14 | if (this._hardhatNetwork.name !== "anvil") { 15 | throw new NomicLabsHardhatPluginError( 16 | pluginName, 17 | `This method only works with Anvil. 18 | You can use \`await hre.ethers.getSigners()\` in other networks.` 19 | ); 20 | } 21 | const networkConfig = this._hardhatNetwork.config as HardhatNetworkConfig; 22 | return normalizeHardhatNetworkAccountsConfig(networkConfig.accounts).map( 23 | (acc) => new Wallet(acc.privateKey, this) 24 | ); 25 | } 26 | 27 | public createEmptyWallet() { 28 | return Wallet.createRandom().connect(this); 29 | } 30 | 31 | // Copied from hardhat-ethers 32 | public async send(method: string, params: any): Promise { 33 | const result = await this._hardhatNetwork.provider.send(method, params); 34 | 35 | // We replicate ethers' behavior. 36 | this.emit("debug", { 37 | action: "send", 38 | request: { 39 | id: 42, 40 | jsonrpc: "2.0", 41 | method, 42 | params, 43 | }, 44 | response: result, 45 | provider: this, 46 | }); 47 | 48 | return result; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/anvil-server.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from "child_process"; 2 | import debug from "debug"; 3 | import { getAnvilCommand } from "@foundry-rs/easy-foundryup"; 4 | import { AnvilOptions } from "./anvil-service"; 5 | 6 | const log = debug("hardhat::plugin::anvil::spawn"); 7 | 8 | export class AnvilServer { 9 | private readonly _anvil: any; 10 | private readonly _options: AnvilOptions; 11 | 12 | private constructor(options: AnvilOptions, anvil: any) { 13 | this._options = options; 14 | this._anvil = anvil; 15 | } 16 | 17 | public static async launch( 18 | options: any, 19 | inherit: boolean = false 20 | ): Promise { 21 | log("Launching anvil"); 22 | let anvil: any; 23 | if (options.launch) { 24 | const anvilPath = options.path ?? (await getAnvilCommand()); 25 | const args = []; 26 | if (options.port) { 27 | args.push("--port", options.port); 28 | } 29 | if (options.totalAccounts) { 30 | args.push("--accounts", options.totalAccounts); 31 | } 32 | if (options.mnemonic) { 33 | args.push("--mnemonic", options.mnemonic); 34 | } 35 | if (options.defaultBalanceEther) { 36 | args.push("--balance", options.defaultBalanceEther); 37 | } 38 | if (options.hdPath) { 39 | args.push("--derivation-path", options.hdPath); 40 | } 41 | if (options.silent) { 42 | args.push("--silent", options.silent); 43 | } 44 | if (options.blockTime) { 45 | args.push("--block-time", options.blockTime); 46 | } 47 | if (options.gasLimit) { 48 | args.push("--gas-limit", options.gasLimit); 49 | } 50 | if (options.gasPrice) { 51 | if (options.gasPrice !== "auto") { 52 | args.push("--gas-price", options.gasPrice); 53 | } 54 | } 55 | if (options.chainId) { 56 | args.push("--chain-id", options.chainId); 57 | } 58 | if (options.forkurl) { 59 | args.push("--fork-url", options.forkurl); 60 | if (options.forkBlockNumber) { 61 | args.push("--fork-block-number", options.forkBlockNumber); 62 | } 63 | } 64 | if (options.noStorageCaching) { 65 | args.push("--no-storage-caching"); 66 | } 67 | if (options.hardfork) { 68 | if (options.hardfork !== "arrowGlacier") { 69 | args.push("--hardfork", options.hardfork); 70 | } 71 | } 72 | 73 | const opts = inherit ? { stdio: "inherit" } : {}; 74 | 75 | anvil = spawn(anvilPath, args, opts as any); 76 | 77 | anvil.on("close", (code: any) => { 78 | log(`anvil child process exited with code ${code}`); 79 | }); 80 | 81 | process.on("exit", function () { 82 | anvil.kill(); 83 | }); 84 | 85 | if (!inherit) { 86 | let serverReady = false; 87 | anvil.stdout.on("data", (data: any) => { 88 | const output = data.toString(); 89 | if (output.includes("Listening")) { 90 | serverReady = true; 91 | } 92 | log(`${data}`); 93 | }); 94 | 95 | anvil.stderr.on("data", (data: any) => { 96 | log(`${data}`); 97 | }); 98 | 99 | // wait until server ready 100 | const retries = 30; // 3secs 101 | for (let i = 0; i < retries; i++) { 102 | if (serverReady) { 103 | log("anvil server ready"); 104 | break; 105 | } 106 | await new Promise((resolve) => setTimeout(resolve, 100)); 107 | } 108 | } 109 | } 110 | 111 | return new AnvilServer(options, anvil); 112 | } 113 | 114 | public kill() { 115 | this._anvil?.kill(); 116 | } 117 | 118 | public async waitUntilClosed(): Promise { 119 | return new Promise((resolve) => { 120 | this._anvil.once("close", resolve); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/anvil-service.ts: -------------------------------------------------------------------------------- 1 | import debug from "debug"; 2 | import { NomicLabsHardhatPluginError } from "hardhat/internal/core/errors"; 3 | import { URL } from "url"; 4 | import { AnvilServer } from "./anvil-server"; 5 | 6 | const log = debug("hardhat:plugin:anvil-service"); 7 | 8 | export declare interface AnvilOptions { 9 | url: string; 10 | accountKeysPath?: string; // Translates to: account_keys_path 11 | accounts?: object[] | object; 12 | hostname?: string; 13 | allowUnlimitedContractSize?: boolean; 14 | blockTime?: number; 15 | launch?: boolean; // whether to launch the server at all 16 | defaultBalanceEther?: number; // Translates to: default_balance_ether 17 | forkUrl?: string | object; 18 | forkBlockNumber?: string | number; // Translates to: fork_block_number 19 | gasLimit?: number; 20 | gasPrice?: string | number; 21 | hdPath?: string; // Translates to: hd_path 22 | mnemonic?: string; 23 | path?: string; // path to the anvil exec 24 | locked?: boolean; 25 | noStorageCaching?: boolean; 26 | hardfork?: string; 27 | logger?: { 28 | log(msg: string): void; 29 | }; 30 | chainId?: number; 31 | port?: number; 32 | totalAccounts?: number; // Translates to: total_accounts 33 | silent?: boolean; 34 | vmErrorsOnRPCResponse?: boolean; 35 | ws?: boolean; 36 | } 37 | 38 | const DEFAULT_PORT = 8545; 39 | 40 | export class AnvilService { 41 | public static error?: Error; 42 | 43 | public static getDefaultAccountConfig(): AnvilOptions { 44 | return { 45 | locked: false, 46 | hdPath: "m/44'/60'/0'/0/", 47 | mnemonic: "test test test test test test test test test test test junk", 48 | ...AnvilService.getDefaultOptions(), 49 | }; 50 | } 51 | 52 | public static getDefaultOptions(): AnvilOptions { 53 | return { 54 | url: `http://127.0.0.1:${DEFAULT_PORT}/`, 55 | launch: true, 56 | }; 57 | } 58 | 59 | /** 60 | * 61 | * @param options 62 | * @returns type checked options for `anvil` 63 | */ 64 | public static async getCheckedArgs(options: any): Promise { 65 | // Get and initialize option validator 66 | const { default: optionsSchema } = await import("./anvil-options-ti"); 67 | const { createCheckers } = await import("ts-interface-checker"); 68 | const { AnvilOptionsTi } = createCheckers(optionsSchema); 69 | 70 | // Validate all options against the validator 71 | try { 72 | AnvilOptionsTi.check(options); 73 | } catch (e) { 74 | const err = e as any; 75 | throw new NomicLabsHardhatPluginError( 76 | "@foundry-rs/hardhat-anvil", 77 | `Anvil network config is invalid: ${err.message}`, 78 | err 79 | ); 80 | } 81 | 82 | // Validate and parse hostname and port from URL (this validation is priority) 83 | const url = new URL(options.url); 84 | if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1") { 85 | console.warn("Warning: It's recommended to use Anvil network with 'localhost'."); 86 | } 87 | 88 | return options as AnvilOptions; 89 | } 90 | 91 | public static async create( 92 | options: any, 93 | inherit: boolean = false 94 | ): Promise { 95 | const args = await AnvilService.getCheckedArgs(options); 96 | const Anvil = await AnvilServer.launch(args, inherit); 97 | 98 | return new AnvilService(Anvil, args); 99 | } 100 | 101 | private readonly _server: any; 102 | private readonly _options: any; 103 | 104 | private constructor(Anvil: any, options: any) { 105 | log("Initializing server"); 106 | this._server = Anvil; 107 | this._options = options; 108 | } 109 | 110 | public stopServer() { 111 | this._server.kill(); 112 | } 113 | 114 | public async waitUntilClosed(): Promise { 115 | return this._server.waitUntilClosed(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const pluginName = "@foundry/hardhat-anvil"; 2 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/deploy.ts: -------------------------------------------------------------------------------- 1 | import type { Contract, providers, Signer } from "ethers"; 2 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | 4 | import path from "path"; 5 | 6 | export function getDeployMockContract() { 7 | const wafflePath = require.resolve("ethereum-waffle"); 8 | const waffleMockContractPath = path.dirname( 9 | require.resolve("@ethereum-waffle/mock-contract", { 10 | paths: [wafflePath], 11 | }) 12 | ); 13 | const waffleMockContract = require(waffleMockContractPath); 14 | return waffleMockContract.deployMockContract; 15 | } 16 | 17 | export async function hardhatDeployContract( 18 | hre: HardhatRuntimeEnvironment, 19 | signer: Signer, 20 | contractJSON: any, 21 | args: any[] = [], 22 | overrideOptions: providers.TransactionRequest = {} 23 | ): Promise { 24 | const { deployContract } = require("ethereum-waffle/dist/cjs/deployContract"); 25 | 26 | if ( 27 | overrideOptions.gasLimit === undefined && 28 | typeof hre.network.config.gas === "number" 29 | ) { 30 | overrideOptions.gasLimit = hre.network.config.gas; 31 | } 32 | 33 | return deployContract(signer, contractJSON, args, overrideOptions); 34 | } 35 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/fixtures.ts: -------------------------------------------------------------------------------- 1 | import { MockProvider } from "ethereum-waffle"; 2 | import { providers, Signer } from "ethers"; 3 | 4 | // This file only exists to workaround this: https://github.com/EthWorks/Waffle/issues/281 5 | 6 | type Fixture = ( 7 | signers: Signer[], 8 | provider: providers.JsonRpcProvider 9 | ) => Promise; 10 | interface Snapshot { 11 | fixture: Fixture; 12 | data: T; 13 | id: string; 14 | provider: providers.JsonRpcProvider; 15 | signers: Signer[]; 16 | } 17 | 18 | function createFixtureLoader( 19 | signers: Signer[], 20 | provider: providers.JsonRpcProvider 21 | ) { 22 | const snapshots: Array> = []; 23 | 24 | return async function load(fixture: Fixture): Promise { 25 | const snapshot = snapshots.find((p) => p.fixture === fixture); 26 | 27 | if (snapshot !== undefined) { 28 | await snapshot.provider.send("evm_revert", [snapshot.id]); 29 | snapshot.id = await snapshot.provider.send("evm_snapshot", []); 30 | return snapshot.data; 31 | } 32 | { 33 | const data = await fixture(signers, provider); 34 | const id = await provider.send("evm_snapshot", []); 35 | 36 | snapshots.push({ fixture, data, id, provider, signers }); 37 | return data; 38 | } 39 | }; 40 | } 41 | 42 | export function hardhatCreateFixtureLoader( 43 | hardhatWaffleProvider: MockProvider, 44 | overrideSigners: Signer[], 45 | overrideProvider?: providers.JsonRpcProvider 46 | ) { 47 | return createFixtureLoader( 48 | overrideSigners, 49 | overrideProvider ?? hardhatWaffleProvider 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/index.ts: -------------------------------------------------------------------------------- 1 | import debug from "debug"; 2 | import { 3 | TASK_NODE, 4 | TASK_NODE_GET_PROVIDER, 5 | TASK_RUN, 6 | TASK_TEST, 7 | } from "hardhat/builtin-tasks/task-names"; 8 | import { extendConfig, extendEnvironment, subtask, task } from "hardhat/config"; 9 | import { 10 | EthereumProvider, 11 | HardhatRuntimeEnvironment, 12 | RunSuperFunction, 13 | TaskArguments, 14 | } from "hardhat/types"; 15 | import { lazyObject } from "hardhat/plugins"; 16 | import { HardhatError } from "hardhat/internal/core/errors"; 17 | import { ERRORS } from "hardhat/internal/core/errors-list"; 18 | import { createProvider } from "hardhat/internal/core/providers/construction"; 19 | import { getDeployMockContract, hardhatDeployContract } from "./deploy"; 20 | import { getLinkFunction } from "./link"; 21 | import { initializeWaffleMatchers } from "./matchers"; 22 | import "./type-extensions"; 23 | import { AnvilService } from "./anvil-service"; 24 | 25 | export const ANVIL_NETWORK_NAME = "anvil"; 26 | 27 | const log = debug("hardhat:plugin:anvil"); 28 | 29 | extendEnvironment((hre) => { 30 | // This mimics the `hardhat-waffle` plugin so that it works as replacement for 31 | // it this is currently necessary because the waffle plugin is bound to a network 32 | // with the name `hardhat` 33 | (hre as any).waffle = lazyObject(() => { 34 | const { AnvilProviderAdapter } = require("./anvil-provider-adapter"); 35 | 36 | const { hardhatCreateFixtureLoader } = require("./fixtures"); 37 | 38 | const anvilWaffleProvider = new AnvilProviderAdapter(hre.network) as any; 39 | 40 | return { 41 | provider: anvilWaffleProvider, 42 | deployContract: hardhatDeployContract.bind(undefined, hre), 43 | deployMockContract: getDeployMockContract(), 44 | solidity: require("./waffle-chai").waffleChai, 45 | createFixtureLoader: hardhatCreateFixtureLoader.bind( 46 | undefined, 47 | anvilWaffleProvider 48 | ), 49 | loadFixture: hardhatCreateFixtureLoader(anvilWaffleProvider), 50 | link: getLinkFunction(), 51 | }; 52 | }); 53 | 54 | initializeWaffleMatchers(hre.config.paths.root); 55 | }); 56 | 57 | task(TASK_TEST, async (_args, env, runSuper) => { 58 | return handlePluginTask(env, runSuper); 59 | }); 60 | 61 | task(TASK_RUN, async (_args, env, runSuper) => { 62 | return handlePluginTask(env, runSuper); 63 | }); 64 | 65 | task(TASK_NODE, "Starts Anvil RPC server").setAction( 66 | async (opts: any, { hardhatArguments, network }) => { 67 | // we throw if the user specified a network argument and it's not hardhat 68 | if ( 69 | network.name !== ANVIL_NETWORK_NAME && 70 | hardhatArguments.network !== undefined 71 | ) { 72 | throw new HardhatError(ERRORS.BUILTIN_TASKS.JSONRPC_UNSUPPORTED_NETWORK); 73 | } 74 | 75 | const args = await AnvilService.getCheckedArgs({ 76 | ...AnvilService.getDefaultAccountConfig(), 77 | ...opts, 78 | ...network.config, 79 | }); 80 | 81 | // launch anvil 82 | const server = await AnvilService.create(args, true); 83 | try { 84 | await server.waitUntilClosed(); 85 | } catch (error) { 86 | if (HardhatError.isHardhatError(error)) { 87 | throw error; 88 | } 89 | 90 | if (error instanceof Error) { 91 | throw new HardhatError( 92 | ERRORS.BUILTIN_TASKS.JSONRPC_SERVER_ERROR, 93 | { 94 | error: error.message, 95 | }, 96 | error 97 | ); 98 | } 99 | 100 | // eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error 101 | throw error; 102 | } 103 | } 104 | ); 105 | 106 | subtask(TASK_NODE_GET_PROVIDER).setAction( 107 | async ( 108 | { 109 | forkBlockNumber: forkBlockNumberParam, 110 | forkUrl: forkUrlParam, 111 | }: { 112 | forkBlockNumber?: number; 113 | forkUrl?: string; 114 | }, 115 | { artifacts, config, network, userConfig } 116 | ): Promise => { 117 | let provider = network.provider; 118 | 119 | if (network.name !== ANVIL_NETWORK_NAME) { 120 | const networkConfig = config.networks[ANVIL_NETWORK_NAME]; 121 | 122 | log(`Creating hardhat provider for JSON-RPC server`); 123 | provider = createProvider( 124 | ANVIL_NETWORK_NAME, 125 | networkConfig, 126 | config.paths, 127 | artifacts 128 | ); 129 | } 130 | 131 | const anvilConfig = config.networks[ANVIL_NETWORK_NAME] as any; 132 | 133 | const forkUrlConfig = anvilConfig?.forking?.url; 134 | const forkBlockNumberConfig = anvilConfig?.forking?.blockNumber; 135 | 136 | const forkUrl = forkUrlParam ?? forkUrlConfig; 137 | const forkBlockNumber = forkBlockNumberParam ?? forkBlockNumberConfig; 138 | 139 | // we throw an error if the user specified a forkBlockNumber but not a 140 | // forkUrl 141 | if (forkBlockNumber !== undefined && forkUrl === undefined) { 142 | throw new HardhatError( 143 | ERRORS.BUILTIN_TASKS.NODE_FORK_BLOCK_NUMBER_WITHOUT_URL 144 | ); 145 | } 146 | 147 | // if the url or the block is different to the one in the configuration, 148 | // we use hardhat_reset to set the fork 149 | if ( 150 | forkUrl !== forkUrlConfig || 151 | forkBlockNumber !== forkBlockNumberConfig 152 | ) { 153 | await provider.request({ 154 | method: "anvil_reset", 155 | params: [ 156 | { 157 | forking: { 158 | jsonRpcUrl: forkUrl, 159 | blockNumber: forkBlockNumber, 160 | }, 161 | }, 162 | ], 163 | }); 164 | } 165 | 166 | const anvilUserConfig = 167 | userConfig.networks?.[ANVIL_NETWORK_NAME] ?? ({} as any); 168 | 169 | // enable logging 170 | await provider.request({ 171 | method: "anvil_setLoggingEnabled", 172 | params: [anvilUserConfig?.loggingEnabled ?? true], 173 | }); 174 | 175 | return provider; 176 | } 177 | ); 178 | 179 | extendConfig((resolvedConfig: any, config: any) => { 180 | const defaultOptions = AnvilService.getDefaultAccountConfig(); 181 | if (config.networks && config.networks.anvil) { 182 | const customOptions = config.networks.anvil; 183 | resolvedConfig.networks.anvil = { ...defaultOptions, ...customOptions }; 184 | } else { 185 | resolvedConfig.networks.anvil = defaultOptions; 186 | } 187 | // make compatible with the hardhat accounts object used by the waffleprovider 188 | resolvedConfig.networks.anvil.accounts = { 189 | mnemonic: resolvedConfig.networks.anvil.mnemonic, 190 | path: resolvedConfig.networks.anvil.hdPath, 191 | initialIndex: 0, 192 | count: resolvedConfig.networks.anvil.totalAccounts, 193 | passphrase: "", 194 | }; 195 | }); 196 | 197 | async function handlePluginTask( 198 | env: HardhatRuntimeEnvironment, 199 | runSuper: RunSuperFunction 200 | ) { 201 | if (env.network.name !== "anvil") { 202 | return runSuper(); 203 | } 204 | log("Starting Anvil"); 205 | 206 | const options = env.network.config; 207 | const anvilService = await AnvilService.create(options); 208 | 209 | let ret; 210 | try { 211 | ret = await runSuper(); 212 | } catch (error) { 213 | log("Stopping Anvil after error"); 214 | anvilService.stopServer(); 215 | throw error; 216 | } 217 | 218 | log("Stopping Anvil"); 219 | anvilService.stopServer(); 220 | 221 | return ret; 222 | } 223 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/link.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | export function getLinkFunction() { 4 | const wafflePath = require.resolve("ethereum-waffle"); 5 | const waffleCompilerPath = path.dirname( 6 | require.resolve("@ethereum-waffle/compiler", { 7 | paths: [wafflePath], 8 | }) 9 | ); 10 | 11 | const waffleCompiler = require(path.join(waffleCompilerPath, "link")); 12 | return waffleCompiler.link; 13 | } 14 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/matchers.ts: -------------------------------------------------------------------------------- 1 | export function initializeWaffleMatchers(projectRoot: string) { 2 | try { 3 | // We use the projectRoot to guarantee that we are using the user's 4 | // installed version of chai 5 | const chaiPath = require.resolve("chai", { 6 | paths: [projectRoot], 7 | }); 8 | 9 | const chai = require(chaiPath); 10 | const { waffleChai } = require("./waffle-chai"); 11 | 12 | chai.use(waffleChai); 13 | } catch { 14 | // If chai isn't installed we just don't initialize the matchers 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/type-extensions.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | createFixtureLoader, 3 | link, 4 | loadFixture, 5 | MockContract, 6 | MockProvider, 7 | solidity, 8 | } from "ethereum-waffle"; 9 | import type { ContractJSON } from "ethereum-waffle/dist/esm/ContractJSON"; 10 | import type { Contract, providers, Signer } from "ethers"; 11 | 12 | import "hardhat/types/runtime"; 13 | 14 | declare module "hardhat/types/runtime" { 15 | interface HardhatRuntimeEnvironment { 16 | waffle: { 17 | provider: MockProvider; 18 | deployContract: ( 19 | signer: Signer, 20 | contractJSON: ContractJSON, 21 | args?: any[], 22 | overrideOptions?: providers.TransactionRequest 23 | ) => Promise; 24 | solidity: typeof solidity; 25 | link: typeof link; 26 | deployMockContract: (signer: Signer, abi: any[]) => Promise; 27 | createFixtureLoader: typeof createFixtureLoader; 28 | loadFixture: typeof loadFixture; 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/src/waffle-chai.ts: -------------------------------------------------------------------------------- 1 | // This file only exists to workaround this: https://github.com/EthWorks/Waffle/issues/281 2 | 3 | import path from "path"; 4 | 5 | /// 6 | 7 | export function waffleChai(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils) { 8 | const wafflePath = require.resolve("ethereum-waffle"); 9 | const waffleChaiPath = path.dirname( 10 | require.resolve("@ethereum-waffle/chai", { 11 | paths: [wafflePath], 12 | }) 13 | ); 14 | 15 | const { supportBigNumber } = require(`${waffleChaiPath}/matchers/bigNumber`); 16 | const { 17 | supportChangeBalance, 18 | } = require(`${waffleChaiPath}/matchers/changeBalance`); 19 | const { 20 | supportChangeBalances, 21 | } = require(`${waffleChaiPath}/matchers/changeBalances`); 22 | const { 23 | supportChangeEtherBalance, 24 | } = require(`${waffleChaiPath}/matchers/changeEtherBalance`); 25 | const { 26 | supportChangeEtherBalances, 27 | } = require(`${waffleChaiPath}/matchers/changeEtherBalances`); 28 | const { 29 | supportChangeTokenBalance, 30 | } = require(`${waffleChaiPath}/matchers/changeTokenBalance`); 31 | const { 32 | supportChangeTokenBalances, 33 | } = require(`${waffleChaiPath}/matchers/changeTokenBalances`); 34 | const { supportEmit } = require(`${waffleChaiPath}/matchers/emit`); 35 | const { 36 | supportProperAddress, 37 | } = require(`${waffleChaiPath}/matchers/properAddress`); 38 | const { supportProperHex } = require(`${waffleChaiPath}/matchers/properHex`); 39 | const { 40 | supportProperPrivateKey, 41 | } = require(`${waffleChaiPath}/matchers/properPrivateKey`); 42 | const { supportReverted } = require(`${waffleChaiPath}/matchers/reverted`); 43 | const { 44 | supportRevertedWith, 45 | } = require(`${waffleChaiPath}/matchers/revertedWith`); 46 | const { supportHexEqual } = require(`${waffleChaiPath}/matchers/hexEqual`); 47 | 48 | supportBigNumber(chai.Assertion, utils); 49 | supportReverted(chai.Assertion); 50 | supportRevertedWith(chai.Assertion); 51 | supportEmit(chai.Assertion); 52 | supportProperAddress(chai.Assertion); 53 | supportProperPrivateKey(chai.Assertion); 54 | supportProperHex(chai.Assertion); 55 | supportHexEqual(chai.Assertion); 56 | supportChangeBalance(chai.Assertion); 57 | supportChangeBalances(chai.Assertion); 58 | supportChangeEtherBalance(chai.Assertion); 59 | supportChangeEtherBalances(chai.Assertion); 60 | supportChangeTokenBalance(chai.Assertion); 61 | supportChangeTokenBalances(chai.Assertion); 62 | supportCalledOnContract(chai.Assertion); 63 | supportCalledOnContractWith(chai.Assertion); 64 | } 65 | 66 | function supportCalledOnContract(Assertion: Chai.AssertionStatic) { 67 | // eslint-disable-next-line import/no-extraneous-dependencies 68 | const Chai = require("chai"); 69 | Assertion.addMethod("calledOnContract", function (_contract: any) { 70 | throw new Chai.AssertionError( 71 | "Waffle's calledOnContract is not supported by Hardhat" 72 | ); 73 | }); 74 | } 75 | 76 | function supportCalledOnContractWith(Assertion: Chai.AssertionStatic) { 77 | // eslint-disable-next-line import/no-extraneous-dependencies 78 | const Chai = require("chai"); 79 | Assertion.addMethod("calledOnContractWith", function (_contract: any) { 80 | throw new Chai.AssertionError( 81 | "Waffle's calledOnContractWith is not supported by Hardhat" 82 | ); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [`${__dirname}/../.eslintrc.js`], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | devDependencies: true, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/fixture-projects/hardhat-project-with-configs/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "../../../src/index"; 2 | 3 | export default { 4 | defaultNetwork: "anvil", 5 | networks: { 6 | anvil: { 7 | url: "http://127.0.0.1:8545/", 8 | launch: true 9 | }, 10 | }, 11 | solidity: "0.8.10", 12 | }; 13 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/fixture-projects/hardhat-project-with-configs/scripts/custom-accounts-sample.js: -------------------------------------------------------------------------------- 1 | // We require the Hardhat Runtime Environment explicitly here. This is optional. 2 | const env = require("hardhat"); 3 | 4 | async function main() { 5 | const customConfigs = require("../hardhat.config.ts").default; 6 | const customOptions = customConfigs.networks.anvil; 7 | 8 | const accounts = await env.waffle.provider.send("eth_accounts"); 9 | 10 | // Test for existence 11 | if (!accounts) { 12 | throw new Error("Accounts not detected"); 13 | } 14 | 15 | // Test for validity of all data 16 | 17 | // Test for: totalAccounts 18 | if (accounts.length !== customOptions.totalAccounts) { 19 | throw new Error("Invalid: total accounts"); 20 | } 21 | 22 | // Test for: defaultBalanceEther 23 | for (const account of accounts) { 24 | const accParams = [account, "latest"]; 25 | const balance = await env.waffle.provider.send("eth_getBalance", accParams); 26 | const defaultBalanceWei = customOptions.defaultBalanceEther * 10 ** 18; 27 | const accBalanceWei = parseInt(balance, 16); 28 | 29 | if (accBalanceWei !== defaultBalanceWei) { 30 | throw new Error("Invalid: default balance"); 31 | } 32 | } 33 | } 34 | 35 | main() 36 | .then(() => process.exit(0)) 37 | .catch((error) => { 38 | console.error(error); 39 | process.exit(1); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/fixture-projects/hardhat-project/.gitignore: -------------------------------------------------------------------------------- 1 | # Hardhat build files 2 | artifacts/ 3 | cache/ -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/fixture-projects/hardhat-project/contracts/EVMInspector.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.10; 2 | 3 | contract EVMConsumer { 4 | 5 | uint256 theChainID; 6 | 7 | constructor() public { 8 | theChainID = 10; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/fixture-projects/hardhat-project/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "../../../src/index"; 2 | 3 | export default { 4 | solidity: "0.8.10", 5 | defaultNetwork: "anvil", 6 | networks: { 7 | anvil: { 8 | url: "http://127.0.0.1:8545/", 9 | launch: true 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/fixture-projects/hardhat-project/scripts/accounts-sample.js: -------------------------------------------------------------------------------- 1 | // We require the Hardhat Runtime Environment explicitly here. This is optional. 2 | const env = require("hardhat"); 3 | 4 | async function main() { 5 | const accounts = await env.waffle.provider.send("eth_accounts"); 6 | 7 | // Test for existence 8 | if (!accounts) { 9 | throw new Error("Accounts not detected"); 10 | } 11 | 12 | // Test for validity of all data 13 | if (accounts.length !== 10) { 14 | throw new Error("Invalid Accounts amount"); 15 | } 16 | } 17 | 18 | main() 19 | .then(() => process.exit(0)) 20 | .catch((error) => { 21 | console.error(error); 22 | process.exit(1); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/fixture-projects/hardhat-project/scripts/delayed-sample.js: -------------------------------------------------------------------------------- 1 | const env = require("hardhat"); 2 | 3 | async function main() { 4 | const accounts = await env.waffle.provider.send("eth_accounts"); 5 | await delay(0.2); 6 | const accountsAux = await env.waffle.provider.send("eth_accounts"); 7 | 8 | // Test for existence 9 | if (!accounts || !accountsAux) { 10 | throw new Error("Accounts not detected"); 11 | } 12 | 13 | // Test for validity of all data 14 | if (accounts.length !== 10 || accountsAux.length !== 10) { 15 | throw new Error("Invalid Accounts amount"); 16 | } 17 | } 18 | 19 | const delay = (seg) => new Promise((res) => setTimeout(res, seg * 1000)); 20 | 21 | main() 22 | .then(() => process.exit(0)) 23 | .catch((error) => { 24 | console.error(error); 25 | process.exit(1); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/fixture-projects/hardhat-project/test/test.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | 3 | describe("Tests using the anvil plugin", function () { 4 | it("Anvil must be accessible", async function () { 5 | const accounts = await network.provider.send("eth_accounts"); 6 | assert(accounts.length !== 0, "No account was returned"); 7 | }); 8 | 9 | it("can send transaction", async function () { 10 | const accounts = waffle.provider.getWallets(); 11 | const EVMConsumer = await artifacts.readArtifact("EVMConsumer"); 12 | const tx = await network.provider.send("eth_sendTransaction", [ 13 | { 14 | from: accounts[0], 15 | data: EVMConsumer.bytecode, 16 | }, 17 | ]); 18 | }); 19 | 20 | it("can send transaction with env provider", async function () { 21 | const accounts = await network.provider.send("eth_accounts"); 22 | const EVMConsumer = await artifacts.readArtifact("EVMConsumer"); 23 | const tx = await network.provider.send("eth_sendTransaction", [ 24 | { 25 | from: accounts[0], 26 | data: EVMConsumer.bytecode, 27 | }, 28 | ]); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/helpers.ts: -------------------------------------------------------------------------------- 1 | import { resetHardhatContext } from "hardhat/plugins-testing"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import path from "path"; 4 | import net from "net"; 5 | 6 | declare module "mocha" { 7 | interface Context { 8 | env: HardhatRuntimeEnvironment; 9 | } 10 | } 11 | 12 | export function useEnvironment( 13 | fixtureProjectName: string, 14 | networkName = "localhost" 15 | ) { 16 | beforeEach("Loading hardhat environment", async function () { 17 | process.chdir(path.join(__dirname, "fixture-projects", fixtureProjectName)); 18 | process.env.HARDHAT_NETWORK = networkName; 19 | 20 | this.env = require("hardhat"); 21 | this.freePort = await getPortFree(); 22 | }); 23 | 24 | afterEach("Resetting hardhat", function () { 25 | resetHardhatContext(); 26 | }); 27 | } 28 | 29 | async function getPortFree() { 30 | return new Promise((res) => { 31 | const srv = net.createServer() as any; 32 | srv.listen(0, () => { 33 | const port = srv.address().port; 34 | srv.close((_err: any) => res(port)); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/test/index.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | 3 | import { AnvilService } from "../src/anvil-service"; 4 | 5 | import { useEnvironment } from "./helpers"; 6 | 7 | describe("Anvil plugin with empty configs", function () { 8 | useEnvironment("hardhat-project", "anvil"); 9 | 10 | it("Should add anvil network to the config", function () { 11 | assert.isDefined(this.env.config.networks.anvil); 12 | }); 13 | 14 | it("Should expose anvil defaults configs in hardhat's config", function () { 15 | assert.isDefined(this.env.config.networks.anvil); 16 | const defaultOptions = AnvilService.getDefaultOptions() as any; 17 | const options = this.env.config.networks.anvil as any; 18 | 19 | // Iterate over all default options and assert equality 20 | for (const [key, value] of Object.entries(defaultOptions)) { 21 | assert.equal(options[key], value); 22 | } 23 | }); 24 | 25 | it("Should run Hardhat TEST task using Anvil", async function () { 26 | const failures = await this.env.run("test", { 27 | testFiles: [], 28 | port: this.freePort, 29 | }); 30 | 31 | assert.equal(failures, 0); 32 | }); 33 | 34 | it("Should run Hardhat RUN task 'accounts-sample.js' using Anvil", async function () { 35 | await this.env.run("run", { 36 | noCompile: true, 37 | script: "scripts/accounts-sample.js", 38 | port: this.freePort, 39 | }); 40 | 41 | assert.equal(process.exitCode, 0); 42 | }); 43 | 44 | it("Should run Hardhat RUN task 'delayed-sample.js' using Anvil", async function () { 45 | await this.env.run("run", { 46 | noCompile: true, 47 | script: "scripts/delayed-sample.js", 48 | port: this.freePort, 49 | }); 50 | 51 | assert.equal(process.exitCode, 0); 52 | }); 53 | }); 54 | 55 | describe("Anvil plugin with custom configs", function () { 56 | useEnvironment("hardhat-project-with-configs", "anvil"); 57 | 58 | it("Should add anvil network to hardhat's config", function () { 59 | assert.isDefined(this.env.config.networks.anvil); 60 | }); 61 | 62 | it("Should load custom configs in hardhat's config'", function () { 63 | assert.isDefined(this.env.config.networks.anvil); 64 | const customConfigs = 65 | require("./fixture-projects/hardhat-project-with-configs/hardhat.config.ts").default; 66 | 67 | assert.isDefined(customConfigs.networks.anvil); 68 | const customOptions = customConfigs.networks.anvil; 69 | 70 | const options = this.env.config.networks.anvil as any; 71 | 72 | // Iterate over all custom options and assert equality 73 | for (const [key, value] of Object.entries(customOptions)) { 74 | assert.equal(options[key], value); 75 | } 76 | }); 77 | 78 | it("Should expose merged (custom + defaults) configs in hardhat's config", function () { 79 | assert.isDefined(this.env.config.networks.anvil); 80 | const customConfigs = 81 | require("./fixture-projects/hardhat-project-with-configs/hardhat.config.ts").default; 82 | const defaultOptions = AnvilService.getDefaultOptions() as any; 83 | 84 | assert.isDefined(customConfigs.networks.anvil); 85 | const customOptions = customConfigs.networks.anvil; 86 | const mergedOptions = { ...defaultOptions, ...customOptions }; 87 | 88 | const options = this.env.config.networks.anvil as any; 89 | 90 | // Iterate over all custom options and assert equality 91 | for (const [key, value] of Object.entries(mergedOptions)) { 92 | assert.equal(options[key], value); 93 | } 94 | }); 95 | 96 | it("Should add run anvil node", async function () { 97 | void this.env.run("node", { port: this.freePort }); 98 | // ensure we don't wait forever 99 | await new Promise((resolve) => setTimeout(resolve, 5000)); 100 | }); 101 | 102 | // it("Should run Hardhat RUN task using Anvil with custom configs", async function () { 103 | // await this.env.run("run", { 104 | // noCompile: true, 105 | // script: "scripts/custom-accounts-sample.js", 106 | // }); 107 | 108 | // assert.equal(process.exitCode, 0); 109 | // }); 110 | }); 111 | -------------------------------------------------------------------------------- /packages/hardhat-anvil/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../config/typescript/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "exclude": ["./dist", "./node_modules", "./test/**/hardhat.config.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/hardhat-forge/.eslintignore: -------------------------------------------------------------------------------- 1 | test/fixture-projects/**/* 2 | -------------------------------------------------------------------------------- /packages/hardhat-forge/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [`${__dirname}/../../config/eslint/eslintrc.js`], 3 | parserOptions: { 4 | project: `${__dirname}/tsconfig.json`, 5 | sourceType: "module", 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/hardhat-forge/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | out/ 3 | dist/ 4 | build/ 5 | artifacts/ 6 | cache/ 7 | !src/forge/build 8 | build-info 9 | -------------------------------------------------------------------------------- /packages/hardhat-forge/.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register/files", 3 | "ignore": ["test/fixture-projects/**/*"], 4 | "timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /packages/hardhat-forge/.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /test/fixture-projects/**/artifacts 4 | /test/fixture-projects/**/cache 5 | CHANGELOG.md 6 | -------------------------------------------------------------------------------- /packages/hardhat-forge/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @foundry-rs/hardhat-forge 2 | 3 | ## 0.1.17 4 | 5 | ### Patch Changes 6 | 7 | - a032ce6: Ignore metadata files when getting contract artifacts 8 | 9 | ## 0.1.16 10 | 11 | ### Patch Changes 12 | 13 | - 7f7e14f: Handle case insensitivity in filesystem 14 | 15 | ## 0.1.15 16 | 17 | ### Patch Changes 18 | 19 | - 26cccf8: Handle writing artifacts when multiple contracts have the same name 20 | 21 | ## 0.1.14 22 | 23 | ### Patch Changes 24 | 25 | - 0395e08: Add build_info_path config 26 | - f2d3db9: Deserialize compiler output metadata in buildinfo 27 | 28 | ## 0.1.13 29 | 30 | ### Patch Changes 31 | 32 | - 8fe943c: Add build_info_path config 33 | 34 | ## 0.1.12 35 | 36 | ### Patch Changes 37 | 38 | - 2d33cef: Add config for writing artifacts to allow conditional writing of artifacts 39 | 40 | ## 0.1.11 41 | 42 | ### Patch Changes 43 | 44 | - 68a4772: Allow user configured `runSuper` to be called in the build task 45 | 46 | ## 0.1.10 47 | 48 | ### Patch Changes 49 | 50 | - 648d494: Make foundry config lazy 51 | 52 | ## 0.1.9 53 | 54 | ### Patch Changes 55 | 56 | - d3a6a10: Add `foundry` section to hardhat config 57 | - b53d145: Fix hh style foundry config 58 | 59 | ## 0.1.8 60 | 61 | ### Patch Changes 62 | 63 | - 7e193d4: Add deprecated private method for typechain compat 64 | - 7ed40a4: Use the correct source name in the artifact 65 | - bb320b3: Better `BuildInfo` support 66 | - 44c261c: Implement `Artifacts.getBuildInfo` 67 | 68 | ## 0.1.7 69 | 70 | ### Patch Changes 71 | 72 | - e02aee0: Add deprecated private method for typechain compat 73 | - e02aee0: Use the correct source name in the artifact 74 | - e02aee0: Implement `Artifacts.getBuildInfo` 75 | 76 | ## 0.1.6 77 | 78 | ### Patch Changes 79 | 80 | - d6f61aa: Add deprecated private method for typechain compat 81 | - d6f61aa: Implement `Artifacts.getBuildInfo` 82 | 83 | ## 0.1.5 84 | 85 | ### Patch Changes 86 | 87 | - fda4d65: Add deprecated private method for typechain compat 88 | 89 | ## 0.1.4 90 | 91 | ### Patch Changes 92 | 93 | - 7d3f9f3: fix: install missing dependency 94 | - 9ba6d04: use correct output dir for main entry 95 | - 64750ac: Enable downstream plugins to work with compile 96 | - 736941e: Update artifact so that JSON will be ordered the same way that hh orders the keys 97 | - b092fe5: Write hh style artifacts to disk 98 | - Updated dependencies [a7bb4be] 99 | - @foundry-rs/easy-foundryup@0.1.3 100 | 101 | ## 0.1.3 102 | 103 | ### Patch Changes 104 | 105 | - 373974f: use correct output dir for main entry 106 | 107 | ## 0.1.2 108 | 109 | ### Patch Changes 110 | 111 | - 22f5a65: initial release 112 | - Updated dependencies [22f5a65] 113 | - @foundry-rs/easy-foundryup@0.1.2 114 | 115 | ## 0.1.1 116 | 117 | ### Patch Changes 118 | 119 | - 24c943a: initial release 120 | - 2923385: initial release 121 | - Updated dependencies [24c943a] 122 | - Updated dependencies [2923385] 123 | - @foundry-rs/easy-foundryup@0.1.1 124 | -------------------------------------------------------------------------------- /packages/hardhat-forge/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nomic Labs LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/hardhat-forge/README.md: -------------------------------------------------------------------------------- 1 | # hardhat-forge 2 | 3 | This Hardhat plugin is for [forge](https://github.com/foundry-rs/foundry/tree/master/forge). 4 | 5 | ## What 6 | 7 | This plugin provides bindings for `forge` commands as hardhat tasks. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | npm install --save-dev @foundry-rs/hardhat-forge 13 | ``` 14 | 15 | And add the following statement to your `hardhat.config.js`: 16 | 17 | ```js 18 | require("@foundry-rs/hardhat-forge"); 19 | ``` 20 | 21 | Or, if you are using TypeScript, add this to your `hardhat.config.ts`: 22 | 23 | ```js 24 | import "@foundry-rs/hardhat-forge"; 25 | ``` 26 | 27 | ## Tasks 28 | 29 | This plugin provides the following tasks: 30 | 31 | - "forge::build" for "`forge build` 32 | - "forge::test" for "`forge test` 33 | 34 | ## Configuration 35 | 36 | See [Foundry](https://github.com/foundry-rs/foundry). 37 | 38 | ## LICENSE 39 | 40 | - MIT license ([LICENSE](LICENSE) or https://opensource.org/licenses/MIT) 41 | -------------------------------------------------------------------------------- /packages/hardhat-forge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@foundry-rs/hardhat-forge", 3 | "version": "0.1.17", 4 | "description": "Hardhat plugin for foundry's forge", 5 | "homepage": "https://github.com/foundry-rs/hardhat", 6 | "repository": "github:foundry-rs/hardhat", 7 | "license": "MIT", 8 | "main": "dist/src/index.js", 9 | "types": "dist/src/index.d.ts", 10 | "keywords": [ 11 | "ethereum", 12 | "smart-contracts", 13 | "hardhat", 14 | "hardhat-plugin", 15 | "forge", 16 | "foundry", 17 | "solidity" 18 | ], 19 | "scripts": { 20 | "lint": "yarn prettier --check && yarn eslint", 21 | "lint:fix": "yarn prettier --write && yarn eslint --fix", 22 | "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", 23 | "prettier": "prettier \"**/*.{js,md}\"", 24 | "test": "mocha --recursive \"test/**/*.ts\" --exit", 25 | "build": "tsc --build .", 26 | "clean": "rimraf dist test/fixture-projects/hardhat-project/{artifacts,cache,out}" 27 | }, 28 | "files": [ 29 | "dist/", 30 | "src/", 31 | "LICENSE", 32 | "README.md" 33 | ], 34 | "dependencies": { 35 | "@foundry-rs/easy-foundryup": "^0.1.3", 36 | "@nomiclabs/hardhat-ethers": "^2.0.0", 37 | "@nomiclabs/hardhat-waffle": "^2.0.0", 38 | "@types/sinon-chai": "^3.2.3", 39 | "@types/web3": "1.0.19", 40 | "camelcase-keys": "7.0.2", 41 | "debug": "^4.1.1", 42 | "ethers": "^5.0.0", 43 | "fs-extra": "^10.1.0", 44 | "glob": "^7.1.3", 45 | "true-case-path": "^2.2.1", 46 | "ts-interface-checker": "^0.1.9" 47 | }, 48 | "devDependencies": { 49 | "@nomiclabs/eslint-plugin-hardhat-internal-rules": "^1.0.0", 50 | "@nomiclabs/hardhat-ethers": "^2.0.0", 51 | "@types/chai": "^4.2.0", 52 | "@types/debug": "^4.1.4", 53 | "@types/fs-extra": "^5.1.0", 54 | "@types/mocha": "^9.1.0", 55 | "@types/node": "^12.0.0", 56 | "@typescript-eslint/eslint-plugin": "4.29.2", 57 | "@typescript-eslint/parser": "4.29.2", 58 | "chai": "^4.2.0", 59 | "eslint": "^7.29.0", 60 | "eslint-config-prettier": "8.3.0", 61 | "eslint-plugin-import": "2.24.1", 62 | "eslint-plugin-prettier": "3.4.0", 63 | "ethereum-waffle": "^3.2.0", 64 | "hardhat": "^2.0.0", 65 | "mocha": "^9.2.0", 66 | "prettier": "2.4.1", 67 | "rimraf": "^3.0.2", 68 | "ts-interface-builder": "^0.2.3", 69 | "ts-node": "^8.1.0", 70 | "typescript": "~4.5.2" 71 | }, 72 | "peerDependencies": { 73 | "@nomiclabs/hardhat-ethers": "^2.0.0", 74 | "ethereum-waffle": "^3.2.0", 75 | "ethers": "^5.0.0", 76 | "hardhat": "^2.0.0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/artifacts.ts: -------------------------------------------------------------------------------- 1 | // forge specific artifacts handler 2 | 3 | import fsExtra from "fs-extra"; 4 | import * as os from "os"; 5 | import * as path from "path"; 6 | 7 | import { 8 | Artifact, 9 | Artifacts as IArtifacts, 10 | BuildInfo, 11 | CompilerInput, 12 | CompilerOutput, 13 | } from "hardhat/types"; 14 | import { 15 | findDistance, 16 | getFullyQualifiedName, 17 | isFullyQualifiedName, 18 | parseFullyQualifiedName, 19 | } from "hardhat/utils/contract-names"; 20 | import { replaceBackslashes } from "hardhat/utils/source-names"; 21 | import { glob, globSync } from "hardhat/internal/util/glob"; 22 | import { HardhatError } from "hardhat/internal/core/errors"; 23 | import { ERRORS } from "hardhat/internal/core/errors-list"; 24 | import { 25 | ARTIFACT_FORMAT_VERSION, 26 | EDIT_DISTANCE_THRESHOLD, 27 | } from "hardhat/internal/constants"; 28 | import { ForgeArtifact, BuildInfoArtifact } from "./types"; 29 | 30 | export class ForgeArtifacts implements IArtifacts { 31 | private _buildInfos: BuildInfoArtifact[]; 32 | 33 | constructor( 34 | private _root: string, 35 | private _out: string, 36 | private _artifacts: string, 37 | private _buildInfo: string, 38 | private _useBuildInfo?: boolean 39 | ) { 40 | this._buildInfos = []; 41 | } 42 | 43 | /** 44 | * Public getter for the build info files that caches 45 | * reads. This assumes that no additional build info files 46 | * are generated while this is running. 47 | */ 48 | public get buildInfos() { 49 | if (this._buildInfos.length === 0) { 50 | for (const buildInfoPath of this.getBuildInfoPathsSync()) { 51 | const buildInfo = fsExtra.readJsonSync(buildInfoPath) as BuildInfo; 52 | this._buildInfos.push({ buildInfo, buildInfoPath }); 53 | } 54 | } 55 | return this._buildInfos; 56 | } 57 | 58 | public async readArtifact(name: string): Promise { 59 | const artifactPath = await this._getArtifactPath(name); 60 | const forgeArtifact = (await fsExtra.readJson( 61 | artifactPath 62 | )) as ForgeArtifact; 63 | return this.convertForgeArtifact(forgeArtifact, name); 64 | } 65 | 66 | public readArtifactSync(name: string): Artifact { 67 | const artifactPath = this._getArtifactPathSync(name); 68 | const forgeArtifact = fsExtra.readJsonSync(artifactPath) as ForgeArtifact; 69 | return this.convertForgeArtifact(forgeArtifact, name); 70 | } 71 | 72 | /** 73 | * Noop so that runSuper can be called 74 | */ 75 | public addValidArtifacts( 76 | _validArtifacts: Array<{ sourceName: string; artifacts: string[] }> 77 | ) {} 78 | 79 | /** 80 | * Noop so that runSuper can be called 81 | */ 82 | public async removeObsoleteArtifacts() {} 83 | 84 | /** 85 | * Converts a forge artifact to a hardhat style `Artifact` 86 | * @param artifact 87 | * @param artifactPath 88 | * @param name 89 | */ 90 | public convertForgeArtifact(artifact: ForgeArtifact, name: string): Artifact { 91 | const { abi, bytecode, deployedBytecode } = artifact; 92 | 93 | if (!artifact.ast) { 94 | throw new Error("Must compile with ast"); 95 | } 96 | 97 | if (isFullyQualifiedName(name)) { 98 | const { contractName } = parseFullyQualifiedName(name); 99 | name = contractName; 100 | } 101 | 102 | const hhArtifact = { 103 | _format: ARTIFACT_FORMAT_VERSION, 104 | contractName: name, 105 | sourceName: artifact.ast.absolutePath, 106 | abi, 107 | bytecode: bytecode.object, 108 | deployedBytecode: deployedBytecode.object, 109 | linkReferences: deployedBytecode.linkReferences, 110 | deployedLinkReferences: bytecode.linkReferences, 111 | }; 112 | return hhArtifact as Artifact; 113 | } 114 | 115 | public async artifactExists(name: string): Promise { 116 | try { 117 | await this.readArtifact(name); 118 | return true; 119 | } catch { 120 | return false; 121 | } 122 | } 123 | 124 | public async getAllFullyQualifiedNames(): Promise { 125 | const paths = await this.getArtifactPaths(); 126 | return paths.map((p) => this._getFullyQualifiedNameFromPath(p)).sort(); 127 | } 128 | 129 | /** 130 | * Dynamically generate a build artifact for a contract. 131 | * In practice, hardhat build artifacts are decorated 132 | * with additional fields on top of the `BuildInfo` 133 | * type. Fields that foundry cannot fill in are currently 134 | * left blank. `extra_output` must be configured for some 135 | * fields to be populated 136 | */ 137 | public async getBuildInfo( 138 | fullyQualifiedName: string 139 | ): Promise { 140 | const forgeArtifact = await this._getArtifactPath(fullyQualifiedName); 141 | const hardhatArtifact = 142 | this._getHardhatArtifactPathFromForgePath(forgeArtifact); 143 | const debugFilePath = this._getDebugFilePath(hardhatArtifact); 144 | 145 | const buildInfoPath = await this._getBuildInfoFromDebugFile(debugFilePath); 146 | if (buildInfoPath === undefined) { 147 | return undefined; 148 | } 149 | 150 | const buildInfo = fsExtra.readJsonSync(buildInfoPath) as BuildInfo; 151 | 152 | // Handle ethers-solc serializing the metadata as a string 153 | // when hardhat serializes it as an object 154 | for (const contract of Object.values(buildInfo.output.contracts)) { 155 | for (const output of Object.values(contract)) { 156 | if (typeof (output as any).metadata === "string") { 157 | (output as any).metadata = JSON.parse((output as any).metadata); 158 | } 159 | } 160 | } 161 | return buildInfo; 162 | } 163 | 164 | public async getArtifactPaths(): Promise { 165 | const paths = await glob(path.join(this._out, "**/*.json"), { 166 | ignore: path.join(this._buildInfo, "*.json"), 167 | }); 168 | return paths.filter((p) => !p.endsWith(".metadata.json")).sort(); 169 | } 170 | 171 | public async getBuildInfoPaths(): Promise { 172 | const paths = await glob(path.join(this._buildInfo, "*.json")); 173 | return paths.sort(); 174 | } 175 | 176 | public getBuildInfoPathsSync(): string[] { 177 | const paths = globSync(path.join(this._buildInfo, "*.json")); 178 | return paths.sort(); 179 | } 180 | 181 | public async getDebugFilePaths(): Promise { 182 | const paths = await glob(path.join(this._artifacts, "**/*.dbg.json")); 183 | return paths.sort(); 184 | } 185 | 186 | public async saveArtifactAndDebugFile( 187 | _artifact: Artifact, 188 | _pathToBuildInfo?: string 189 | ) {} 190 | 191 | public async saveBuildInfo( 192 | _solcVersion: string, 193 | _solcLongVersion: string, 194 | _input: CompilerInput, 195 | _output: CompilerOutput 196 | ): Promise { 197 | return ""; 198 | } 199 | 200 | /** 201 | * Returns the absolute path to the artifact that corresponds to the given 202 | * name. 203 | * 204 | * If the name is fully qualified, the path is computed from it. If not, an 205 | * artifact that matches the given name is searched in the existing artifacts. 206 | * If there is an ambiguity, an error is thrown. 207 | */ 208 | private async _getArtifactPath(name: string): Promise { 209 | if (isFullyQualifiedName(name)) { 210 | return this._getValidArtifactPathFromFullyQualifiedName(name); 211 | } 212 | 213 | const files = await this.getArtifactPaths(); 214 | return this._getArtifactPathFromFiles(name, files); 215 | } 216 | 217 | private _getArtifactPathsSync(): string[] { 218 | const paths = globSync(path.join(this._out, "**/*.json"), { 219 | ignore: path.join(this._buildInfo, "*.json"), 220 | }); 221 | return paths.filter((p) => !p.endsWith(".metadata.json")).sort(); 222 | } 223 | 224 | /** 225 | * Sync version of _getArtifactPath 226 | */ 227 | private _getArtifactPathSync(name: string): string { 228 | if (isFullyQualifiedName(name)) { 229 | return this._getValidArtifactPathFromFullyQualifiedNameSync(name); 230 | } 231 | 232 | const files = this._getArtifactPathsSync(); 233 | return this._getArtifactPathFromFiles(name, files); 234 | } 235 | 236 | /** 237 | * Same signature as imported function, but abstracted to handle the only error we consistently care about 238 | */ 239 | private async _trueCasePath( 240 | filePath: string, 241 | basePath?: string 242 | ): Promise { 243 | const { trueCasePath } = await import("true-case-path"); 244 | 245 | try { 246 | const result = await trueCasePath(filePath, basePath); 247 | return result; 248 | } catch (error) { 249 | if (error instanceof Error) { 250 | if (error.message.includes("no matching file exists")) { 251 | return null; 252 | } 253 | } 254 | 255 | // eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error 256 | throw error; 257 | } 258 | } 259 | 260 | /** 261 | * Same signature as imported function, but abstracted to handle the only error we consistently care about 262 | * and synchronous 263 | */ 264 | private _trueCasePathSync( 265 | filePath: string, 266 | basePath?: string 267 | ): string | null { 268 | const { trueCasePathSync } = require("true-case-path"); 269 | 270 | try { 271 | const result = trueCasePathSync(filePath, basePath); 272 | return result; 273 | } catch (error) { 274 | if (error instanceof Error) { 275 | if (error.message.includes("no matching file exists")) { 276 | return null; 277 | } 278 | } 279 | 280 | // eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error 281 | throw error; 282 | } 283 | } 284 | 285 | /** 286 | * Returns the absolute path to the given artifact 287 | */ 288 | public formArtifactPathFromFullyQualifiedName( 289 | fullyQualifiedName: string 290 | ): string { 291 | const { sourceName, contractName } = 292 | parseFullyQualifiedName(fullyQualifiedName); 293 | return path.join(this._out, sourceName, `${contractName}.json`); 294 | } 295 | 296 | private async _getValidArtifactPathFromFullyQualifiedName( 297 | fullyQualifiedName: string 298 | ): Promise { 299 | const artifactPath = 300 | this.formArtifactPathFromFullyQualifiedName(fullyQualifiedName); 301 | 302 | let trueCaseArtifactPath = await this._trueCasePath( 303 | path.relative(this._out, artifactPath), 304 | this._out 305 | ); 306 | 307 | // On case-sensitive filesystems, "true-case-path" 308 | // contains a bug that results in it only observing 309 | // one of the many possible matching files but with different 310 | // casings. If this bug is encountered, check to see if the 311 | // artifact path exists on the filesystem and if it does then 312 | // we know that the casing is not a problem. 313 | if (trueCaseArtifactPath === null) { 314 | if (await fsExtra.pathExists(artifactPath)) { 315 | trueCaseArtifactPath = artifactPath; 316 | } else { 317 | return this._handleWrongArtifactForFullyQualifiedName( 318 | fullyQualifiedName 319 | ); 320 | } 321 | } 322 | 323 | if (artifactPath !== trueCaseArtifactPath) { 324 | throw new HardhatError(ERRORS.ARTIFACTS.WRONG_CASING, { 325 | correct: trueCaseArtifactPath, 326 | incorrect: artifactPath, 327 | }); 328 | } 329 | 330 | return artifactPath; 331 | } 332 | 333 | /** 334 | * DO NOT DELETE OR CHANGE 335 | * 336 | * use this.formArtifactPathFromFullyQualifiedName instead 337 | * @deprecated until typechain migrates to public version 338 | * @see https://github.com/dethcrypto/TypeChain/issues/544 339 | */ 340 | private _getArtifactPathFromFullyQualifiedName( 341 | fullyQualifiedName: string 342 | ): string { 343 | return this.formArtifactPathFromFullyQualifiedName(fullyQualifiedName); 344 | } 345 | 346 | private _getAllContractNamesFromFiles(files: string[]): string[] { 347 | return files.map((file) => { 348 | const fqn = this._getFullyQualifiedNameFromPath(file); 349 | return parseFullyQualifiedName(fqn).contractName; 350 | }); 351 | } 352 | 353 | private _getAllFullyQualifiedNamesSync(): string[] { 354 | const paths = this._getArtifactPathsSync(); 355 | return paths.map((p) => this._getFullyQualifiedNameFromPath(p)).sort(); 356 | } 357 | 358 | private _formatSuggestions(names: string[], contractName: string): string { 359 | switch (names.length) { 360 | case 0: 361 | return ""; 362 | case 1: 363 | return `Did you mean "${names[0]}"?`; 364 | default: 365 | return `We found some that were similar: 366 | 367 | ${names.map((n) => ` * ${n}`).join(os.EOL)} 368 | 369 | Please replace "${contractName}" for the correct contract name wherever you are trying to read its artifact. 370 | `; 371 | } 372 | } 373 | 374 | private _handleWrongArtifactForFullyQualifiedName( 375 | fullyQualifiedName: string 376 | ): never { 377 | const names = this._getAllFullyQualifiedNamesSync(); 378 | 379 | const similarNames = this._getSimilarContractNames( 380 | fullyQualifiedName, 381 | names 382 | ); 383 | 384 | throw new HardhatError(ERRORS.ARTIFACTS.NOT_FOUND, { 385 | contractName: fullyQualifiedName, 386 | suggestion: this._formatSuggestions(similarNames, fullyQualifiedName), 387 | }); 388 | } 389 | 390 | private _handleWrongArtifactForContractName( 391 | contractName: string, 392 | files: string[] 393 | ): never { 394 | const names = this._getAllContractNamesFromFiles(files); 395 | 396 | let similarNames = this._getSimilarContractNames(contractName, names); 397 | 398 | if (similarNames.length > 1) { 399 | similarNames = this._filterDuplicatesAsFullyQualifiedNames( 400 | files, 401 | similarNames 402 | ); 403 | } 404 | 405 | throw new HardhatError(ERRORS.ARTIFACTS.NOT_FOUND, { 406 | contractName, 407 | suggestion: this._formatSuggestions(similarNames, contractName), 408 | }); 409 | } 410 | 411 | /** 412 | * If the project has these contracts: 413 | * - 'contracts/Greeter.sol:Greeter' 414 | * - 'contracts/Meeter.sol:Greeter' 415 | * - 'contracts/Greater.sol:Greater' 416 | * And the user tries to get an artifact with the name 'Greeter', then 417 | * the suggestions will be 'Greeter', 'Greeter', and 'Greater'. 418 | * 419 | * We don't want to show duplicates here, so we use FQNs for those. The 420 | * suggestions will then be: 421 | * - 'contracts/Greeter.sol:Greeter' 422 | * - 'contracts/Meeter.sol:Greeter' 423 | * - 'Greater' 424 | */ 425 | private _filterDuplicatesAsFullyQualifiedNames( 426 | files: string[], 427 | similarNames: string[] 428 | ): string[] { 429 | const outputNames = []; 430 | const groups = similarNames.reduce((obj, cur) => { 431 | obj[cur] = obj[cur] ? obj[cur] + 1 : 1; 432 | return obj; 433 | }, {} as { [k: string]: number }); 434 | 435 | for (const [name, occurrences] of Object.entries(groups)) { 436 | if (occurrences > 1) { 437 | for (const file of files) { 438 | if (path.basename(file) === `${name}.json`) { 439 | outputNames.push(this._getFullyQualifiedNameFromPath(file)); 440 | } 441 | } 442 | continue; 443 | } 444 | 445 | outputNames.push(name); 446 | } 447 | 448 | return outputNames; 449 | } 450 | 451 | /** 452 | * 453 | * @param givenName can be FQN or contract name 454 | * @param names MUST match type of givenName (i.e. array of FQN's if givenName is FQN) 455 | * @returns 456 | */ 457 | private _getSimilarContractNames( 458 | givenName: string, 459 | names: string[] 460 | ): string[] { 461 | let shortestDistance = EDIT_DISTANCE_THRESHOLD; 462 | let mostSimilarNames: string[] = []; 463 | for (const name of names) { 464 | const distance = findDistance(givenName, name); 465 | 466 | if (distance < shortestDistance) { 467 | shortestDistance = distance; 468 | mostSimilarNames = [name]; 469 | continue; 470 | } 471 | 472 | if (distance === shortestDistance) { 473 | mostSimilarNames.push(name); 474 | continue; 475 | } 476 | } 477 | 478 | return mostSimilarNames; 479 | } 480 | 481 | private _getValidArtifactPathFromFullyQualifiedNameSync( 482 | fullyQualifiedName: string 483 | ): string { 484 | const artifactPath = 485 | this.formArtifactPathFromFullyQualifiedName(fullyQualifiedName); 486 | 487 | let trueCaseArtifactPath = this._trueCasePathSync( 488 | path.relative(this._out, artifactPath), 489 | this._out 490 | ); 491 | 492 | if (trueCaseArtifactPath === null) { 493 | if (fsExtra.pathExistsSync(artifactPath)) { 494 | trueCaseArtifactPath = artifactPath; 495 | } else { 496 | return this._handleWrongArtifactForFullyQualifiedName( 497 | fullyQualifiedName 498 | ); 499 | } 500 | } 501 | 502 | if (artifactPath !== trueCaseArtifactPath) { 503 | throw new HardhatError(ERRORS.ARTIFACTS.WRONG_CASING, { 504 | correct: trueCaseArtifactPath, 505 | incorrect: artifactPath, 506 | }); 507 | } 508 | 509 | return artifactPath; 510 | } 511 | 512 | private _getDebugFilePath(artifactPath: string): string { 513 | return artifactPath.replace(/\.json$/, ".dbg.json"); 514 | } 515 | 516 | private _getArtifactPathFromFiles( 517 | contractName: string, 518 | files: string[] 519 | ): string { 520 | const matchingFiles = files.filter((file) => { 521 | return path.basename(file) === `${contractName}.json`; 522 | }); 523 | 524 | if (matchingFiles.length === 0) { 525 | return this._handleWrongArtifactForContractName(contractName, files); 526 | } 527 | 528 | if (matchingFiles.length > 1) { 529 | const candidates = matchingFiles.map((file) => 530 | this._getFullyQualifiedNameFromPath(file) 531 | ); 532 | 533 | throw new HardhatError(ERRORS.ARTIFACTS.MULTIPLE_FOUND, { 534 | contractName, 535 | candidates: candidates.join(os.EOL), 536 | }); 537 | } 538 | 539 | return matchingFiles[0]; 540 | } 541 | 542 | /** 543 | * Returns the FQN of a contract giving the absolute path to its artifact. 544 | * 545 | * For example, given a path like 546 | * `/path/to/project/artifacts/contracts/Foo.sol/Bar.json`, it'll return the 547 | * FQN `contracts/Foo.sol:Bar` 548 | */ 549 | private _getFullyQualifiedNameFromPath(absolutePath: string): string { 550 | const sourceName = replaceBackslashes( 551 | path.relative(this._out, path.dirname(absolutePath)) 552 | ); 553 | 554 | const contractName = path.basename(absolutePath).replace(".json", ""); 555 | 556 | return getFullyQualifiedName(sourceName, contractName); 557 | } 558 | 559 | /** 560 | * Remove the artifact file, its debug file and, if it exists, its build 561 | * info file. 562 | */ 563 | private async _removeArtifactFiles(artifactPath: string) { 564 | await fsExtra.remove(artifactPath); 565 | 566 | const debugFilePath = this._getDebugFilePath(artifactPath); 567 | const buildInfoPath = await this._getBuildInfoFromDebugFile(debugFilePath); 568 | 569 | await fsExtra.remove(debugFilePath); 570 | 571 | if (buildInfoPath !== undefined) { 572 | await fsExtra.remove(buildInfoPath); 573 | } 574 | } 575 | 576 | /** 577 | * Given the path to a debug file, returns the absolute path to its 578 | * corresponding build info file if it exists, or undefined otherwise. 579 | */ 580 | private async _getBuildInfoFromDebugFile( 581 | debugFilePath: string 582 | ): Promise { 583 | if (await fsExtra.pathExists(debugFilePath)) { 584 | const { buildInfo } = await fsExtra.readJson(debugFilePath); 585 | return path.resolve(path.dirname(debugFilePath), buildInfo); 586 | } 587 | 588 | return undefined; 589 | } 590 | 591 | /** 592 | * Write hardhat style artifacts to disk 593 | */ 594 | public writeArtifactsSync() { 595 | const paths = this._getArtifactPathsSync(); 596 | 597 | for (const filepath of paths) { 598 | // Handle multiple contracts with the same name 599 | const fqn = this._getFullyQualifiedNameFromPath(filepath); 600 | const artifact = this.readArtifactSync(fqn); 601 | const out = this._getHardhatArtifactPathFromForgePath(filepath); 602 | fsExtra.mkdirpSync(path.dirname(out)); 603 | fsExtra.writeJsonSync(out, artifact, { spaces: 2 }); 604 | 605 | if (this._useBuildInfo === true) { 606 | this._writeDebugFile(out, artifact.sourceName); 607 | } 608 | } 609 | } 610 | 611 | /** 612 | * Writes a debug file to disk. The debug file contains 613 | * the path to the build info artifact corresponding 614 | * to the hardhat artifact 615 | */ 616 | private _writeDebugFile(out: string, sourceName: string) { 617 | for (const { buildInfo, buildInfoPath } of this.buildInfos) { 618 | for (const contract of Object.keys(buildInfo.output.contracts)) { 619 | if (contract === sourceName) { 620 | const debugFile = { 621 | _format: "hh-sol-dbg-1", 622 | buildInfo: path.relative(path.dirname(out), buildInfoPath), 623 | }; 624 | const debug = this._getDebugFilePath(out); 625 | fsExtra.writeJsonSync(debug, debugFile, { spaces: 2 }); 626 | return; 627 | } 628 | } 629 | } 630 | } 631 | 632 | /** 633 | * Converts a foundry artifact path to a hardhat artifact path 634 | */ 635 | private _getHardhatArtifactPathFromForgePath(filepath: string) { 636 | const artifacts = path.relative(this._root, this._artifacts); 637 | const forgeArtifact = fsExtra.readJsonSync(filepath) as ForgeArtifact; 638 | 639 | const contractPath = forgeArtifact.ast?.absolutePath; 640 | if (!contractPath) { 641 | throw new Error("Must compile with ast to build harhat style artifacts"); 642 | } 643 | 644 | const dir = path.join(this._root, artifacts, contractPath); 645 | return path.join(dir, path.basename(filepath)); 646 | } 647 | } 648 | 649 | /** 650 | * Retrieves an artifact for the given `contractName` from the compilation output. 651 | * 652 | * @param sourceName The contract's source name. 653 | * @param contractName the contract's name. 654 | * @param contractOutput the contract's compilation output as emitted by `solc`. 655 | */ 656 | export function getArtifactFromContractOutput( 657 | sourceName: string, 658 | contractName: string, 659 | contractOutput: any 660 | ): Artifact { 661 | const evmBytecode = contractOutput.evm && contractOutput.evm.bytecode; 662 | let bytecode: string = 663 | evmBytecode && evmBytecode.object ? evmBytecode.object : ""; 664 | 665 | if (bytecode.slice(0, 2).toLowerCase() !== "0x") { 666 | bytecode = `0x${bytecode}`; 667 | } 668 | 669 | const evmDeployedBytecode = 670 | contractOutput.evm && contractOutput.evm.deployedBytecode; 671 | let deployedBytecode: string = 672 | evmDeployedBytecode && evmDeployedBytecode.object 673 | ? evmDeployedBytecode.object 674 | : ""; 675 | 676 | if (deployedBytecode.slice(0, 2).toLowerCase() !== "0x") { 677 | deployedBytecode = `0x${deployedBytecode}`; 678 | } 679 | 680 | const linkReferences = 681 | evmBytecode && evmBytecode.linkReferences ? evmBytecode.linkReferences : {}; 682 | const deployedLinkReferences = 683 | evmDeployedBytecode && evmDeployedBytecode.linkReferences 684 | ? evmDeployedBytecode.linkReferences 685 | : {}; 686 | 687 | return { 688 | _format: ARTIFACT_FORMAT_VERSION, 689 | contractName, 690 | sourceName, 691 | abi: contractOutput.abi, 692 | bytecode, 693 | deployedBytecode, 694 | linkReferences, 695 | deployedLinkReferences, 696 | }; 697 | } 698 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/build/build-ti.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module was automatically generated by `ts-interface-builder` 3 | */ 4 | import * as t from "ts-interface-checker"; 5 | // tslint:disable:object-literal-key-quotes 6 | 7 | export const ForgeBuildArgsTi = t.iface(["CompilerArgs", "ProjectPathArgs"], { 8 | force: t.opt("boolean"), 9 | names: t.opt("boolean"), 10 | sizes: t.opt("boolean"), 11 | libraries: t.opt(t.array("string")), 12 | ignoredErrorCodes: t.opt(t.array("number")), 13 | noAutodetect: t.opt("boolean"), 14 | useSolc: t.opt("string"), 15 | offline: t.opt("boolean"), 16 | viaIR: t.opt("boolean"), 17 | }); 18 | 19 | const exportedTypeSuite: t.ITypeSuite = { 20 | ForgeBuildArgsTi, 21 | }; 22 | // eslint-disable-next-line import/no-default-export 23 | export default exportedTypeSuite; 24 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/build/build.ts: -------------------------------------------------------------------------------- 1 | // bindings for forge build 2 | import { spawn as spawn } from "child_process"; 3 | import * as foundryup from "@foundry-rs/easy-foundryup"; 4 | import { 5 | compilerArgs, 6 | CompilerArgs, 7 | ProjectPathArgs, 8 | projectPathsArgs, 9 | } from "../common"; 10 | 11 | /** 12 | * Mirrors the `forge build` arguments 13 | */ 14 | export declare interface ForgeBuildArgs extends CompilerArgs, ProjectPathArgs { 15 | force?: boolean; 16 | names?: boolean; 17 | sizes?: boolean; 18 | libraries?: string[]; 19 | ignoredErrorCodes?: number[]; 20 | noAutodetect?: boolean; 21 | useSolc?: string; 22 | offline?: boolean; 23 | viaIr?: boolean; 24 | ast?: boolean; 25 | buildInfo?: boolean; 26 | buildInfoPath?: string; 27 | } 28 | 29 | /** * 30 | * Invokes `forge build` 31 | * @param opts The arguments to pass to `forge build` 32 | */ 33 | export async function spawnBuild(opts: ForgeBuildArgs): Promise { 34 | const args = ["build", ...buildArgs(opts)]; 35 | const forgeCmd = await foundryup.getForgeCommand(); 36 | return new Promise((resolve) => { 37 | const process = spawn(forgeCmd, args, { 38 | stdio: "inherit", 39 | }); 40 | process.on("exit", (code) => { 41 | resolve(code === 0); 42 | }); 43 | }); 44 | } 45 | 46 | /** 47 | * Converts the `args` object into a list of arguments for the `forge build` command 48 | * @param args 49 | */ 50 | export function buildArgs(args: ForgeBuildArgs): string[] { 51 | const allArgs: string[] = []; 52 | if (args.force === true) { 53 | allArgs.push("--force"); 54 | } 55 | if (args.names === true) { 56 | allArgs.push("--names"); 57 | } 58 | if (args.sizes === true) { 59 | allArgs.push("--sizes"); 60 | } 61 | if (args.libraries && args.libraries.length) { 62 | allArgs.push("--libraries", ...args.libraries); 63 | } 64 | if (args.ignoredErrorCodes && args.ignoredErrorCodes.length) { 65 | const codes = args.ignoredErrorCodes.map((code) => code.toString()); 66 | allArgs.push("--ignored-error-codes", ...codes); 67 | } 68 | if (args.noAutodetect === true) { 69 | allArgs.push("--no-auto-detect"); 70 | } 71 | const useSolc = args.useSolc ?? ""; 72 | if (useSolc) { 73 | allArgs.push("--use", useSolc); 74 | } 75 | if (args.offline === true) { 76 | allArgs.push("--offline"); 77 | } 78 | if (args.viaIr === true) { 79 | allArgs.push("--via-ir"); 80 | } 81 | if (args.ast === true) { 82 | allArgs.push("--ast"); 83 | } 84 | if (args.buildInfo === true) { 85 | allArgs.push("--build-info"); 86 | } 87 | if (typeof args.buildInfoPath === "string") { 88 | allArgs.push("--build-info-path", args.buildInfoPath); 89 | } 90 | 91 | allArgs.push(...compilerArgs(args)); 92 | allArgs.push(...projectPathsArgs(args)); 93 | 94 | return allArgs; 95 | } 96 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/build/index.ts: -------------------------------------------------------------------------------- 1 | import { task } from "hardhat/config"; 2 | import camelcaseKeys = require("camelcase-keys"); 3 | import { NomicLabsHardhatPluginError } from "hardhat/internal/core/errors"; 4 | import { registerCompilerArgs, registerProjectPathArgs } from "../common"; 5 | import { ForgeBuildArgs, spawnBuild } from "./build"; 6 | 7 | registerProjectPathArgs(registerCompilerArgs(task("compile"))) 8 | .setDescription("Compiles the entire project with forge") 9 | .addFlag( 10 | "offline", 11 | "Do not access the network. Missing solc versions will not be installed." 12 | ) 13 | .addFlag( 14 | "viaIr", 15 | "Use the Yul intermediate representation compilation pipeline." 16 | ) 17 | .setAction(async (args, hre, runSuper) => { 18 | const input = { ...args, ...(hre.config.foundry || {}) }; 19 | const buildArgs = await getCheckedArgs(input); 20 | await spawnBuild(buildArgs); 21 | 22 | if (hre.config.foundry?.writeArtifacts!) { 23 | (hre as any).artifacts.writeArtifactsSync(); 24 | } 25 | 26 | if (hre.config.foundry?.runSuper!) { 27 | await runSuper(args); 28 | } 29 | }); 30 | 31 | async function getCheckedArgs(args: any): Promise { 32 | // Get and initialize option validator 33 | const { default: buildArgsSchema } = await import("./build-ti"); 34 | const { default: compilerArgsSchema } = await import("../common/compiler-ti"); 35 | const { default: projectPathsSchema } = await import( 36 | "../common/projectpaths-ti" 37 | ); 38 | const { createCheckers } = await import("ts-interface-checker"); 39 | const { ForgeBuildArgsTi } = createCheckers( 40 | buildArgsSchema, 41 | compilerArgsSchema, 42 | projectPathsSchema 43 | ); 44 | const uncheckedBuildArgs = camelcaseKeys(args); 45 | // Validate all options against the validator 46 | try { 47 | ForgeBuildArgsTi.check(uncheckedBuildArgs); 48 | } catch (e: any) { 49 | throw new NomicLabsHardhatPluginError( 50 | "@foundry-rs/hardhat-forge", 51 | `Forge build config is invalid: ${e.message}`, 52 | e 53 | ); 54 | } 55 | return uncheckedBuildArgs as ForgeBuildArgs; 56 | } 57 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/compiler-ti.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module was automatically generated by `ts-interface-builder` 3 | */ 4 | import * as t from "ts-interface-checker"; 5 | // tslint:disable:object-literal-key-quotes 6 | 7 | export const CompilerArgs = t.iface([], { 8 | evmVersion: t.opt("string"), 9 | optimizer: t.opt("boolean"), 10 | optimizerRuns: t.opt("number"), 11 | extraOutput: t.opt(t.array("string")), 12 | extraOutputFiles: t.opt(t.array("string")), 13 | }); 14 | 15 | const exportedTypeSuite: t.ITypeSuite = { 16 | CompilerArgs, 17 | }; 18 | 19 | // eslint-disable-next-line import/no-default-export 20 | export default exportedTypeSuite; 21 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/compiler.ts: -------------------------------------------------------------------------------- 1 | // bindings for common compiler settings 2 | import { types } from "hardhat/config"; 3 | import { ConfigurableTaskDefinition } from "hardhat/types"; 4 | 5 | /** 6 | * Registers all `CompilerArgs` on the hardhat `ConfigurableTaskDefinition` 7 | * @param task 8 | */ 9 | export function registerCompilerArgs( 10 | task: ConfigurableTaskDefinition 11 | ): ConfigurableTaskDefinition { 12 | return task 13 | .addOptionalParam( 14 | "evmVersion", 15 | "The target EVM version.", 16 | undefined, 17 | types.string 18 | ) 19 | .addFlag("optimize", "Activate the Solidity optimizer.") 20 | .addOptionalParam( 21 | "optimizerRuns", 22 | "The number of optimizer runs.", 23 | undefined, 24 | types.int 25 | ); 26 | } 27 | 28 | /** 29 | * Mirrors the `CompilerArgs` type 30 | */ 31 | export declare interface CompilerArgs { 32 | evmVersion?: string; 33 | optimize?: boolean; 34 | optimizerRuns?: number; 35 | extraOutput?: string[]; 36 | extraOutputFiles?: string[]; 37 | } 38 | 39 | /** 40 | * Transforms the `CompilerArgs` in to a list of comand arguments 41 | * @param args 42 | */ 43 | export function compilerArgs(args: CompilerArgs): string[] { 44 | const allArgs: string[] = []; 45 | 46 | const evmVersion = args.evmVersion ?? ""; 47 | if (evmVersion) { 48 | allArgs.push("--evm-version", evmVersion); 49 | } 50 | 51 | if (args.optimize === true) { 52 | allArgs.push("--optimize"); 53 | const optimizerRuns = args.optimizerRuns ?? 0; 54 | if (optimizerRuns) { 55 | allArgs.push("--optimizer-runs", optimizerRuns.toString()); 56 | } 57 | } 58 | 59 | if (args.extraOutput && args.extraOutput.length) { 60 | allArgs.push("--extra-output", ...args.extraOutput); 61 | } 62 | 63 | if (args.extraOutputFiles && args.extraOutputFiles.length) { 64 | allArgs.push("--extra-output-files", ...args.extraOutputFiles); 65 | } 66 | 67 | return allArgs; 68 | } 69 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/env-ti.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module was automatically generated by `ts-interface-builder` 3 | */ 4 | import * as t from "ts-interface-checker"; 5 | // tslint:disable:object-literal-key-quotes 6 | 7 | export const ForgeEnvArgsTi = t.iface([], { 8 | gasLimit: t.opt("number"), 9 | chainId: t.opt("number"), 10 | gasPrice: t.opt(t.union("string", "number")), 11 | txOrigin: t.opt("string"), 12 | blockCoinbase: t.opt("string"), 13 | blockTimestamp: t.opt("number"), 14 | blockNumber: t.opt("number"), 15 | blockDifficulty: t.opt("number"), 16 | blockGasLimit: t.opt("number"), 17 | }); 18 | 19 | const exportedTypeSuite: t.ITypeSuite = { 20 | ForgeEnvArgsTi, 21 | }; 22 | // eslint-disable-next-line import/no-default-export 23 | export default exportedTypeSuite; 24 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/env.ts: -------------------------------------------------------------------------------- 1 | // bindings for common env settings 2 | 3 | import { ConfigurableTaskDefinition } from "hardhat/types"; 4 | import { types } from "hardhat/config"; 5 | 6 | /** 7 | * Registers all `CompilerArgs` on the hardhat `ConfigurableTaskDefinition` 8 | * @param task 9 | */ 10 | export function registerEnvArgs( 11 | task: ConfigurableTaskDefinition 12 | ): ConfigurableTaskDefinition { 13 | return task 14 | .addOptionalParam("gasLimit", "The block gas limit.", undefined, types.int) 15 | .addOptionalParam("chainId", "The chain Id.", undefined, types.int) 16 | .addOptionalParam("gasPrice", "The gas price.", undefined, types.int) 17 | .addOptionalParam("txOrigin", undefined, undefined, types.string) 18 | .addOptionalParam("blockCoinbase", undefined, undefined, types.string) 19 | .addOptionalParam("blockTimestamp", undefined, undefined, types.int) 20 | .addOptionalParam("blockNumber", undefined, undefined, types.int) 21 | .addOptionalParam("blockDifficulty", undefined, undefined, types.int) 22 | .addOptionalParam("blockGasLimit", undefined, undefined, types.int); 23 | } 24 | 25 | /** 26 | * Mirrors the `forge build` arguments 27 | */ 28 | export declare interface ForgeEnvArgs { 29 | gasLimit?: number; 30 | chainId?: number; 31 | gasPrice?: string | number; 32 | txOrigin?: string; 33 | blockCoinbase?: string; 34 | blockTimestamp?: number; 35 | blockNumber?: number; 36 | blockDifficulty?: number; 37 | blockGasLimit?: number; 38 | } 39 | 40 | /** 41 | * Transforms the `ForgeEnvArgs` in to a list of command arguments 42 | * @param args 43 | */ 44 | export function envArgs(args: ForgeEnvArgs): string[] { 45 | const allArgs: string[] = []; 46 | 47 | const gasLimit = args.gasLimit ?? -1; 48 | if (gasLimit >= 0) { 49 | allArgs.push("--gas-limit", gasLimit.toString()); 50 | } 51 | 52 | const chainId = args.chainId ?? -1; 53 | if (chainId >= 0) { 54 | allArgs.push("--chain-id", chainId.toString()); 55 | } 56 | 57 | const gasPrice = args.gasPrice ?? -1; 58 | if (gasPrice >= 0) { 59 | allArgs.push("--gas-price", gasPrice.toString()); 60 | } 61 | 62 | const txOrigin = args.txOrigin ?? ""; 63 | if (txOrigin) { 64 | allArgs.push("--tx-origin", txOrigin); 65 | } 66 | 67 | const blockCoinbase = args.blockCoinbase ?? ""; 68 | if (blockCoinbase) { 69 | allArgs.push("--block-coinbase", blockCoinbase); 70 | } 71 | 72 | const blockTimestamp = args.blockTimestamp ?? -1; 73 | if (blockTimestamp >= 0) { 74 | allArgs.push("--block-timestamp", blockTimestamp.toString()); 75 | } 76 | 77 | const blockNumber = args.blockNumber ?? -1; 78 | if (blockNumber >= 0) { 79 | allArgs.push("--block-number", blockNumber.toString()); 80 | } 81 | 82 | const blockDifficulty = args.blockDifficulty ?? -1; 83 | if (blockDifficulty >= 0) { 84 | allArgs.push("--block-difficulty", blockDifficulty.toString()); 85 | } 86 | 87 | const blockGasLimit = args.blockGasLimit ?? -1; 88 | if (blockGasLimit >= 0) { 89 | allArgs.push("--block-gas-limit", blockGasLimit.toString()); 90 | } 91 | 92 | return allArgs; 93 | } 94 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/evm-ti.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module was automatically generated by `ts-interface-builder` 3 | */ 4 | import * as t from "ts-interface-checker"; 5 | // tslint:disable:object-literal-key-quotes 6 | 7 | export const ForgeEvmArgsTi = t.iface([], { 8 | forkUrl: t.opt("string"), 9 | forkBlockNumber: t.opt("number"), 10 | noStorageCaching: t.opt("boolean"), 11 | initialBalance: t.opt("string"), 12 | sender: t.opt("string"), 13 | ffi: t.opt("boolean"), 14 | verbosity: t.opt("number"), 15 | }); 16 | 17 | const exportedTypeSuite: t.ITypeSuite = { 18 | ForgeEvmArgsTi, 19 | }; 20 | 21 | // eslint-disable-next-line import/no-default-export 22 | export default exportedTypeSuite; 23 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/evm.ts: -------------------------------------------------------------------------------- 1 | // bindings for common evm settings 2 | 3 | import { ConfigurableTaskDefinition } from "hardhat/types"; 4 | import { types } from "hardhat/config"; 5 | import { envArgs, ForgeEnvArgs } from "./env"; 6 | 7 | /** 8 | * Registers all `CompilerArgs` on the hardhat `ConfigurableTaskDefinition` 9 | * @param task 10 | */ 11 | export function registerEvmArgs( 12 | task: ConfigurableTaskDefinition 13 | ): ConfigurableTaskDefinition { 14 | return task 15 | .addOptionalParam( 16 | "forkUrl", 17 | "Fetch state over a remote endpoint instead of starting from an empty state.", 18 | undefined, 19 | types.string 20 | ) 21 | .addOptionalParam( 22 | "forkBlockNumber", 23 | "Fetch state from a specific block number over a remote endpoint.", 24 | undefined, 25 | types.int 26 | ) 27 | .addFlag("noStorageCaching", "Explicitly disables the use of RPC caching.") 28 | .addFlag("ffi", "Enable the FFI cheatcode.") 29 | .addOptionalParam( 30 | "optimizerRuns", 31 | "The number of optimizer runs.", 32 | undefined, 33 | types.int 34 | ) 35 | .addOptionalParam( 36 | "verbosity", 37 | "Verbosity of the EVM output.", 38 | undefined, 39 | types.int 40 | ); 41 | } 42 | 43 | /** 44 | * EVM related args 45 | */ 46 | export declare interface ForgeEvmArgs extends ForgeEnvArgs { 47 | forkUrl?: string; 48 | forkBlockNumber?: number; 49 | noStorageCaching?: boolean; 50 | initialBalance?: string; 51 | sender?: string; 52 | ffi?: boolean; 53 | verbosity?: number; 54 | } 55 | 56 | /** 57 | * Transforms the `ForgeEvmArgs` in to a list of command arguments 58 | * @param args 59 | */ 60 | export function evmArgs(args: ForgeEvmArgs): string[] { 61 | const allArgs: string[] = []; 62 | 63 | const forkUrl = args.txOrigin ?? ""; 64 | if (forkUrl) { 65 | allArgs.push("--fork-url", forkUrl); 66 | const forkBlockNumber = args.forkBlockNumber ?? -1; 67 | if (forkBlockNumber >= 0) { 68 | allArgs.push("--fork-block-number", forkBlockNumber.toString()); 69 | } 70 | } 71 | 72 | if (args.noStorageCaching ?? false) { 73 | allArgs.push("--no-storage-caching"); 74 | } 75 | 76 | const initialBalance = args.initialBalance ?? ""; 77 | if (initialBalance) { 78 | allArgs.push("--initial-balance", initialBalance); 79 | } 80 | 81 | const sender = args.sender ?? ""; 82 | if (sender) { 83 | allArgs.push("--sender", sender); 84 | } 85 | 86 | if (args.ffi ?? false) { 87 | allArgs.push("--ffi"); 88 | } 89 | 90 | const verbosity = args.verbosity ?? -1; 91 | if (verbosity >= 0) { 92 | allArgs.push("--verbosity", verbosity.toString()); 93 | } 94 | 95 | allArgs.push(...envArgs(args)); 96 | 97 | return allArgs; 98 | } 99 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./compiler"; 2 | export * from "./evm"; 3 | export * from "./projectpaths"; 4 | export * from "./env"; 5 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/projectpaths-ti.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module was automatically generated by `ts-interface-builder` 3 | */ 4 | import * as t from "ts-interface-checker"; 5 | // tslint:disable:object-literal-key-quotes 6 | 7 | export const ProjectPathArgs = t.iface([], { 8 | root: t.opt("string"), 9 | contracts: t.opt("string"), 10 | remappings: t.opt(t.array("string")), 11 | remappingsEnv: t.opt("string"), 12 | cachePath: t.opt("string"), 13 | libPaths: t.opt("string"), 14 | hardhat: t.opt("boolean"), 15 | configPath: t.opt("string"), 16 | outPath: t.opt("string"), 17 | }); 18 | 19 | const exportedTypeSuite: t.ITypeSuite = { 20 | ProjectPathArgs, 21 | }; 22 | 23 | // eslint-disable-next-line import/no-default-export 24 | export default exportedTypeSuite; 25 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/common/projectpaths.ts: -------------------------------------------------------------------------------- 1 | // bindings for common project paths settings 2 | 3 | import { types } from "hardhat/config"; 4 | import { ConfigurableTaskDefinition } from "hardhat/types"; 5 | 6 | /** 7 | * Registers all `CompilerArgs` on the hardhat `ConfigurableTaskDefinition` 8 | * @param task 9 | */ 10 | export function registerProjectPathArgs( 11 | task: ConfigurableTaskDefinition 12 | ): ConfigurableTaskDefinition { 13 | return task 14 | .addOptionalParam( 15 | "root", 16 | "The project's root path.", 17 | undefined, 18 | types.string 19 | ) 20 | .addOptionalParam( 21 | "contracts", 22 | "The contracts source directory.", 23 | undefined, 24 | types.string 25 | ) 26 | .addOptionalParam( 27 | "remappings", 28 | "The project's remappings", 29 | undefined, 30 | types.string 31 | ) 32 | .addOptionalParam( 33 | "cachePath", 34 | "The path to the compiler cache.", 35 | undefined, 36 | types.string 37 | ) 38 | .addOptionalParam( 39 | "libPaths", 40 | "The path to the library folder.", 41 | undefined, 42 | types.string 43 | ) 44 | .addFlag("hardhat", "Use the Hardhat-style project layout.") 45 | .addOptionalParam( 46 | "configPath", 47 | "Path to the config file.", 48 | undefined, 49 | types.string 50 | ); 51 | } 52 | 53 | /** 54 | * Mirrors the `ProjectPaths` type 55 | */ 56 | export declare interface ProjectPathArgs { 57 | root?: string; 58 | contracts?: string; 59 | remappings?: string[]; 60 | remappingsEnv?: string; 61 | cachePath?: string; 62 | libPaths?: string; 63 | hardhat?: boolean; 64 | configPath?: string; 65 | outPath?: string; 66 | } 67 | 68 | export function projectPathsArgs(args: ProjectPathArgs): string[] { 69 | const allArgs: string[] = []; 70 | 71 | const root = args.root ?? ""; 72 | if (root) { 73 | allArgs.push("--root", root); 74 | } 75 | const contracts = args.contracts ?? ""; 76 | if (contracts) { 77 | allArgs.push("--contracts", contracts); 78 | } 79 | if (args.remappings && args.remappings.length) { 80 | allArgs.push("--remappings", ...args.remappings); 81 | } 82 | const remappingsEnv = args.remappingsEnv ?? ""; 83 | if (remappingsEnv) { 84 | allArgs.push("--remappings-env", remappingsEnv); 85 | } 86 | const cachePath = args.cachePath ?? ""; 87 | if (cachePath) { 88 | allArgs.push("--cache-path", cachePath); 89 | } 90 | const libPaths = args.libPaths ?? ""; 91 | if (libPaths) { 92 | allArgs.push("--lib-paths", libPaths); 93 | } 94 | if (args.hardhat === true) { 95 | allArgs.push("--hardhat"); 96 | } 97 | const configPath = args.configPath ?? ""; 98 | if (configPath) { 99 | allArgs.push("--config-path", configPath); 100 | } 101 | const outPath = args.outPath ?? ""; 102 | if (outPath) { 103 | allArgs.push("--out", outPath); 104 | } 105 | 106 | return allArgs; 107 | } 108 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/config/config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | export declare interface RpcStorageCaching { 3 | chains?: string; 4 | endpoints?: string; 5 | } 6 | 7 | /** 8 | * Represents the foundry config object 9 | */ 10 | export declare interface FoundryConfig { 11 | src: string; 12 | test: string; 13 | out: string; 14 | libs: string[]; 15 | remappings: string[]; 16 | libraries?: string[]; 17 | cache?: boolean; 18 | cache_path?: string; 19 | force?: boolean; 20 | evm_version?: string; 21 | gas_reports?: string[]; 22 | solc?: string; 23 | auto_detect_solc?: boolean; 24 | offline?: boolean; 25 | optimizer?: boolean; 26 | optimizer_runs?: number; 27 | optimizer_details?: any; 28 | verbosity?: number; 29 | eth_rpc_url?: string; 30 | etherscan_api_key?: string; 31 | ignored_error_codes?: number[]; 32 | match_test?: string; 33 | no_match_test?: string; 34 | match_contract?: string; 35 | no_match_contract?: string; 36 | match_path?: string; 37 | no_match_path?: string; 38 | fuzz_runs?: number; 39 | ffi?: boolean; 40 | sender?: string; 41 | tx_origin?: string; 42 | initial_balance?: string; 43 | block_number?: number; 44 | fork_block_number?: any; 45 | chain_id?: any; 46 | gas_limit?: number; 47 | gas_price?: number; 48 | block_base_fee_per_gas?: number; 49 | block_coinbase?: string; 50 | block_timestamp?: number; 51 | block_difficulty?: number; 52 | block_gas_limit?: any; 53 | memory_limit?: number; 54 | extra_output?: string[]; 55 | extra_output_files?: string[]; 56 | fuzz_max_local_rejects?: number; 57 | fuzz_max_global_rejects?: number; 58 | names?: boolean; 59 | sizes?: boolean; 60 | via_ir?: boolean; 61 | rpc_storage_caching?: RpcStorageCaching; 62 | no_storage_caching?: boolean; 63 | bytecode_hash?: string; 64 | revert_strings?: any; 65 | sparse_mode?: boolean; 66 | build_info?: boolean; 67 | build_info_path?: string; 68 | } 69 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/config/index.ts: -------------------------------------------------------------------------------- 1 | // bindings for forge config 2 | 3 | import * as foundryup from "@foundry-rs/easy-foundryup"; 4 | import { spawn, spawnSync } from "child_process"; 5 | import { task } from "hardhat/config"; 6 | import { FoundryConfig } from "./config"; 7 | 8 | task("forge:config") 9 | .setDescription("Returns the config of the current project") 10 | .setAction(async () => { 11 | return spawnConfig(); 12 | }); 13 | 14 | /** * 15 | * Invokes `forge config` and returns the current config of the current project 16 | */ 17 | export async function spawnConfig(): Promise { 18 | const args = ["config", "--json"]; 19 | const forgeCmd = await foundryup.getForgeCommand(); 20 | return new Promise((resolve) => { 21 | const process = spawn(forgeCmd, args); 22 | let config = ""; 23 | process.stdout.on("data", (data) => { 24 | config += data.toString(); 25 | }); 26 | 27 | process.on("exit", (_code) => { 28 | resolve(JSON.parse(config) as FoundryConfig); 29 | }); 30 | }); 31 | } 32 | 33 | /** * 34 | * Invokes `forge config` and returns the current config of the current project 35 | */ 36 | export function spawnConfigSync(): FoundryConfig { 37 | const args = ["config", "--json"]; 38 | const forgeCmd = foundryup.getForgeCommandSync(); 39 | const res = spawnSync(forgeCmd, args); 40 | return JSON.parse(res.stdout.toString()) as FoundryConfig; 41 | } 42 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/constants.ts: -------------------------------------------------------------------------------- 1 | export const SOLIDITY_FILES_CACHE_FILENAME = "solidity-files-cache.json"; 2 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/index.ts: -------------------------------------------------------------------------------- 1 | /// forge bindings 2 | 3 | export * from "./build"; 4 | export * from "./test"; 5 | export * from "./config"; 6 | export * from "./artifacts"; 7 | export * from "./types"; 8 | export * from "./constants"; 9 | import "./common/evm"; 10 | import "./common/compiler"; 11 | import "./common/projectpaths"; 12 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/test/index.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { NomicLabsHardhatPluginError } from "hardhat/internal/core/errors"; 3 | import camelcaseKeys = require("camelcase-keys"); 4 | import { registerEnvArgs, registerEvmArgs } from "../common"; 5 | import { spawnTest, ForgeTestArgs } from "./test"; 6 | 7 | registerEvmArgs(registerEnvArgs(task("test"))) 8 | .setDescription("Runs all the test in the project") 9 | .addFlag("json", "Output test results in JSON format.") 10 | .addFlag("gasReport", "Print a gas report.") 11 | .addFlag("allowFailure", "Exit with code 0 even if a test fails.") 12 | .addOptionalParam( 13 | "matchTest", 14 | "Only run test functions matching the specified regex pattern.", 15 | undefined, 16 | types.string 17 | ) 18 | .addOptionalParam( 19 | "matchContract", 20 | "Only run tests in contracts matching the specified regex pattern.", 21 | undefined, 22 | types.string 23 | ) 24 | .addOptionalParam( 25 | "etherscanApiKey", 26 | "Etherscan API to use", 27 | undefined, 28 | types.string 29 | ) 30 | .setAction(async (args, {}) => { 31 | const buildArgs = await getCheckedArgs(args); 32 | await spawnTest(buildArgs); 33 | }); 34 | 35 | async function getCheckedArgs(args: any): Promise { 36 | // Get and initialize option validator 37 | const { default: buildArgsSchema } = await import("../build/build-ti"); 38 | const { default: envArgsSchema } = await import("../common/env-ti"); 39 | const { default: evmArgsSchema } = await import("../common/evm-ti"); 40 | const { default: compilerArgsSchema } = await import("../common/compiler-ti"); 41 | const { default: projectPathsSchema } = await import( 42 | "../common/projectpaths-ti" 43 | ); 44 | const { createCheckers } = await import("ts-interface-checker"); 45 | const { ForgeBuildArgsTi } = createCheckers( 46 | buildArgsSchema, 47 | envArgsSchema, 48 | evmArgsSchema, 49 | compilerArgsSchema, 50 | projectPathsSchema 51 | ); 52 | const uncheckedBuildArgs = camelcaseKeys(args); 53 | // Validate all options against the validator 54 | try { 55 | ForgeBuildArgsTi.check(uncheckedBuildArgs); 56 | } catch (e: any) { 57 | throw new NomicLabsHardhatPluginError( 58 | "@foundry-rs/hardhat-forge", 59 | `Forge build config is invalid: ${e.message}`, 60 | e 61 | ); 62 | } 63 | return uncheckedBuildArgs as ForgeTestArgs; 64 | } 65 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/test/test-ti.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module was automatically generated by `ts-interface-builder` 3 | */ 4 | import * as t from "ts-interface-checker"; 5 | // tslint:disable:object-literal-key-quotes 6 | 7 | export const ForgeTestArgsTi = t.iface(["ForgeBuildArgs", "ForgeEvmArgs"], { 8 | json: t.opt("boolean"), 9 | gasReport: t.opt("boolean"), 10 | allowFailure: t.opt("boolean"), 11 | etherscanApiKey: t.opt("string"), 12 | matchTest: t.opt("string"), 13 | matchContract: t.opt("string"), 14 | }); 15 | 16 | const exportedTypeSuite: t.ITypeSuite = { 17 | ForgeTestArgsTi, 18 | }; 19 | // eslint-disable-next-line import/no-default-export 20 | export default exportedTypeSuite; 21 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/test/test.ts: -------------------------------------------------------------------------------- 1 | // bindings for forge test 2 | import { spawn as spawn } from "child_process"; 3 | import * as foundryup from "@foundry-rs/easy-foundryup"; 4 | import { buildArgs, ForgeBuildArgs } from "../build/build"; 5 | import { envArgs, evmArgs, ForgeEvmArgs } from "../common"; 6 | 7 | /** 8 | * Mirrors the `forge test` arguments 9 | */ 10 | export interface ForgeTestArgs extends ForgeBuildArgs, ForgeEvmArgs { 11 | json?: boolean; 12 | gasReport?: boolean; 13 | allowFailure?: boolean; 14 | etherscanApiKey?: string; 15 | matchTest?: string; 16 | matchContract?: string; 17 | } 18 | 19 | /** * 20 | * Invokes `forge build` 21 | * @param opts The arguments to pass to `forge build` 22 | */ 23 | export async function spawnTest(opts: ForgeTestArgs): Promise { 24 | const args = ["test", ...testArgs(opts)]; 25 | const forgeCmd = await foundryup.getForgeCommand(); 26 | return new Promise((resolve) => { 27 | const process = spawn(forgeCmd, args, { 28 | stdio: "inherit", 29 | }); 30 | process.on("exit", (code) => { 31 | resolve(code === 0); 32 | }); 33 | }); 34 | } 35 | 36 | /** 37 | * Converts the `args` object into a list of arguments for the `forge test` command 38 | * @param args 39 | */ 40 | export function testArgs(args: ForgeTestArgs): string[] { 41 | const allArgs: string[] = []; 42 | const etherscanApiKey = args.etherscanApiKey ?? ""; 43 | if (etherscanApiKey) { 44 | allArgs.push("--etherscan-api-key", etherscanApiKey); 45 | } 46 | 47 | if (args.json ?? false) { 48 | allArgs.push("--json"); 49 | } 50 | 51 | if (args.allowFailure ?? false) { 52 | allArgs.push("--allow-failure"); 53 | } 54 | 55 | if (args.gasReport ?? false) { 56 | allArgs.push("--gas-report"); 57 | } 58 | 59 | const matchTest = args.matchTest ?? ""; 60 | if (matchTest) { 61 | allArgs.push("--match-test", matchTest); 62 | } 63 | 64 | const matchContract = args.matchContract ?? ""; 65 | if (matchContract) { 66 | allArgs.push("--match-contract", matchContract); 67 | } 68 | 69 | allArgs.push(...buildArgs(args)); 70 | allArgs.push(...evmArgs(args)); 71 | allArgs.push(...envArgs(args)); 72 | 73 | return allArgs; 74 | } 75 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/forge/types.ts: -------------------------------------------------------------------------------- 1 | import "hardhat/types/config"; 2 | import { BuildInfo } from "hardhat/types"; 3 | import { ForgeEvmArgs } from "./common/evm"; 4 | import { ForgeBuildArgs } from "./build/build"; 5 | 6 | export interface FoundryHardhatConfig 7 | extends Partial, 8 | Partial { 9 | runSuper?: boolean; 10 | writeArtifacts?: boolean; 11 | } 12 | 13 | declare module "hardhat/types/config" { 14 | interface HardhatConfig { 15 | foundry?: Partial; 16 | } 17 | 18 | interface HardhatUserConfig { 19 | foundry?: Partial; 20 | } 21 | } 22 | 23 | /** 24 | * Represents an artifact emitted by forge 25 | */ 26 | export declare interface ForgeArtifact { 27 | abi: any[]; 28 | bytecode: Bytecode; 29 | deployedBytecode: Bytecode; 30 | ast?: any; 31 | assembly?: any; 32 | methodIdentifiers?: any; 33 | generatedSources?: any; 34 | functionDebugData?: any; 35 | gasEstimates?: any; 36 | metadata?: any; 37 | storageLayout?: any; 38 | userdoc?: any; 39 | devdoc?: any; 40 | ir?: any; 41 | irOptimized?: any; 42 | ewasm?: any; 43 | } 44 | 45 | export declare interface Bytecode { 46 | object?: string; 47 | sourceMap?: string; 48 | linkReferences?: any; 49 | } 50 | 51 | export interface ForgeCache { 52 | _format: string; 53 | paths: Paths; 54 | files: Map; 55 | } 56 | 57 | export interface Paths { 58 | artifacts: string; 59 | sources: string; 60 | tests: string; 61 | libraries: string[]; 62 | } 63 | 64 | export interface FileEntry { 65 | lastModificationDate: number; 66 | contentHash: string; 67 | sourceName: string; 68 | imports: any[]; 69 | versionRequirement: string; 70 | artifacts: Map; 71 | } 72 | 73 | // Represents a BuildInfo and the path 74 | // to its file on the filesystem 75 | export interface BuildInfoArtifact { 76 | buildInfo: BuildInfo; 77 | buildInfoPath: string; 78 | } 79 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./forge/types"; 2 | import { extendEnvironment, extendConfig } from "hardhat/config"; 3 | import { lazyObject } from "hardhat/plugins"; 4 | import { 5 | HardhatConfig, 6 | HardhatUserConfig, 7 | HardhatRuntimeEnvironment, 8 | } from "hardhat/types"; 9 | import path from "path"; 10 | import { ForgeArtifacts, spawnConfigSync } from "./forge"; 11 | 12 | export * from "./task-names"; 13 | export * as forge from "./forge"; 14 | 15 | extendEnvironment((hre: HardhatRuntimeEnvironment) => { 16 | // patches the default artifacts handler 17 | (hre as any).artifacts = lazyObject(() => { 18 | const config = spawnConfigSync(); 19 | const outDir = path.join(hre.config.paths.root, config.out); 20 | const buildInfoDir = 21 | typeof config.build_info_path === "string" 22 | ? config.build_info_path 23 | : path.join(outDir, "build-info"); 24 | 25 | const artifacts = new ForgeArtifacts( 26 | hre.config.paths.root, 27 | outDir, 28 | hre.config.paths.artifacts, 29 | buildInfoDir, 30 | config.build_info 31 | ); 32 | 33 | return artifacts; 34 | }); 35 | }); 36 | 37 | extendConfig( 38 | (config: HardhatConfig, userConfig: Readonly) => { 39 | config.foundry = lazyObject(() => { 40 | // Set default values then merge user defined values 41 | return { 42 | runSuper: false, 43 | writeArtifacts: true, 44 | ...userConfig.foundry, 45 | }; 46 | }); 47 | } 48 | ); 49 | -------------------------------------------------------------------------------- /packages/hardhat-forge/src/task-names.ts: -------------------------------------------------------------------------------- 1 | export declare const TASK_FORGE_CONFIG = "forge:config"; 2 | export declare const TASK_FORGE_TEST = "forge:test"; 3 | -------------------------------------------------------------------------------- /packages/hardhat-forge/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [`${__dirname}/../.eslintrc.js`], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | devDependencies: true, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/hardhat-forge/test/fixture-projects/hardhat-project/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] 7 | build_info = true 8 | build_info_path = 'build-info' 9 | -------------------------------------------------------------------------------- /packages/hardhat-forge/test/fixture-projects/hardhat-project/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | // We load the plugin here. 2 | import { HardhatUserConfig } from "hardhat/types"; 3 | 4 | import "../../../src/index"; 5 | 6 | const config: HardhatUserConfig = { 7 | solidity: "0.7.3", 8 | defaultNetwork: "hardhat", 9 | foundry: { 10 | viaIr: true, 11 | writeArtifacts: true, 12 | }, 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /packages/hardhat-forge/test/fixture-projects/hardhat-project/src/Contract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract Contract { 5 | uint256 public bar; 6 | function example() public {} 7 | } 8 | -------------------------------------------------------------------------------- /packages/hardhat-forge/test/fixture-projects/hardhat-project/src/ContractTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract ContractTest { 5 | 6 | function testExample() public { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/hardhat-forge/test/fixture-projects/hardhat-project/src/foo/Contract2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | contract Contract { 5 | function example() public {} 6 | 7 | } 8 | -------------------------------------------------------------------------------- /packages/hardhat-forge/test/helpers.ts: -------------------------------------------------------------------------------- 1 | import { resetHardhatContext } from "hardhat/plugins-testing"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import fsExtra from "fs-extra"; 4 | 5 | import path from "path"; 6 | 7 | declare module "mocha" { 8 | interface Context { 9 | hre: HardhatRuntimeEnvironment; 10 | } 11 | } 12 | 13 | export function useEnvironment(fixtureProjectName: string) { 14 | beforeEach("Loading hardhat environment", function () { 15 | process.chdir(path.join(__dirname, "fixture-projects", fixtureProjectName)); 16 | 17 | this.hre = require("hardhat"); 18 | }); 19 | 20 | afterEach("Resetting hardhat", function () { 21 | resetHardhatContext(); 22 | }); 23 | } 24 | 25 | export async function getAllFiles(directory: string, files: string[] = []) { 26 | const current = await fsExtra.readdir(directory); 27 | for (const file of current) { 28 | const next = path.join(directory, file); 29 | const info = await fsExtra.stat(next); 30 | if (info.isDirectory()) { 31 | files = await getAllFiles(next, files); 32 | } else { 33 | files.push(next); 34 | } 35 | } 36 | return files; 37 | } 38 | -------------------------------------------------------------------------------- /packages/hardhat-forge/test/project.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import path from "path"; 3 | import { useEnvironment, getAllFiles } from "./helpers"; 4 | 5 | describe("Integration tests", function () { 6 | this.timeout(300000); 7 | describe("Hardhat Runtime Environment extension", function () { 8 | useEnvironment("hardhat-project"); 9 | 10 | it("Should build", async function () { 11 | await this.hre.run("compile"); 12 | }); 13 | 14 | it("Should test", async function () { 15 | await this.hre.run("test"); 16 | }); 17 | 18 | it("Should return config", async function () { 19 | const config = await this.hre.run("forge:config"); 20 | assert.equal(config.src, "src"); 21 | assert.equal(config.out, "out"); 22 | }); 23 | 24 | it("Should populare hre.config.foundry", async function () { 25 | assert.exists(this.hre.config.foundry); 26 | assert.typeOf(this.hre.config.foundry, "object"); 27 | assert.equal(this.hre.config.foundry?.viaIr, true); 28 | }); 29 | 30 | it("Should read artifacts", async function () { 31 | const artifacts = await this.hre.artifacts.getArtifactPaths(); 32 | assert.isNotEmpty(artifacts); 33 | const contract = await this.hre.artifacts.readArtifact( 34 | "Contract.sol:Contract" 35 | ); 36 | assert.equal(contract.sourceName, "src/Contract.sol"); 37 | assert.exists(contract.abi); 38 | assert.exists(contract.bytecode); 39 | assert.typeOf(contract.bytecode, "string"); 40 | assert.exists(contract.deployedBytecode); 41 | assert.typeOf(contract.deployedBytecode, "string"); 42 | assert.exists(contract.linkReferences); 43 | assert.exists(contract.deployedLinkReferences); 44 | assert.exists(contract.contractName); 45 | assert.exists(contract.sourceName); 46 | }); 47 | 48 | it("Should write artifacts to disk", async function () { 49 | const artifacts = await this.hre.artifacts.getArtifactPaths(); 50 | const files = await getAllFiles(this.hre.config.paths.artifacts); 51 | // filter out the debug files and metadata 52 | const filtered = files 53 | .filter((f) => !f.includes(".dbg.json")) 54 | .filter((f) => !f.includes(".metadata.json")); 55 | assert.equal(artifacts.length, filtered.length); 56 | 57 | for (const file of filtered) { 58 | const name = path.basename(file); 59 | assert(artifacts.map((a) => path.basename(a)).includes(name)); 60 | const artifact = require(file); 61 | assert.equal(artifact.contractName, path.basename(name, ".json")); 62 | } 63 | }); 64 | 65 | it("Should write debug files to disk", async function () { 66 | const debugFilePaths = await this.hre.artifacts.getDebugFilePaths(); 67 | const artifactPaths = await this.hre.artifacts.getArtifactPaths(); 68 | assert.equal(debugFilePaths.length, artifactPaths.length); 69 | 70 | for (const debugFile of debugFilePaths) { 71 | const debug = require(debugFile); 72 | assert.equal(debug._format, "hh-sol-dbg-1"); 73 | assert.exists(debug.buildInfo); 74 | } 75 | }); 76 | 77 | it("Should return build info", async function () { 78 | const info = await this.hre.artifacts.getBuildInfo( 79 | "Contract.sol:Contract" 80 | ); 81 | assert.exists(info); 82 | const contract = info?.output.contracts["src/Contract.sol"].Contract!; 83 | assert.exists(contract); 84 | assert.exists(contract.abi); 85 | assert.exists((contract as any).devdoc); 86 | assert.exists((contract as any).metadata); 87 | assert.typeOf((contract as any).metadata, "object"); 88 | assert.exists((contract as any).storageLayout); 89 | assert.exists((contract as any).userdoc); 90 | assert.exists(contract.evm); 91 | assert.exists(contract.evm.bytecode); 92 | assert.exists(contract.evm.bytecode.object); 93 | assert.exists(contract.evm.deployedBytecode); 94 | assert.exists(contract.evm.deployedBytecode.object); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /packages/hardhat-forge/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../config/typescript/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "exclude": ["./dist", "./node_modules", "./test/**/hardhat.config.ts"], 7 | "references": [ 8 | { 9 | "path": "../easy-foundryup/src" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/hardhat/.eslintignore: -------------------------------------------------------------------------------- 1 | test/fixture-projects/**/* 2 | -------------------------------------------------------------------------------- /packages/hardhat/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [`${__dirname}/../../config/eslint/eslintrc.js`], 3 | parserOptions: { 4 | project: `${__dirname}/tsconfig.json`, 5 | sourceType: "module", 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/hardhat/.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | /node_modules 3 | 4 | # Compilation output 5 | /build-test/ 6 | /dist 7 | 8 | # Code coverage artifacts 9 | /coverage 10 | /.nyc_output 11 | 12 | # Below is Github's node gitignore template, 13 | # ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing 14 | 15 | # Logs 16 | logs 17 | *.log 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | lerna-debug.log* 22 | 23 | # Diagnostic reports (https://nodejs.org/api/report.html) 24 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | coverage 37 | 38 | # nyc test coverage 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | .grunt 43 | 44 | # Bower dependency directory (https://bower.io/) 45 | bower_components 46 | 47 | # node-waf configuration 48 | .lock-wscript 49 | 50 | # Compiled binary addons (https://nodejs.org/api/addons.html) 51 | build/Release 52 | 53 | # Dependency directories 54 | #node_modules/ 55 | jspm_packages/ 56 | 57 | # TypeScript v1 declaration files 58 | typings/ 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # next.js build output 79 | .next 80 | 81 | # nuxt.js build output 82 | .nuxt 83 | 84 | # vuepress build output 85 | .vuepress/dist 86 | 87 | # Serverless directories 88 | .serverless/ 89 | 90 | # FuseBox cache 91 | .fusebox/ 92 | 93 | # DynamoDB Local files 94 | .dynamodb/ 95 | 96 | -------------------------------------------------------------------------------- /packages/hardhat/.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register/files", 3 | "ignore": ["test/fixture-projects/**/*"], 4 | "timeout": 10000 5 | } -------------------------------------------------------------------------------- /packages/hardhat/.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /test/fixture-projects/**/artifacts 4 | /test/fixture-projects/**/cache 5 | CHANGELOG.md 6 | -------------------------------------------------------------------------------- /packages/hardhat/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @foundry-rs/hardhat 2 | 3 | ## 0.1.7 4 | 5 | ### Patch Changes 6 | 7 | - ba96df2: fix flaky tests 8 | - Updated dependencies [7e193d4] 9 | - Updated dependencies [7ed40a4] 10 | - Updated dependencies [bb320b3] 11 | - Updated dependencies [ba96df2] 12 | - Updated dependencies [44c261c] 13 | - @foundry-rs/hardhat-forge@0.1.8 14 | - @foundry-rs/hardhat-anvil@0.1.6 15 | 16 | ## 0.1.6 17 | 18 | ### Patch Changes 19 | 20 | - e02aee0: fix flaky tests 21 | - Updated dependencies [e02aee0] 22 | - Updated dependencies [e02aee0] 23 | - Updated dependencies [e02aee0] 24 | - Updated dependencies [e02aee0] 25 | - @foundry-rs/hardhat-forge@0.1.7 26 | - @foundry-rs/hardhat-anvil@0.1.5 27 | 28 | ## 0.1.5 29 | 30 | ### Patch Changes 31 | 32 | - 9ba6d04: use correct output dir for main entry 33 | - 6d0971c: fix: reexport via package name import 34 | - 505357b: Fix build ordering 35 | - 47bb439: add test change 36 | - 36c829b: fix dependency and anvil command issues 37 | - Updated dependencies [7d3f9f3] 38 | - Updated dependencies [9ba6d04] 39 | - Updated dependencies [64750ac] 40 | - Updated dependencies [36c829b] 41 | - Updated dependencies [736941e] 42 | - Updated dependencies [b092fe5] 43 | - Updated dependencies [a7bb4be] 44 | - @foundry-rs/hardhat-forge@0.1.4 45 | - @foundry-rs/hardhat-anvil@0.1.4 46 | - @foundry-rs/easy-foundryup@0.1.3 47 | 48 | ## 0.1.4 49 | 50 | ### Patch Changes 51 | 52 | - 373974f: use correct output dir for main entry 53 | - 373974f: add test change 54 | 55 | ## 0.1.3 56 | 57 | ### Patch Changes 58 | 59 | - 794c306: add test change 60 | 61 | ## 0.1.2 62 | 63 | ### Patch Changes 64 | 65 | - 22f5a65: initial release 66 | - Updated dependencies [22f5a65] 67 | - @foundry-rs/easy-foundryup@0.1.2 68 | 69 | ## 0.1.1 70 | 71 | ### Patch Changes 72 | 73 | - 24c943a: initial release 74 | - 2923385: initial release 75 | - Updated dependencies [24c943a] 76 | - Updated dependencies [2923385] 77 | - @foundry-rs/easy-foundryup@0.1.1 78 | -------------------------------------------------------------------------------- /packages/hardhat/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nomic Labs LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/hardhat/README.md: -------------------------------------------------------------------------------- 1 | # hardhat 2 | 3 | Aggregated foundry hardhat plugin 4 | 5 | ## Installation 6 | 7 | See in each plugin 8 | 9 | - [anvil](../hardhat-anvil/README.md) 10 | - [forge](../hardhat-forge/README.md) 11 | - [foundryup](../easy-foundryup/README.md) 12 | 13 | ## LICENSE 14 | 15 | - MIT license ([LICENSE](LICENSE) or https://opensource.org/licenses/MIT) 16 | -------------------------------------------------------------------------------- /packages/hardhat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@foundry-rs/hardhat", 3 | "version": "0.1.7", 4 | "description": "Foundry's hardhat plugin", 5 | "homepage": "https://github.com/foundry-rs/hardhat", 6 | "repository": "github:foundry-rs/hardhat", 7 | "license": "MIT", 8 | "main": "dist/src/index.js", 9 | "types": "dist/src/index.d.ts", 10 | "keywords": [ 11 | "ethereum", 12 | "smart-contracts", 13 | "hardhat", 14 | "hardhat-plugin", 15 | "anvil", 16 | "foundry", 17 | "forge", 18 | "solidity", 19 | "testing-network" 20 | ], 21 | "scripts": { 22 | "lint": "yarn prettier --check && yarn eslint", 23 | "lint:fix": "yarn prettier --write && yarn eslint --fix", 24 | "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", 25 | "prettier": "prettier \"**/*.{js,md}\"", 26 | "test": "mocha --recursive \"test/**/*.ts\" --exit", 27 | "build": "tsc --build .", 28 | "clean": "rimraf dist" 29 | }, 30 | "files": [ 31 | "dist/", 32 | "src/", 33 | "LICENSE", 34 | "README.md" 35 | ], 36 | "dependencies": { 37 | "@foundry-rs/easy-foundryup": "^0.1.3", 38 | "@foundry-rs/hardhat-anvil": "^0.1.6", 39 | "@foundry-rs/hardhat-forge": "^0.1.8", 40 | "@types/sinon-chai": "^3.2.3", 41 | "@types/web3": "1.0.19", 42 | "camelcase-keys": "^7.0.2", 43 | "debug": "^4.1.1", 44 | "ethers": "^5.0.0", 45 | "ts-interface-checker": "^0.1.9" 46 | }, 47 | "devDependencies": { 48 | "@nomiclabs/eslint-plugin-hardhat-internal-rules": "^1.0.0", 49 | "@nomiclabs/hardhat-ethers": "^2.0.0", 50 | "@types/chai": "^4.2.0", 51 | "@types/debug": "^4.1.4", 52 | "@types/fs-extra": "^5.1.0", 53 | "@types/mocha": "^9.1.0", 54 | "@types/node": "^12.0.0", 55 | "@typescript-eslint/eslint-plugin": "4.29.2", 56 | "@typescript-eslint/parser": "4.29.2", 57 | "chai": "^4.2.0", 58 | "eslint": "^7.29.0", 59 | "eslint-config-prettier": "8.3.0", 60 | "eslint-plugin-import": "2.24.1", 61 | "eslint-plugin-prettier": "3.4.0", 62 | "ethereum-waffle": "^3.2.0", 63 | "hardhat": "^2.0.0", 64 | "mocha": "^9.2.0", 65 | "prettier": "2.4.1", 66 | "rimraf": "^3.0.2", 67 | "ts-interface-builder": "^0.2.0", 68 | "ts-node": "^8.1.0", 69 | "typescript": "~4.5.2" 70 | }, 71 | "peerDependencies": { 72 | "@nomiclabs/hardhat-ethers": "^2.0.0", 73 | "ethereum-waffle": "^3.2.0", 74 | "ethers": "^5.0.0", 75 | "hardhat": "^2.0.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/hardhat/src/index.ts: -------------------------------------------------------------------------------- 1 | export * as forge from "@foundry-rs/hardhat-forge"; 2 | export * as anvil from "@foundry-rs/hardhat-anvil"; 3 | -------------------------------------------------------------------------------- /packages/hardhat/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [`${__dirname}/../.eslintrc.js`], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | devDependencies: true, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/hardhat/test/fixture-projects/hardhat-project/.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | -------------------------------------------------------------------------------- /packages/hardhat/test/fixture-projects/hardhat-project/foundry.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /packages/hardhat/test/fixture-projects/hardhat-project/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | // We load the plugin here. 2 | import { HardhatUserConfig } from "hardhat/types"; 3 | 4 | import "../../../src"; 5 | 6 | const config: HardhatUserConfig = { 7 | solidity: "0.7.3", 8 | defaultNetwork: "hardhat" 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /packages/hardhat/test/fixture-projects/hardhat-project/src/Contract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract Contract {} 5 | -------------------------------------------------------------------------------- /packages/hardhat/test/fixture-projects/no-anvil-config/foundry.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /packages/hardhat/test/fixture-projects/no-anvil-config/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | // We load the plugin here. 2 | import { HardhatUserConfig } from "hardhat/types"; 3 | 4 | import "../../../src/index"; 5 | 6 | const config: HardhatUserConfig = { 7 | solidity: "0.7.3" 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /packages/hardhat/test/fixture-projects/no-anvil-config/src/Contract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract Contract {} 5 | -------------------------------------------------------------------------------- /packages/hardhat/test/helpers.ts: -------------------------------------------------------------------------------- 1 | import { resetHardhatContext } from "hardhat/plugins-testing"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import path from "path"; 4 | import net from "net"; 5 | 6 | declare module "mocha" { 7 | interface Context { 8 | hre: HardhatRuntimeEnvironment; 9 | } 10 | } 11 | 12 | export function useEnvironment(fixtureProjectName: string) { 13 | beforeEach("Loading hardhat environment", async function () { 14 | process.chdir(path.join(__dirname, "fixture-projects", fixtureProjectName)); 15 | 16 | this.hre = require("hardhat"); 17 | this.freePort = await getPortFree(); 18 | }); 19 | 20 | afterEach("Resetting hardhat", function () { 21 | resetHardhatContext(); 22 | }); 23 | } 24 | 25 | async function getPortFree() { 26 | return new Promise((res) => { 27 | const srv = net.createServer() as any; 28 | srv.listen(0, () => { 29 | const port = srv.address().port; 30 | srv.close((_err: any) => res(port)); 31 | }); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /packages/hardhat/test/project.test.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line no-implicit-dependencies 2 | // eslint-disable-next-line import/no-extraneous-dependencies 3 | 4 | import { assert } from "chai"; 5 | import { AnvilService } from "@foundry-rs/hardhat-anvil/src/anvil-service"; 6 | import { useEnvironment } from "./helpers"; 7 | 8 | describe("Integration tests", function () { 9 | this.timeout(300000); 10 | describe("Hardhat Runtime Environment extension", function () { 11 | useEnvironment("hardhat-project"); 12 | 13 | it("Should build", async function () { 14 | await this.hre.run("compile", {}); 15 | }); 16 | 17 | it("Should add run anvil node", async function () { 18 | void this.hre.run("node", { port: this.freePort }); 19 | // ensure we don't wait forever 20 | await new Promise((resolve) => setTimeout(resolve, 5000)); 21 | }); 22 | }); 23 | describe("Hardhat Runtime Environment extension", function () { 24 | useEnvironment("no-anvil-config"); 25 | 26 | it("Should expose anvil defaults configs in hardhat's config", function () { 27 | assert.isDefined(this.hre.config.networks.anvil); 28 | const defaultOptions = AnvilService.getDefaultOptions() as any; 29 | const options = this.hre.config.networks.anvil as any; 30 | 31 | // Iterate over all default options and assert equality 32 | for (const [key, value] of Object.entries(defaultOptions)) { 33 | assert.equal(options[key], value); 34 | } 35 | }); 36 | 37 | it("Should add run anvil node", async function () { 38 | void this.hre.run("node", { port: this.freePort }); 39 | // ensure we don't wait forever 40 | await new Promise((resolve) => setTimeout(resolve, 5000)); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/hardhat/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../config/typescript/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "exclude": ["./dist", "./node_modules", "./test/**/hardhat.config.ts"], 7 | "references": [ 8 | { 9 | "path": "../easy-foundryup/src" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest", 4 | "tslint-plugin-prettier", 5 | "tslint-config-prettier" 6 | ], 7 | "rules": { 8 | "prettier": true, 9 | "object-literal-sort-keys": false, 10 | "no-submodule-imports": false, 11 | "interface-name": false, 12 | "max-classes-per-file": false, 13 | "no-empty": false, 14 | "no-console": false, 15 | "only-arrow-functions": false, 16 | "variable-name": [ 17 | true, 18 | "check-format", 19 | "allow-leading-underscore", 20 | "allow-pascal-case" 21 | ], 22 | "ordered-imports": [ 23 | true, 24 | { 25 | "grouped-imports": true, 26 | "import-sources-order": "case-insensitive" 27 | } 28 | ], 29 | "no-floating-promises": true, 30 | "prefer-conditional-expression": false, 31 | "no-implicit-dependencies": true 32 | } 33 | } 34 | --------------------------------------------------------------------------------