├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── checks.yml │ ├── codeql.yml │ ├── publish-npm.yml │ └── test.yml ├── .gitignore ├── .mocharc.json ├── .prettierignore ├── .prettierrc.yml ├── FUNDING.json ├── LICENSE ├── README.md ├── eslint.config.js ├── package.json ├── pnpm-lock.yaml ├── renovate.json ├── src ├── abi │ └── CreateX.json ├── contracts │ └── CreateX.sol ├── index.ts ├── internal │ ├── config.ts │ ├── type-extensions.ts │ └── types.ts └── utils │ ├── colour-codes.ts │ ├── constants.ts │ ├── networks.ts │ └── table.ts ├── test ├── fixture-projects │ ├── hardhat-project-with-constructor │ │ ├── .gitignore │ │ ├── contracts │ │ │ ├── Create2DeployerLocal.sol │ │ │ ├── CreateX.sol │ │ │ ├── ERC20Mock.sol │ │ │ └── imported │ │ │ │ ├── Context.sol │ │ │ │ ├── ERC20.sol │ │ │ │ ├── IERC20.sol │ │ │ │ └── IERC20Metadata.sol │ │ ├── deploy-args.ts │ │ └── hardhat.config.ts │ ├── hardhat-project-without-constructor │ │ ├── .gitignore │ │ ├── contracts │ │ │ ├── Create2DeployerLocal.sol │ │ │ ├── CreateX.sol │ │ │ └── SimpleContract.sol │ │ └── hardhat.config.ts │ └── sepolia-holesky-project-with-constructor │ │ ├── .gitignore │ │ ├── contracts │ │ ├── ERC20Mock.sol │ │ └── imported │ │ │ ├── Context.sol │ │ │ ├── ERC20.sol │ │ │ ├── IERC20.sol │ │ │ └── IERC20Metadata.sol │ │ ├── deploy-args.ts │ │ └── hardhat.config.ts ├── helpers.ts ├── xdeploy-list-networks.test.ts ├── xdeploy-sepolia-holesky-with-constructor.test.ts ├── xdeploy-with-constructor.test.ts └── xdeploy-without-constructor.test.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = false 10 | 11 | [*.sol] 12 | indent_size = 4 13 | max_line_length = 120 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://etherscan.io/address/0x07bF3CDA34aA78d92949bbDce31520714AB5b228 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug Report" 2 | description: "Report a bug for xdeployer." 3 | title: "[Bug-Candidate]: " 4 | labels: 5 | - bug 🐛 6 | assignees: 7 | - pcaversaccio 8 | body: 9 | - attributes: 10 | value: | 11 | Please check the [issues tab](https://github.com/pcaversaccio/xdeployer/issues) to avoid duplicates. 12 | Thanks for taking the time to fill out this bug report! 13 | type: markdown 14 | 15 | - attributes: 16 | label: "Describe the issue:" 17 | id: what-happened 18 | type: textarea 19 | validations: 20 | required: true 21 | 22 | - attributes: 23 | label: "Code example to reproduce the issue:" 24 | description: "It can be a GitHub repository/gist or a simple code snippet." 25 | placeholder: "```ts\nasync function main() {}\n```" 26 | id: reproduce 27 | type: textarea 28 | validations: 29 | required: true 30 | 31 | - attributes: 32 | label: "Version:" 33 | description: | 34 | What version of xdeployer are you running? 35 | Run `pnpm list --depth=0` and check the xdeployer version. 36 | id: version 37 | type: textarea 38 | validations: 39 | required: true 40 | 41 | - attributes: 42 | label: "Relevant log output:" 43 | description: | 44 | Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 45 | render: Shell 46 | id: logs 47 | type: textarea 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature Request" 2 | description: "Suggest an idea for xdeployer." 3 | title: "[Feature-Request]: " 4 | labels: 5 | - feature 💥 6 | assignees: 7 | - pcaversaccio 8 | body: 9 | - attributes: 10 | value: | 11 | Please check the [issues tab](https://github.com/pcaversaccio/xdeployer/issues) to avoid duplicates. 12 | Thanks for taking the time to provide feedback on xdeployer! 13 | type: markdown 14 | 15 | - attributes: 16 | label: "Describe the desired feature:" 17 | description: Explain what the feature solves/improves. 18 | id: feature-request 19 | type: textarea 20 | validations: 21 | required: true 22 | 23 | - attributes: 24 | label: "Code example that solves the feature:" 25 | description: "It can be a GitHub repository/gist or a simple code snippet." 26 | placeholder: "```ts\nasync function main() {}\n```" 27 | id: reproduce 28 | type: textarea 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "monday" 8 | target-branch: "main" 9 | labels: 10 | - "dependencies 🔁" 11 | assignees: 12 | - "pcaversaccio" 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | #### ✅ PR Checklist 13 | 14 | - [ ] Because this PR includes a **bug fix**, relevant tests have been included. 15 | - [ ] Because this PR includes a **new feature**, the change was previously discussed in an [issue](https://github.com/pcaversaccio/xdeployer/issues) or in the [discussions](https://github.com/pcaversaccio/xdeployer/discussions) section. 16 | - [x] I didn't do anything of this. 17 | 18 | ### 🕓 Changelog 19 | 20 | 21 | 22 | #### 🐶 Cute Animal Picture 23 | 24 | ![Put a link to a cute animal picture inside the parenthesis-->]() 25 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: 👮‍♂️ Sanity checks 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | prettify-n-lint: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | node_version: 17 | - 22 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v4 25 | with: 26 | run_install: false 27 | 28 | - name: Get pnpm cache directory path 29 | id: pnpm-cache-dir-path 30 | run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT 31 | 32 | - name: Restore pnpm cache 33 | uses: actions/cache@v4 34 | id: pnpm-cache 35 | with: 36 | path: ${{ steps.pnpm-cache-dir-path.outputs.dir }} 37 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 38 | restore-keys: | 39 | ${{ runner.os }}-pnpm-store- 40 | 41 | - name: Use Node.js ${{ matrix.node_version }} 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version: ${{ matrix.node_version }} 45 | 46 | - name: Install pnpm project with a clean slate 47 | run: pnpm install --prefer-offline --frozen-lockfile 48 | 49 | - name: Prettier and lint 50 | run: pnpm lint:check 51 | 52 | codespell: 53 | runs-on: ${{ matrix.os }} 54 | strategy: 55 | matrix: 56 | os: 57 | - ubuntu-latest 58 | 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v4 62 | 63 | - name: Run codespell 64 | uses: codespell-project/actions-codespell@v2 65 | with: 66 | check_filenames: true 67 | skip: ./.git,pnpm-lock.yaml 68 | ignore_words_list: superseed 69 | 70 | validate-links: 71 | runs-on: ${{ matrix.os }} 72 | strategy: 73 | matrix: 74 | os: 75 | - ubuntu-latest 76 | ruby_version: 77 | - 3.4 78 | 79 | steps: 80 | - name: Checkout 81 | uses: actions/checkout@v4 82 | 83 | - name: Set up Ruby 84 | uses: ruby/setup-ruby@v1 85 | with: 86 | ruby-version: ${{ matrix.ruby_version }} 87 | bundler-cache: true 88 | 89 | - name: Install awesome_bot 90 | run: gem install awesome_bot 91 | 92 | - name: Validate URLs 93 | run: | 94 | awesome_bot ./*.md src/*.ts src/**/*.ts --allow-dupe --request-delay 0.4 \ 95 | --white-list http://127.0.0.1:8545,https://github.com/pcaversaccio/createx,https://rpc.sepolia.org,https://holesky.rpc.thirdweb.com,https://www.oklink.com,https://5irescan.io,https://testnet.5irescan.io 96 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: 🔍️ CodeQL 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | analyse: 11 | runs-on: ${{ matrix.os }} 12 | permissions: 13 | security-events: write 14 | strategy: 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | language: 19 | - javascript-typescript 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | 25 | - name: Initialise CodeQL 26 | uses: github/codeql-action/init@v3 27 | with: 28 | languages: ${{ matrix.language }} 29 | queries: +security-and-quality 30 | 31 | - name: Autobuild 32 | uses: github/codeql-action/autobuild@v3 33 | 34 | - name: Perform CodeQL analysis 35 | uses: github/codeql-action/analyze@v3 36 | with: 37 | category: "/language:${{ matrix.language }}" 38 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: 📦 Publish xdeployer to npm 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | runs-on: ${{ matrix.os }} 11 | environment: 12 | name: npm 13 | url: https://npmjs.com/package/xdeployer 14 | permissions: 15 | id-token: write 16 | strategy: 17 | matrix: 18 | os: 19 | - ubuntu-latest 20 | node_version: 21 | - 22 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | 27 | - name: Install pnpm 28 | uses: pnpm/action-setup@v4 29 | with: 30 | run_install: false 31 | 32 | - name: Get pnpm cache directory path 33 | id: pnpm-cache-dir-path 34 | run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT 35 | 36 | - name: Restore pnpm cache 37 | uses: actions/cache@v4 38 | id: pnpm-cache 39 | with: 40 | path: ${{ steps.pnpm-cache-dir-path.outputs.dir }} 41 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 42 | restore-keys: | 43 | ${{ runner.os }}-pnpm-store- 44 | 45 | - name: Setup .npmrc file to publish to npm 46 | uses: actions/setup-node@v4 47 | with: 48 | node-version: ${{ matrix.node_version }} 49 | registry-url: "https://registry.npmjs.org" 50 | 51 | - name: Install pnpm project with a clean slate 52 | run: pnpm install --prefer-offline --frozen-lockfile 53 | 54 | - name: Publish to npm 55 | run: pnpm publish --no-git-checks 56 | env: 57 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 58 | NPM_CONFIG_PROVENANCE: true 59 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 🕵️‍♂️ Test xdeploy 2 | 3 | on: [push, pull_request, pull_request_target, workflow_dispatch] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | tests: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | node_version: 17 | - 22 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v4 25 | with: 26 | run_install: false 27 | 28 | - name: Get pnpm cache directory path 29 | id: pnpm-cache-dir-path 30 | run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT 31 | 32 | - name: Restore pnpm cache 33 | uses: actions/cache@v4 34 | id: pnpm-cache 35 | with: 36 | path: ${{ steps.pnpm-cache-dir-path.outputs.dir }} 37 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 38 | restore-keys: | 39 | ${{ runner.os }}-pnpm-store- 40 | 41 | - name: Use Node.js ${{ matrix.node_version }} 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version: ${{ matrix.node_version }} 45 | 46 | - name: Install pnpm project with a clean slate 47 | run: pnpm install --prefer-offline --frozen-lockfile 48 | 49 | - name: Run tests 50 | if: ${{ (github.event_name == 'push') || (github.event_name == 'workflow_dispatch') || (github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request') || (github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target') }} 51 | run: pnpm test 52 | env: 53 | HARDHAT_VAR_XDEPLOYER_TEST_ACCOUNT: ${{ secrets.XDEPLOYER_TEST_ACCOUNT }} 54 | HARDHAT_VAR_ETH_SEPOLIA_TESTNET_URL: ${{ secrets.ETH_SEPOLIA_TESTNET_URL }} 55 | HARDHAT_VAR_ETH_HOLESKY_TESTNET_URL: ${{ secrets.ETH_HOLESKY_TESTNET_URL }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all Visual Studio Code settings 2 | .vscode/* 3 | !.vscode/settings.json 4 | !.vscode/tasks.json 5 | !.vscode/launch.json 6 | !.vscode/extensions.json 7 | !.vscode/*.code-snippets 8 | 9 | # Local history for Visual Studio Code 10 | .history 11 | 12 | # Built Visual Studio Code extensions 13 | *.vsix 14 | 15 | # Dependency directory 16 | node_modules 17 | 18 | # Build directory 19 | dist 20 | 21 | # Hardhat configuration file 22 | vars.json 23 | 24 | # dotenv environment variable files 25 | .env 26 | .env.development.local 27 | .env.test.local 28 | .env.production.local 29 | .env.local 30 | 31 | # Log files 32 | logs 33 | *.log 34 | npm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | .pnpm-debug.log* 38 | 39 | # Yarn integrity file 40 | .yarn-integrity 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Modern Yarn files 46 | .pnp.* 47 | .yarn/* 48 | !.yarn/cache 49 | !.yarn/patches 50 | !.yarn/plugins 51 | !.yarn/releases 52 | !.yarn/sdks 53 | !.yarn/versions 54 | 55 | # Hardhat files 56 | cache 57 | artifacts 58 | .deps 59 | 60 | # Miscellaneous files 61 | bin 62 | out 63 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register/files", 3 | "ignore": ["test/fixture-projects/**/*"], 4 | "timeout": 150000 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | pnpm-lock.yaml 3 | deployments 4 | artifacts 5 | cache 6 | dist 7 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - "prettier-plugin-solidity" 3 | -------------------------------------------------------------------------------- /FUNDING.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0x537097f27d55cb52d39b95c3ed65076349ab68aac48051d742d715456e0884d8" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 Pascal Marco Caversaccio 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xdeployer 💥 2 | 3 | [![Test xdeploy](https://github.com/pcaversaccio/xdeployer/actions/workflows/test.yml/badge.svg)](https://github.com/pcaversaccio/xdeployer/actions/workflows/test.yml) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/license/mit) 5 | [![npm package](https://img.shields.io/npm/v/xdeployer.svg)](https://www.npmjs.com/package/xdeployer) 6 | 7 | [Hardhat](https://hardhat.org) plugin to deploy your smart contracts across multiple Ethereum Virtual Machine (EVM) chains with the same deterministic address. 8 | 9 | > [!TIP] 10 | > It is pronounced _cross_-deployer. 11 | 12 | ## What 13 | 14 | This plugin will help you make easier and safer usage of the [`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014) EVM opcode. [`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014) can be used to compute in advance the address where a smart contract will be deployed, which allows for interesting new mechanisms known as _counterfactual interactions_. 15 | 16 | ## Installation 17 | 18 | With `npm` versions `>=7`: 19 | 20 | ```console 21 | # based on ethers v6 22 | npm install --save-dev xdeployer 23 | ``` 24 | 25 | With `npm` version `6`: 26 | 27 | ```console 28 | # based on ethers v6 29 | npm install --save-dev xdeployer @nomicfoundation/hardhat-ethers ethers 30 | ``` 31 | 32 |
33 | Using ethers version 5 34 | 35 | With `npm` versions `>=7`: 36 | 37 | ```console 38 | # based on ethers v5 39 | npm install --save-dev 'xdeployer@^1.2.7' 40 | ``` 41 | 42 | With `npm` version `6`: 43 | 44 | ```console 45 | # based on ethers v5 46 | npm install --save-dev 'xdeployer@^1.2.7' @nomiclabs/hardhat-ethers 'ethers@^5.7.2' '@openzeppelin/contracts@^4.9.0' 47 | ``` 48 | 49 |
50 | 51 | Or if you are using [Yarn](https://classic.yarnpkg.com): 52 | 53 | ```console 54 | # based on ethers v6 55 | yarn add --dev xdeployer @nomicfoundation/hardhat-ethers ethers 56 | ``` 57 | 58 |
59 | Using ethers version 5 60 | 61 | ```console 62 | # based on ethers v5 63 | yarn add --dev 'xdeployer@^1.2.7' @nomiclabs/hardhat-ethers 'ethers@^5.7.2' '@openzeppelin/contracts@^4.9.0' 64 | ``` 65 | 66 |
67 | 68 | In case you are using [pnpm](https://pnpm.io), invoke: 69 | 70 | ```console 71 | # based on ethers v6 72 | pnpm add --save-dev xdeployer 73 | ``` 74 | 75 |
76 | Using ethers version 5 77 | 78 | ```console 79 | # based on ethers v5 80 | pnpm add --save-dev 'xdeployer@^1.2.7' 81 | ``` 82 | 83 |
84 | 85 | > [!NOTE] 86 | > This plugin uses the optional chaining operator (`?.`). Optional chaining is _not_ supported in [Node.js](https://nodejs.org/en) `v13` and below. 87 | 88 | Import the plugin in your `hardhat.config.js`: 89 | 90 | ```js 91 | require("xdeployer"); 92 | ``` 93 | 94 | Or if you are using TypeScript, in your `hardhat.config.ts`: 95 | 96 | ```ts 97 | import "xdeployer"; 98 | ``` 99 | 100 | ## Required Plugins 101 | 102 | - [`@nomicfoundation/hardhat-ethers`](https://www.npmjs.com/package/@nomicfoundation/hardhat-ethers) 103 | - [`ethers`](https://www.npmjs.com/package/ethers) 104 | 105 | ## Tasks 106 | 107 | This plugin provides the `xdeploy` task, which allows you to deploy your smart contracts across multiple EVM chains with the same deterministic address: 108 | 109 | ```console 110 | npx hardhat xdeploy 111 | ``` 112 | 113 | ## Environment Extensions 114 | 115 | This plugin does not extend the environment. 116 | 117 | ## Configuration 118 | 119 | You need to add the following configurations to your `hardhat.config.js` file: 120 | 121 | ```js 122 | module.exports = { 123 | networks: { 124 | mainnet: { ... } 125 | }, 126 | xdeploy: { 127 | contract: "YOUR_CONTRACT_NAME_TO_BE_DEPLOYED", 128 | constructorArgsPath: "PATH_TO_CONSTRUCTOR_ARGS", // optional; default value is `undefined` 129 | salt: "YOUR_SALT_MESSAGE", 130 | signer: "SIGNER_PRIVATE_KEY", 131 | networks: ["LIST_OF_NETWORKS"], 132 | rpcUrls: ["LIST_OF_RPCURLS"], 133 | gasLimit: 1_500_000, // optional; default value is `1.5e6` 134 | }, 135 | }; 136 | ``` 137 | 138 | Or if you are using TypeScript, in your `hardhat.config.ts`: 139 | 140 | ```ts 141 | const config: HardhatUserConfig = { 142 | networks: { 143 | mainnet: { ... } 144 | }, 145 | xdeploy: { 146 | contract: "YOUR_CONTRACT_NAME_TO_BE_DEPLOYED", 147 | constructorArgsPath: "PATH_TO_CONSTRUCTOR_ARGS", // optional; default value is `undefined` 148 | salt: "YOUR_SALT_MESSAGE", 149 | signer: "SIGNER_PRIVATE_KEY", 150 | networks: ["LIST_OF_NETWORKS"], 151 | rpcUrls: ["LIST_OF_RPCURLS"], 152 | gasLimit: 1_500_000, // optional; default value is `1.5e6` 153 | }, 154 | }; 155 | ``` 156 | 157 | The parameters `constructorArgsPath` and `gasLimit` are _optional_. The `salt` parameter is a random string value used to create the contract address. If you have previously deployed the same contract with the identical `salt`, the contract creation transaction will fail due to [EIP-684](https://eips.ethereum.org/EIPS/eip-684). For more details, see also [here](#a-note-on-selfdestruct). 158 | 159 | > [!IMPORTANT] 160 | > Please note that `xdeployer` computes the UTF-8 byte representation of the specified `salt` and calculates the `keccak256` hash, which represents the 32-byte `salt` value that is passed to [`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014). 161 | 162 | _Example:_ 163 | 164 | ```ts 165 | xdeploy: { 166 | contract: "ERC20Mock", 167 | constructorArgsPath: "./deploy-args.ts", 168 | salt: "WAGMI", 169 | signer: vars.get("PRIVATE_KEY", ""), 170 | networks: ["hardhat", "sepolia", "holesky"], 171 | rpcUrls: [ 172 | "hardhat", 173 | vars.get("ETH_SEPOLIA_TESTNET_URL", "https://rpc.sepolia.org"), 174 | vars.get("ETH_HOLESKY_TESTNET_URL", "https://holesky.rpc.thirdweb.com"), 175 | ], 176 | gasLimit: 1.2 * 10 ** 6, 177 | }, 178 | ``` 179 | 180 | > [!NOTE] 181 | > We recommend using [Hardhat configuration variables](https://hardhat.org/hardhat-runner/docs/guides/configuration-variables) introduced in Hardhat version [`2.19.0`](https://github.com/NomicFoundation/hardhat/releases/tag/hardhat%402.19.0) to set the private key of your signer. 182 | 183 | The current available networks are: 184 | 185 | > [!TIP] 186 | > To display the complete list of supported networks with the corresponding block explorer links and chain IDs, run `npx hardhat xdeploy --list-networks`. 187 | 188 | - **Local:** 189 | - `localhost` 190 | - `hardhat` 191 | - **EVM-Based Test Networks:** 192 | - `sepolia` 193 | - `holesky` 194 | - `hoodi` 195 | - `bscTestnet` 196 | - `optimismSepolia` 197 | - `arbitrumSepolia` 198 | - `amoy` 199 | - `polygonZkEVMTestnet` 200 | - `fantomTestnet` 201 | - `fuji` 202 | - `chiado` 203 | - `moonbaseAlpha` 204 | - `alfajores` 205 | - `auroraTestnet` 206 | - `harmonyTestnet` 207 | - `spark` 208 | - `cronosTestnet` 209 | - `evmosTestnet` 210 | - `bobaTestnet` 211 | - `cantoTestnet` 212 | - `baseSepolia` 213 | - `mantleTestnet` 214 | - `filecoinTestnet` 215 | - `scrollSepolia` 216 | - `lineaTestnet` 217 | - `zoraSepolia` 218 | - `luksoTestnet` 219 | - `mantaTestnet` 220 | - `blastTestnet` 221 | - `dosTestnet` 222 | - `fraxtalTestnet` 223 | - `metisTestnet` 224 | - `modeTestnet` 225 | - `seiArcticDevnet` 226 | - `seiAtlanticTestnet` 227 | - `xlayerTestnet` 228 | - `bobTestnet` 229 | - `coreTestnet` 230 | - `telosTestnet` 231 | - `rootstockTestnet` 232 | - `chilizTestnet` 233 | - `taraxaTestnet` 234 | - `taikoTestnet` 235 | - `zetaChainTestnet` 236 | - `5ireChainTestnet` 237 | - `sapphireTestnet` 238 | - `worldChainTestnet` 239 | - `plumeTestnet` 240 | - `unichainTestnet` 241 | - `xdcTestnet` 242 | - `sxTestnet` 243 | - `liskTestnet` 244 | - `metalL2Testnet` 245 | - `superseedTestnet` 246 | - `storyTestnet` 247 | - `sonicTestnet` 248 | - `flowTestnet` 249 | - `inkTestnet` 250 | - `morphTestnet` 251 | - `shapeTestnet` 252 | - `etherlinkTestnet` 253 | - `soneiumTestnet` 254 | - `swellTestnet` 255 | - `hemiTestnet` 256 | - `berachainTestnet` 257 | - `monadTestnet` 258 | - `cornTestnet` 259 | - `arenazTestnet` 260 | - `iotexTestnet` 261 | - `hychainTestnet` 262 | - `zircuitTestnet` 263 | - `megaETHTestnet` 264 | - `bitlayerTestnet` 265 | - `roninTestnet` 266 | - `zkSyncTestnet` 267 | - `immutableZkEVMTestnet` 268 | - `abstractTestnet` 269 | - `hyperevmTestnet` 270 | - `apeChainTestnet` 271 | - **EVM-Based Production Networks:** 272 | - `ethMain` 273 | - `bscMain` 274 | - `optimismMain` 275 | - `arbitrumOne` 276 | - `arbitrumNova` 277 | - `polygon` 278 | - `polygonZkEVMMain` 279 | - `fantomMain` 280 | - `avalanche` 281 | - `gnosis` 282 | - `moonriver` 283 | - `moonbeam` 284 | - `celo` 285 | - `auroraMain` 286 | - `harmonyMain` 287 | - `fuse` 288 | - `cronosMain` 289 | - `evmosMain` 290 | - `bobaMain` 291 | - `cantoMain` 292 | - `baseMain` 293 | - `mantleMain` 294 | - `filecoinMain` 295 | - `scrollMain` 296 | - `lineaMain` 297 | - `zoraMain` 298 | - `luksoMain` 299 | - `mantaMain` 300 | - `blastMain` 301 | - `dosMain` 302 | - `fraxtalMain` 303 | - `enduranceMain` 304 | - `kavaMain` 305 | - `metisMain` 306 | - `modeMain` 307 | - `seiMain` 308 | - `xlayerMain` 309 | - `bobMain` 310 | - `coreMain` 311 | - `telosMain` 312 | - `rootstockMain` 313 | - `chilizMain` 314 | - `taraxaMain` 315 | - `gravityAlphaMain` 316 | - `taikoMain` 317 | - `zetaChainMain` 318 | - `5ireChainMain` 319 | - `sapphireMain` 320 | - `worldChainMain` 321 | - `plumeMain` 322 | - `unichainMain` 323 | - `xdcMain` 324 | - `sxMain` 325 | - `liskMain` 326 | - `metalL2Main` 327 | - `superseedMain` 328 | - `sonicMain` 329 | - `flowMain` 330 | - `inkMain` 331 | - `morphMain` 332 | - `shapeMain` 333 | - `etherlinkMain` 334 | - `soneiumMain` 335 | - `swellMain` 336 | - `hemiMain` 337 | - `berachainMain` 338 | - `cornMain` 339 | - `arenazMain` 340 | - `iotexMain` 341 | - `hychainMain` 342 | - `zircuitMain` 343 | - `bitlayerMain` 344 | - `roninMain` 345 | - `zkSyncMain` 346 | - `immutableZkEVMMain` 347 | - `abstractMain` 348 | - `hyperevmMain` 349 | - `kaiaMain` 350 | - `apeChainMain` 351 | 352 | > [!IMPORTANT] 353 | > Note that you must ensure that your deployment account has sufficient funds on **all** target networks. 354 | 355 | ### Local Deployment 356 | 357 | If you also want to test deploy your smart contracts on `"hardhat"` or `"localhost"`, you must first add the following Solidity file called `Create2DeployerLocal.sol` to your `contracts/` folder: 358 | 359 | ```solidity 360 | // SPDX-License-Identifier: MIT 361 | pragma solidity ^0.8.23; 362 | 363 | import { CreateX } from "xdeployer/src/contracts/CreateX.sol"; 364 | 365 | contract Create2DeployerLocal is CreateX {} 366 | ``` 367 | 368 | > For this kind of deployment, you must set the Solidity version in the `hardhat.config.js` or `hardhat.config.ts` file to `0.8.23` or higher. 369 | 370 | The RPC URL for `hardhat` is simply `hardhat`, while for `localhost` you must first run `npx hardhat node`, which defaults to `http://127.0.0.1:8545`. It is important to note that the local deployment does _not_ generate the same deterministic address as on all live test/production networks, since the address of the smart contract that calls the opcode [`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014) differs locally from the live test/production networks. I recommend using local deployments for general testing, for example to understand the correct `gasLimit` target size. 371 | 372 | ### Further Considerations 373 | 374 | The constructor arguments file must have an _exportable_ field called `data` in case you are using TypeScript: 375 | 376 | ```ts 377 | const data = [ 378 | "arg1", 379 | "arg2", 380 | ... 381 | ]; 382 | export { data }; 383 | ``` 384 | 385 | > BigInt literals (e.g. `100_000_000_000_000_000_000n`) can be used for the constructor arguments if you set `target: ES2020` or higher in your `tsconfig.json` file. See also [here](./tsconfig.json) for an example. 386 | 387 | If you are using common JavaScript: 388 | 389 | ```js 390 | module.exports = [ 391 | "arg1", 392 | "arg2", 393 | ... 394 | ]; 395 | ``` 396 | 397 | The `gasLimit` field is set to **1'500'000** by default because the [`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014) operations are a complex sequence of opcode executions. Usually the providers do not manage to estimate the `gasLimit` for these calls, so a predefined value is set. 398 | 399 | The contract creation transaction is displayed on Etherscan (or any other block explorer) as a so-called _internal transaction_. An internal transaction is an action that is occurring within, or between, one or multiple smart contracts. In other words, it is initiated inside the code itself, rather than externally, from a wallet address controlled by a human. For more details on why it works this way, see [here](#how-it-works). 400 | 401 | > [!WARNING] 402 | > Solidity version [`0.8.20`](https://github.com/ethereum/solidity/releases/tag/v0.8.20) introduced support for the new opcode [`PUSH0`](https://eips.ethereum.org/EIPS/eip-3855), which was added as part of the [Shanghai hard fork](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md). Prior to running a deployment with a `>=0.8.20`-compiled bytecode (using the EVM version `shanghai`), please verify that _all_ targeted EVM networks support the `PUSH0` opcode. Otherwise, a deployment attempt on an EVM chain without `PUSH0` support may result in deployment or runtime failure(s). 403 | 404 | > [!WARNING] 405 | > Solidity version [`0.8.25`](https://github.com/ethereum/solidity/releases/tag/v0.8.25) defaults to EVM version [`cancun`](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md), which features a number of new opcodes. Prior to running a deployment with a `>=0.8.25`-compiled bytecode (using the EVM version `cancun`), please verify that _all_ targeted EVM networks support the new `cancun` opcodes. Otherwise, a deployment attempt on an EVM chain without `cancun` support may result in deployment or runtime failure(s). 406 | 407 | > [!WARNING] 408 | > Solidity version [`0.8.30`](https://github.com/ethereum/solidity/releases/tag/v0.8.30) defaults to EVM version [`prague`](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/prague.md), which introduces _no new opcodes_ but includes a few new precompiled contracts (see [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537)). If your contract(s) make use of these new precompiled contracts, ensure that _all_ targeted EVM networks support the `prague` precompiled contracts. Otherwise, deployed contracts may encounter runtime failure(s). 409 | 410 | ## Usage 411 | 412 | ```console 413 | npx hardhat xdeploy 414 | ``` 415 | 416 | ### Usage With Truffle 417 | 418 | [Truffle](https://archive.trufflesuite.com) suite users can leverage the Hardhat plugin [`hardhat-truffle5`](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-truffle5) (or if you use Truffle `v4` [`hardhat-truffle4`](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-truffle4)) to integrate with `TruffleContract` from Truffle `v5`. This plugin allows tests and scripts written for Truffle to work with Hardhat. 419 | 420 | ## How It Works 421 | 422 | EVM opcodes can only be called via a smart contract. I have deployed a helper smart contract [`CreateX`](https://github.com/pcaversaccio/createx) with the same address across all the available networks to make easier and safer usage of the [`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014) EVM opcode. During your deployment, the plugin will call this contract. 423 | 424 | ### A Note on [`SELFDESTRUCT`](https://www.evm.codes/?fork=cancun#ff) 425 | 426 | Using the [`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014) EVM opcode always allows to redeploy a new smart contract to a previously selfdestructed contract address.[^1] However, if a contract creation is attempted, due to either a creation transaction or the [`CREATE`](https://www.evm.codes/#f0?fork=cancun)/[`CREATE2`](https://eips.ethereum.org/EIPS/eip-1014) EVM opcode, and the destination address already has either nonzero nonce, or non-empty code, then the creation throws immediately, with exactly the same behavior as would arise if the first byte in the init code were an invalid opcode. This applies retroactively starting from genesis. 427 | 428 | ### A Note on the Contract Creation Transaction 429 | 430 | It is important to note that the `msg.sender` of the contract creation transaction is the helper smart contract [`CreateX`](https://github.com/pcaversaccio/createx) with address `0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed`. If you are relying on common smart contract libraries such as [OpenZeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts)[^2] for your smart contract, which set certain constructor arguments to `msg.sender` (e.g. `owner`), you will need to change these arguments to `tx.origin` so that they are set to your deployer's EOA address. For another workaround, see [here](https://github.com/pcaversaccio/xdeployer/discussions/18). 431 | 432 | > [!CAUTION] 433 | > Please familiarise yourself with the security considerations concerning `tx.origin`. You can find more information about it, e.g. [here](https://docs.soliditylang.org/en/latest/security-considerations.html#tx-origin). 434 | 435 | ## Donation 436 | 437 | I am a strong advocate of the open-source and free software paradigm. However, if you feel my work deserves a donation, you can send it to this address: [`0x07bF3CDA34aA78d92949bbDce31520714AB5b228`](https://etherscan.io/address/0x07bF3CDA34aA78d92949bbDce31520714AB5b228). I can pledge that I will use this money to help fix more existing challenges in the Ethereum ecosystem 🤝. 438 | 439 | [^1]: The `cancun` hard fork introduced [EIP-6780](https://eips.ethereum.org/EIPS/eip-6780), which redefines the behaviour of [`SELFDESTRUCT`](https://www.evm.codes/?fork=cancun#ff). It now only transfers the contract's balance to the target address without deleting the contract's state or code. The contract is only removed if [`SELFDESTRUCT`](https://www.evm.codes/?fork=cancun#ff) is called in the same transaction in which it was created. 440 | 441 | [^2]: Please note that [OpenZeppelin Contracts version `5.0.0`](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.0) has made the initial `owner` explicit (see PR [#4267](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4267)). 442 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | const eslint = require("@eslint/js"); 3 | const tseslint = require("typescript-eslint"); 4 | const eslintConfigPrettier = require("eslint-config-prettier"); 5 | /* eslint-enable @typescript-eslint/no-require-imports */ 6 | 7 | /** @type {import("eslint").Linter.Config} */ 8 | module.exports = tseslint.config( 9 | { 10 | files: ["**/*.{js,ts}"], 11 | extends: [ 12 | eslint.configs.recommended, 13 | ...tseslint.configs.recommended, 14 | ...tseslint.configs.stylistic, 15 | eslintConfigPrettier, 16 | ], 17 | plugins: { 18 | "@typescript-eslint": tseslint.plugin, 19 | }, 20 | languageOptions: { 21 | ecmaVersion: "latest", 22 | parser: tseslint.parser, 23 | parserOptions: { 24 | project: true, 25 | }, 26 | }, 27 | }, 28 | { 29 | ignores: [ 30 | "node_modules/**", 31 | "pnpm-lock.yaml", 32 | "deployments/**", 33 | "artifacts/**", 34 | "cache/**", 35 | "dist/**", 36 | ], 37 | }, 38 | ); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xdeployer", 3 | "version": "3.1.15", 4 | "description": "Hardhat plugin to deploy your smart contracts across multiple EVM chains with the same deterministic address.", 5 | "author": "Pascal Marco Caversaccio ", 6 | "license": "MIT", 7 | "funding": "https://github.com/pcaversaccio/xdeployer/blob/main/.github/FUNDING.yml", 8 | "main": "dist/src/index.js", 9 | "types": "dist/src/index.d.ts", 10 | "keywords": [ 11 | "deployment", 12 | "ethereum", 13 | "create2", 14 | "hardhat", 15 | "hardhat-plugin" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/pcaversaccio/xdeployer.git" 20 | }, 21 | "homepage": "https://github.com/pcaversaccio/xdeployer#readme", 22 | "bugs": { 23 | "url": "https://github.com/pcaversaccio/xdeployer/issues" 24 | }, 25 | "engines": { 26 | "node": ">=14.0.0" 27 | }, 28 | "packageManager": "pnpm@10.11.0", 29 | "scripts": { 30 | "prettier:check": "npx prettier -c \"**/*.{js,ts,md,sol,json,yml,yaml}\"", 31 | "prettier:fix": "npx prettier -w \"**/*.{js,ts,md,sol,json,yml,yaml}\"", 32 | "lint:check": "pnpm prettier:check && npx eslint .", 33 | "lint:fix": "pnpm prettier:fix && npx eslint . --fix", 34 | "test": "mocha --exit --recursive \"test/**/*.test.ts\"", 35 | "build": "tsc", 36 | "watch": "tsc -w", 37 | "prepublishOnly": "pnpm build" 38 | }, 39 | "files": [ 40 | "dist/src/", 41 | "src/", 42 | "LICENSE", 43 | "README.md" 44 | ], 45 | "devDependencies": { 46 | "@eslint/js": "^9.28.0", 47 | "@nomicfoundation/hardhat-ethers": "^3.0.8", 48 | "@types/chai": "^4.3.20", 49 | "@types/fs-extra": "^11.0.4", 50 | "@types/mocha": "^10.0.10", 51 | "@types/node": "^22.15.29", 52 | "chai": "^4.5.0", 53 | "eslint": "^9.28.0", 54 | "eslint-config-prettier": "^10.1.5", 55 | "ethers": "^6.14.3", 56 | "hardhat": "^2.24.1", 57 | "mocha": "^11.5.0", 58 | "prettier": "^3.5.3", 59 | "prettier-plugin-solidity": "^2.0.0", 60 | "ts-node": "^10.9.2", 61 | "typescript": "^5.8.3", 62 | "typescript-eslint": "^8.33.0" 63 | }, 64 | "peerDependencies": { 65 | "@nomicfoundation/hardhat-ethers": "^3.0.8", 66 | "ethers": "^6.14.3", 67 | "hardhat": "^2.24.1" 68 | }, 69 | "pnpm": { 70 | "onlyBuiltDependencies": [ 71 | "keccak", 72 | "secp256k1" 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseBranches": ["main"], 3 | "labels": ["dependencies 🔁"], 4 | "assignees": ["pcaversaccio"], 5 | "separateMajorMinor": false, 6 | "extends": [ 7 | ":preserveSemverRanges", 8 | "group:all", 9 | "schedule:monthly", 10 | ":maintainLockFilesMonthly" 11 | ], 12 | "lockFileMaintenance": { 13 | "extends": ["group:all"], 14 | "commitMessageAction": "Update" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/abi/CreateX.json: -------------------------------------------------------------------------------- 1 | [ 2 | "error FailedContractCreation(address)", 3 | "error FailedContractInitialisation(address,bytes)", 4 | "error FailedEtherTransfer(address,bytes)", 5 | "error InvalidNonceValue(address)", 6 | "error InvalidSalt(address)", 7 | "event ContractCreation(address indexed,bytes32 indexed)", 8 | "event ContractCreation(address indexed)", 9 | "event Create3ProxyContractCreation(address indexed,bytes32 indexed)", 10 | "function computeCreate2Address(bytes32,bytes32) view returns (address)", 11 | "function computeCreate2Address(bytes32,bytes32,address) pure returns (address)", 12 | "function computeCreate3Address(bytes32,address) pure returns (address)", 13 | "function computeCreate3Address(bytes32) view returns (address)", 14 | "function computeCreateAddress(uint256) view returns (address)", 15 | "function computeCreateAddress(address,uint256) view returns (address)", 16 | "function deployCreate(bytes) payable returns (address)", 17 | "function deployCreate2(bytes32,bytes) payable returns (address)", 18 | "function deployCreate2(bytes) payable returns (address)", 19 | "function deployCreate2AndInit(bytes32,bytes,bytes,tuple(uint256,uint256),address) payable returns (address)", 20 | "function deployCreate2AndInit(bytes,bytes,tuple(uint256,uint256)) payable returns (address)", 21 | "function deployCreate2AndInit(bytes,bytes,tuple(uint256,uint256),address) payable returns (address)", 22 | "function deployCreate2AndInit(bytes32,bytes,bytes,tuple(uint256,uint256)) payable returns (address)", 23 | "function deployCreate2Clone(bytes32,address,bytes) payable returns (address)", 24 | "function deployCreate2Clone(address,bytes) payable returns (address)", 25 | "function deployCreate3(bytes) payable returns (address)", 26 | "function deployCreate3(bytes32,bytes) payable returns (address)", 27 | "function deployCreate3AndInit(bytes32,bytes,bytes,tuple(uint256,uint256)) payable returns (address)", 28 | "function deployCreate3AndInit(bytes,bytes,tuple(uint256,uint256)) payable returns (address)", 29 | "function deployCreate3AndInit(bytes32,bytes,bytes,tuple(uint256,uint256),address) payable returns (address)", 30 | "function deployCreate3AndInit(bytes,bytes,tuple(uint256,uint256),address) payable returns (address)", 31 | "function deployCreateAndInit(bytes,bytes,tuple(uint256,uint256)) payable returns (address)", 32 | "function deployCreateAndInit(bytes,bytes,tuple(uint256,uint256),address) payable returns (address)", 33 | "function deployCreateClone(address,bytes) payable returns (address)" 34 | ] 35 | -------------------------------------------------------------------------------- /src/contracts/CreateX.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity ^0.8.23; 3 | 4 | /** 5 | * @title CreateX Factory Smart Contract 6 | * @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/) 7 | * @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/) 8 | * @notice Factory smart contract to make easier and safer usage of the 9 | * `CREATE` (https://web.archive.org/web/20230921103540/https://www.evm.codes/#f0?fork=shanghai) and `CREATE2` 10 | * (https://web.archive.org/web/20230921103540/https://www.evm.codes/#f5?fork=shanghai) EVM opcodes as well as of 11 | * `CREATE3`-based (https://web.archive.org/web/20230921103920/https://github.com/ethereum/EIPs/pull/3171) contract creations. 12 | * @dev To simplify testing of non-public variables and functions, we use the `internal` 13 | * function visibility specifier `internal` for all variables and functions, even though 14 | * they could technically be `private` since we do not expect anyone to inherit from 15 | * the `CreateX` contract. 16 | * @custom:security-contact See https://web.archive.org/web/20230921105029/https://raw.githubusercontent.com/pcaversaccio/createx/main/SECURITY.md. 17 | */ 18 | contract CreateX { 19 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 20 | /* IMMUTABLES */ 21 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 22 | 23 | /** 24 | * @dev Caches the contract address at construction, to be used for the custom errors. 25 | */ 26 | address internal immutable _SELF = address(this); 27 | 28 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 29 | /* TYPES */ 30 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 31 | 32 | /** 33 | * @dev Struct for the `payable` amounts in a deploy-and-initialise call. 34 | */ 35 | struct Values { 36 | uint256 constructorAmount; 37 | uint256 initCallAmount; 38 | } 39 | 40 | /** 41 | * @dev Enum for the selection of a permissioned deploy protection. 42 | */ 43 | enum SenderBytes { 44 | MsgSender, 45 | ZeroAddress, 46 | Random 47 | } 48 | 49 | /** 50 | * @dev Enum for the selection of a cross-chain redeploy protection. 51 | */ 52 | enum RedeployProtectionFlag { 53 | True, 54 | False, 55 | Unspecified 56 | } 57 | 58 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 59 | /* EVENTS */ 60 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 61 | 62 | /** 63 | * @dev Event that is emitted when a contract is successfully created. 64 | * @param newContract The address of the new contract. 65 | * @param salt The 32-byte random value used to create the contract address. 66 | */ 67 | event ContractCreation(address indexed newContract, bytes32 indexed salt); 68 | 69 | /** 70 | * @dev Event that is emitted when a contract is successfully created. 71 | * @param newContract The address of the new contract. 72 | */ 73 | event ContractCreation(address indexed newContract); 74 | 75 | /** 76 | * @dev Event that is emitted when a `CREATE3` proxy contract is successfully created. 77 | * @param newContract The address of the new proxy contract. 78 | * @param salt The 32-byte random value used to create the proxy address. 79 | */ 80 | event Create3ProxyContractCreation(address indexed newContract, bytes32 indexed salt); 81 | 82 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 83 | /* CUSTOM ERRORS */ 84 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 85 | 86 | /** 87 | * @dev Error that occurs when the contract creation failed. 88 | * @param emitter The contract that emits the error. 89 | */ 90 | error FailedContractCreation(address emitter); 91 | 92 | /** 93 | * @dev Error that occurs when the contract initialisation call failed. 94 | * @param emitter The contract that emits the error. 95 | * @param revertData The data returned by the failed initialisation call. 96 | */ 97 | error FailedContractInitialisation(address emitter, bytes revertData); 98 | 99 | /** 100 | * @dev Error that occurs when the salt value is invalid. 101 | * @param emitter The contract that emits the error. 102 | */ 103 | error InvalidSalt(address emitter); 104 | 105 | /** 106 | * @dev Error that occurs when the nonce value is invalid. 107 | * @param emitter The contract that emits the error. 108 | */ 109 | error InvalidNonceValue(address emitter); 110 | 111 | /** 112 | * @dev Error that occurs when transferring ether has failed. 113 | * @param emitter The contract that emits the error. 114 | * @param revertData The data returned by the failed ether transfer. 115 | */ 116 | error FailedEtherTransfer(address emitter, bytes revertData); 117 | 118 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 119 | /* CREATE */ 120 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 121 | 122 | /** 123 | * @dev Deploys a new contract via calling the `CREATE` opcode and using the creation 124 | * bytecode `initCode` and `msg.value` as inputs. In order to save deployment costs, 125 | * we do not sanity check the `initCode` length. Note that if `msg.value` is non-zero, 126 | * `initCode` must have a `payable` constructor. 127 | * @param initCode The creation bytecode. 128 | * @return newContract The 20-byte address where the contract was deployed. 129 | */ 130 | function deployCreate(bytes memory initCode) public payable returns (address newContract) { 131 | assembly ("memory-safe") { 132 | newContract := create(callvalue(), add(initCode, 0x20), mload(initCode)) 133 | } 134 | _requireSuccessfulContractCreation({newContract: newContract}); 135 | emit ContractCreation({newContract: newContract}); 136 | } 137 | 138 | /** 139 | * @dev Deploys and initialises a new contract via calling the `CREATE` opcode and using the 140 | * creation bytecode `initCode`, the initialisation code `data`, the struct for the `payable` 141 | * amounts `values`, the refund address `refundAddress`, and `msg.value` as inputs. In order to 142 | * save deployment costs, we do not sanity check the `initCode` length. Note that if `values.constructorAmount` 143 | * is non-zero, `initCode` must have a `payable` constructor. 144 | * @param initCode The creation bytecode. 145 | * @param data The initialisation code that is passed to the deployed contract. 146 | * @param values The specific `payable` amounts for the deployment and initialisation call. 147 | * @param refundAddress The 20-byte address where any excess ether is returned to. 148 | * @return newContract The 20-byte address where the contract was deployed. 149 | * @custom:security This function allows for reentrancy, however we refrain from adding 150 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 151 | * level that potentially malicious reentrant calls do not affect your smart contract system. 152 | */ 153 | function deployCreateAndInit( 154 | bytes memory initCode, 155 | bytes memory data, 156 | Values memory values, 157 | address refundAddress 158 | ) public payable returns (address newContract) { 159 | assembly ("memory-safe") { 160 | newContract := create(mload(values), add(initCode, 0x20), mload(initCode)) 161 | } 162 | _requireSuccessfulContractCreation({newContract: newContract}); 163 | emit ContractCreation({newContract: newContract}); 164 | 165 | (bool success, bytes memory returnData) = newContract.call{value: values.initCallAmount}(data); 166 | if (!success) { 167 | revert FailedContractInitialisation({emitter: _SELF, revertData: returnData}); 168 | } 169 | 170 | if (_SELF.balance != 0) { 171 | // Any wei amount previously forced into this contract (e.g. by using the `SELFDESTRUCT` 172 | // opcode) will be part of the refund transaction. 173 | (success, returnData) = refundAddress.call{value: _SELF.balance}(""); 174 | if (!success) { 175 | revert FailedEtherTransfer({emitter: _SELF, revertData: returnData}); 176 | } 177 | } 178 | } 179 | 180 | /** 181 | * @dev Deploys and initialises a new contract via calling the `CREATE` opcode and using the 182 | * creation bytecode `initCode`, the initialisation code `data`, the struct for the `payable` 183 | * amounts `values`, and `msg.value` as inputs. In order to save deployment costs, we do not 184 | * sanity check the `initCode` length. Note that if `values.constructorAmount` is non-zero, 185 | * `initCode` must have a `payable` constructor, and any excess ether is returned to `msg.sender`. 186 | * @param initCode The creation bytecode. 187 | * @param data The initialisation code that is passed to the deployed contract. 188 | * @param values The specific `payable` amounts for the deployment and initialisation call. 189 | * @return newContract The 20-byte address where the contract was deployed. 190 | * @custom:security This function allows for reentrancy, however we refrain from adding 191 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 192 | * level that potentially malicious reentrant calls do not affect your smart contract system. 193 | */ 194 | function deployCreateAndInit( 195 | bytes memory initCode, 196 | bytes memory data, 197 | Values memory values 198 | ) public payable returns (address newContract) { 199 | newContract = deployCreateAndInit({initCode: initCode, data: data, values: values, refundAddress: msg.sender}); 200 | } 201 | 202 | /** 203 | * @dev Deploys a new EIP-1167 minimal proxy contract using the `CREATE` opcode, and initialises 204 | * the implementation contract using the implementation address `implementation`, the initialisation 205 | * code `data`, and `msg.value` as inputs. Note that if `msg.value` is non-zero, the initialiser 206 | * function called via `data` must be `payable`. 207 | * @param implementation The 20-byte implementation contract address. 208 | * @param data The initialisation code that is passed to the deployed proxy contract. 209 | * @return proxy The 20-byte address where the clone was deployed. 210 | * @custom:security This function allows for reentrancy, however we refrain from adding 211 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 212 | * level that potentially malicious reentrant calls do not affect your smart contract system. 213 | */ 214 | function deployCreateClone(address implementation, bytes memory data) public payable returns (address proxy) { 215 | bytes20 implementationInBytes = bytes20(implementation); 216 | assembly ("memory-safe") { 217 | let clone := mload(0x40) 218 | mstore( 219 | clone, 220 | hex"3d_60_2d_80_60_0a_3d_39_81_f3_36_3d_3d_37_3d_3d_3d_36_3d_73_00_00_00_00_00_00_00_00_00_00_00_00" 221 | ) 222 | mstore(add(clone, 0x14), implementationInBytes) 223 | mstore( 224 | add(clone, 0x28), 225 | hex"5a_f4_3d_82_80_3e_90_3d_91_60_2b_57_fd_5b_f3_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00" 226 | ) 227 | proxy := create(0, clone, 0x37) 228 | } 229 | if (proxy == address(0)) { 230 | revert FailedContractCreation({emitter: _SELF}); 231 | } 232 | emit ContractCreation({newContract: proxy}); 233 | 234 | (bool success, bytes memory returnData) = proxy.call{value: msg.value}(data); 235 | _requireSuccessfulContractInitialisation({ 236 | success: success, 237 | returnData: returnData, 238 | implementation: implementation 239 | }); 240 | } 241 | 242 | /** 243 | * @dev Returns the address where a contract will be stored if deployed via `deployer` using 244 | * the `CREATE` opcode. For the specification of the Recursive Length Prefix (RLP) encoding 245 | * scheme, please refer to p. 19 of the Ethereum Yellow Paper (https://web.archive.org/web/20230921110603/https://ethereum.github.io/yellowpaper/paper.pdf) 246 | * and the Ethereum Wiki (https://web.archive.org/web/20230921112807/https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/). 247 | * For further insights also, see the following issue: https://web.archive.org/web/20230921112943/https://github.com/transmissions11/solmate/issues/207. 248 | * 249 | * Based on the EIP-161 (https://web.archive.org/web/20230921113207/https://raw.githubusercontent.com/ethereum/EIPs/master/EIPS/eip-161.md) specification, 250 | * all contract accounts on the Ethereum mainnet are initiated with `nonce = 1`. Thus, the 251 | * first contract address created by another contract is calculated with a non-zero nonce. 252 | * @param deployer The 20-byte deployer address. 253 | * @param nonce The next 32-byte nonce of the deployer address. 254 | * @return computedAddress The 20-byte address where a contract will be stored. 255 | */ 256 | function computeCreateAddress(address deployer, uint256 nonce) public view returns (address computedAddress) { 257 | bytes memory data; 258 | bytes1 len = bytes1(0x94); 259 | 260 | // The theoretical allowed limit, based on EIP-2681, for an account nonce is 2**64-2: 261 | // https://web.archive.org/web/20230921113252/https://eips.ethereum.org/EIPS/eip-2681. 262 | if (nonce > type(uint64).max - 1) { 263 | revert InvalidNonceValue({emitter: _SELF}); 264 | } 265 | 266 | // The integer zero is treated as an empty byte string and therefore has only one length prefix, 267 | // 0x80, which is calculated via 0x80 + 0. 268 | if (nonce == 0x00) { 269 | data = abi.encodePacked(bytes1(0xd6), len, deployer, bytes1(0x80)); 270 | } 271 | // A one-byte integer in the [0x00, 0x7f] range uses its own value as a length prefix, there is no 272 | // additional "0x80 + length" prefix that precedes it. 273 | else if (nonce <= 0x7f) { 274 | data = abi.encodePacked(bytes1(0xd6), len, deployer, uint8(nonce)); 275 | } 276 | // In the case of `nonce > 0x7f` and `nonce <= type(uint8).max`, we have the following encoding scheme 277 | // (the same calculation can be carried over for higher nonce bytes): 278 | // 0xda = 0xc0 (short RLP prefix) + 0x1a (= the bytes length of: 0x94 + address + 0x84 + nonce, in hex), 279 | // 0x94 = 0x80 + 0x14 (= the bytes length of an address, 20 bytes, in hex), 280 | // 0x84 = 0x80 + 0x04 (= the bytes length of the nonce, 4 bytes, in hex). 281 | else if (nonce <= type(uint8).max) { 282 | data = abi.encodePacked(bytes1(0xd7), len, deployer, bytes1(0x81), uint8(nonce)); 283 | } else if (nonce <= type(uint16).max) { 284 | data = abi.encodePacked(bytes1(0xd8), len, deployer, bytes1(0x82), uint16(nonce)); 285 | } else if (nonce <= type(uint24).max) { 286 | data = abi.encodePacked(bytes1(0xd9), len, deployer, bytes1(0x83), uint24(nonce)); 287 | } else if (nonce <= type(uint32).max) { 288 | data = abi.encodePacked(bytes1(0xda), len, deployer, bytes1(0x84), uint32(nonce)); 289 | } else if (nonce <= type(uint40).max) { 290 | data = abi.encodePacked(bytes1(0xdb), len, deployer, bytes1(0x85), uint40(nonce)); 291 | } else if (nonce <= type(uint48).max) { 292 | data = abi.encodePacked(bytes1(0xdc), len, deployer, bytes1(0x86), uint48(nonce)); 293 | } else if (nonce <= type(uint56).max) { 294 | data = abi.encodePacked(bytes1(0xdd), len, deployer, bytes1(0x87), uint56(nonce)); 295 | } else { 296 | data = abi.encodePacked(bytes1(0xde), len, deployer, bytes1(0x88), uint64(nonce)); 297 | } 298 | 299 | computedAddress = address(uint160(uint256(keccak256(data)))); 300 | } 301 | 302 | /** 303 | * @dev Returns the address where a contract will be stored if deployed via this contract 304 | * using the `CREATE` opcode. For the specification of the Recursive Length Prefix (RLP) 305 | * encoding scheme, please refer to p. 19 of the Ethereum Yellow Paper (https://web.archive.org/web/20230921110603/https://ethereum.github.io/yellowpaper/paper.pdf) 306 | * and the Ethereum Wiki (https://web.archive.org/web/20230921112807/https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/). 307 | * For further insights also, see the following issue: https://web.archive.org/web/20230921112943/https://github.com/transmissions11/solmate/issues/207. 308 | * 309 | * Based on the EIP-161 (https://web.archive.org/web/20230921113207/https://raw.githubusercontent.com/ethereum/EIPs/master/EIPS/eip-161.md) specification, 310 | * all contract accounts on the Ethereum mainnet are initiated with `nonce = 1`. Thus, the 311 | * first contract address created by another contract is calculated with a non-zero nonce. 312 | * @param nonce The next 32-byte nonce of this contract. 313 | * @return computedAddress The 20-byte address where a contract will be stored. 314 | */ 315 | function computeCreateAddress(uint256 nonce) public view returns (address computedAddress) { 316 | computedAddress = computeCreateAddress({deployer: _SELF, nonce: nonce}); 317 | } 318 | 319 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 320 | /* CREATE2 */ 321 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 322 | 323 | /** 324 | * @dev Deploys a new contract via calling the `CREATE2` opcode and using the salt value `salt`, 325 | * the creation bytecode `initCode`, and `msg.value` as inputs. In order to save deployment costs, 326 | * we do not sanity check the `initCode` length. Note that if `msg.value` is non-zero, `initCode` 327 | * must have a `payable` constructor. 328 | * @param salt The 32-byte random value used to create the contract address. 329 | * @param initCode The creation bytecode. 330 | * @return newContract The 20-byte address where the contract was deployed. 331 | */ 332 | function deployCreate2(bytes32 salt, bytes memory initCode) public payable returns (address newContract) { 333 | bytes32 guardedSalt = _guard({salt: salt}); 334 | assembly ("memory-safe") { 335 | newContract := create2(callvalue(), add(initCode, 0x20), mload(initCode), guardedSalt) 336 | } 337 | _requireSuccessfulContractCreation({newContract: newContract}); 338 | emit ContractCreation({newContract: newContract, salt: guardedSalt}); 339 | } 340 | 341 | /** 342 | * @dev Deploys a new contract via calling the `CREATE2` opcode and using the creation bytecode 343 | * `initCode` and `msg.value` as inputs. The salt value is calculated pseudo-randomly using a 344 | * diverse selection of block and transaction properties. This approach does not guarantee true 345 | * randomness! In order to save deployment costs, we do not sanity check the `initCode` length. 346 | * Note that if `msg.value` is non-zero, `initCode` must have a `payable` constructor. 347 | * @param initCode The creation bytecode. 348 | * @return newContract The 20-byte address where the contract was deployed. 349 | */ 350 | function deployCreate2(bytes memory initCode) public payable returns (address newContract) { 351 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 352 | // `deployCreate2`. 353 | newContract = deployCreate2({salt: _generateSalt(), initCode: initCode}); 354 | } 355 | 356 | /** 357 | * @dev Deploys and initialises a new contract via calling the `CREATE2` opcode and using the 358 | * salt value `salt`, the creation bytecode `initCode`, the initialisation code `data`, the struct 359 | * for the `payable` amounts `values`, the refund address `refundAddress`, and `msg.value` as inputs. 360 | * In order to save deployment costs, we do not sanity check the `initCode` length. Note that if 361 | * `values.constructorAmount` is non-zero, `initCode` must have a `payable` constructor. 362 | * @param salt The 32-byte random value used to create the contract address. 363 | * @param initCode The creation bytecode. 364 | * @param data The initialisation code that is passed to the deployed contract. 365 | * @param values The specific `payable` amounts for the deployment and initialisation call. 366 | * @param refundAddress The 20-byte address where any excess ether is returned to. 367 | * @return newContract The 20-byte address where the contract was deployed. 368 | * @custom:security This function allows for reentrancy, however we refrain from adding 369 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 370 | * level that potentially malicious reentrant calls do not affect your smart contract system. 371 | */ 372 | function deployCreate2AndInit( 373 | bytes32 salt, 374 | bytes memory initCode, 375 | bytes memory data, 376 | Values memory values, 377 | address refundAddress 378 | ) public payable returns (address newContract) { 379 | bytes32 guardedSalt = _guard({salt: salt}); 380 | assembly ("memory-safe") { 381 | newContract := create2(mload(values), add(initCode, 0x20), mload(initCode), guardedSalt) 382 | } 383 | _requireSuccessfulContractCreation({newContract: newContract}); 384 | emit ContractCreation({newContract: newContract, salt: guardedSalt}); 385 | 386 | (bool success, bytes memory returnData) = newContract.call{value: values.initCallAmount}(data); 387 | if (!success) { 388 | revert FailedContractInitialisation({emitter: _SELF, revertData: returnData}); 389 | } 390 | 391 | if (_SELF.balance != 0) { 392 | // Any wei amount previously forced into this contract (e.g. by using the `SELFDESTRUCT` 393 | // opcode) will be part of the refund transaction. 394 | (success, returnData) = refundAddress.call{value: _SELF.balance}(""); 395 | if (!success) { 396 | revert FailedEtherTransfer({emitter: _SELF, revertData: returnData}); 397 | } 398 | } 399 | } 400 | 401 | /** 402 | * @dev Deploys and initialises a new contract via calling the `CREATE2` opcode and using the 403 | * salt value `salt`, creation bytecode `initCode`, the initialisation code `data`, the struct for 404 | * the `payable` amounts `values`, and `msg.value` as inputs. In order to save deployment costs, 405 | * we do not sanity check the `initCode` length. Note that if `values.constructorAmount` is non-zero, 406 | * `initCode` must have a `payable` constructor, and any excess ether is returned to `msg.sender`. 407 | * @param salt The 32-byte random value used to create the contract address. 408 | * @param initCode The creation bytecode. 409 | * @param data The initialisation code that is passed to the deployed contract. 410 | * @param values The specific `payable` amounts for the deployment and initialisation call. 411 | * @return newContract The 20-byte address where the contract was deployed. 412 | * @custom:security This function allows for reentrancy, however we refrain from adding 413 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 414 | * level that potentially malicious reentrant calls do not affect your smart contract system. 415 | */ 416 | function deployCreate2AndInit( 417 | bytes32 salt, 418 | bytes memory initCode, 419 | bytes memory data, 420 | Values memory values 421 | ) public payable returns (address newContract) { 422 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 423 | // `deployCreate2AndInit`. 424 | newContract = deployCreate2AndInit({ 425 | salt: salt, 426 | initCode: initCode, 427 | data: data, 428 | values: values, 429 | refundAddress: msg.sender 430 | }); 431 | } 432 | 433 | /** 434 | * @dev Deploys and initialises a new contract via calling the `CREATE2` opcode and using the 435 | * creation bytecode `initCode`, the initialisation code `data`, the struct for the `payable` 436 | * amounts `values`, the refund address `refundAddress`, and `msg.value` as inputs. The salt value 437 | * is calculated pseudo-randomly using a diverse selection of block and transaction properties. 438 | * This approach does not guarantee true randomness! In order to save deployment costs, we do not 439 | * sanity check the `initCode` length. Note that if `values.constructorAmount` is non-zero, `initCode` 440 | * must have a `payable` constructor. 441 | * @param initCode The creation bytecode. 442 | * @param data The initialisation code that is passed to the deployed contract. 443 | * @param values The specific `payable` amounts for the deployment and initialisation call. 444 | * @param refundAddress The 20-byte address where any excess ether is returned to. 445 | * @return newContract The 20-byte address where the contract was deployed. 446 | * @custom:security This function allows for reentrancy, however we refrain from adding 447 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 448 | * level that potentially malicious reentrant calls do not affect your smart contract system. 449 | */ 450 | function deployCreate2AndInit( 451 | bytes memory initCode, 452 | bytes memory data, 453 | Values memory values, 454 | address refundAddress 455 | ) public payable returns (address newContract) { 456 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 457 | // `deployCreate2AndInit`. 458 | newContract = deployCreate2AndInit({ 459 | salt: _generateSalt(), 460 | initCode: initCode, 461 | data: data, 462 | values: values, 463 | refundAddress: refundAddress 464 | }); 465 | } 466 | 467 | /** 468 | * @dev Deploys and initialises a new contract via calling the `CREATE2` opcode and using the 469 | * creation bytecode `initCode`, the initialisation code `data`, the struct for the `payable` amounts 470 | * `values`, and `msg.value` as inputs. The salt value is calculated pseudo-randomly using a 471 | * diverse selection of block and transaction properties. This approach does not guarantee true 472 | * randomness! In order to save deployment costs, we do not sanity check the `initCode` length. 473 | * Note that if `values.constructorAmount` is non-zero, `initCode` must have a `payable` constructor, 474 | * and any excess ether is returned to `msg.sender`. 475 | * @param initCode The creation bytecode. 476 | * @param data The initialisation code that is passed to the deployed contract. 477 | * @param values The specific `payable` amounts for the deployment and initialisation call. 478 | * @return newContract The 20-byte address where the contract was deployed. 479 | * @custom:security This function allows for reentrancy, however we refrain from adding 480 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 481 | * level that potentially malicious reentrant calls do not affect your smart contract system. 482 | */ 483 | function deployCreate2AndInit( 484 | bytes memory initCode, 485 | bytes memory data, 486 | Values memory values 487 | ) public payable returns (address newContract) { 488 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 489 | // `deployCreate2AndInit`. 490 | newContract = deployCreate2AndInit({ 491 | salt: _generateSalt(), 492 | initCode: initCode, 493 | data: data, 494 | values: values, 495 | refundAddress: msg.sender 496 | }); 497 | } 498 | 499 | /** 500 | * @dev Deploys a new EIP-1167 minimal proxy contract using the `CREATE2` opcode and the salt 501 | * value `salt`, and initialises the implementation contract using the implementation address 502 | * `implementation`, the initialisation code `data`, and `msg.value` as inputs. Note that if 503 | * `msg.value` is non-zero, the initialiser function called via `data` must be `payable`. 504 | * @param salt The 32-byte random value used to create the proxy contract address. 505 | * @param implementation The 20-byte implementation contract address. 506 | * @param data The initialisation code that is passed to the deployed proxy contract. 507 | * @return proxy The 20-byte address where the clone was deployed. 508 | * @custom:security This function allows for reentrancy, however we refrain from adding 509 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 510 | * level that potentially malicious reentrant calls do not affect your smart contract system. 511 | */ 512 | function deployCreate2Clone( 513 | bytes32 salt, 514 | address implementation, 515 | bytes memory data 516 | ) public payable returns (address proxy) { 517 | bytes32 guardedSalt = _guard({salt: salt}); 518 | bytes20 implementationInBytes = bytes20(implementation); 519 | assembly ("memory-safe") { 520 | let clone := mload(0x40) 521 | mstore( 522 | clone, 523 | hex"3d_60_2d_80_60_0a_3d_39_81_f3_36_3d_3d_37_3d_3d_3d_36_3d_73_00_00_00_00_00_00_00_00_00_00_00_00" 524 | ) 525 | mstore(add(clone, 0x14), implementationInBytes) 526 | mstore( 527 | add(clone, 0x28), 528 | hex"5a_f4_3d_82_80_3e_90_3d_91_60_2b_57_fd_5b_f3_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00" 529 | ) 530 | proxy := create2(0, clone, 0x37, guardedSalt) 531 | } 532 | if (proxy == address(0)) { 533 | revert FailedContractCreation({emitter: _SELF}); 534 | } 535 | emit ContractCreation({newContract: proxy, salt: guardedSalt}); 536 | 537 | (bool success, bytes memory returnData) = proxy.call{value: msg.value}(data); 538 | _requireSuccessfulContractInitialisation({ 539 | success: success, 540 | returnData: returnData, 541 | implementation: implementation 542 | }); 543 | } 544 | 545 | /** 546 | * @dev Deploys a new EIP-1167 minimal proxy contract using the `CREATE2` opcode and the salt 547 | * value `salt`, and initialises the implementation contract using the implementation address 548 | * `implementation`, the initialisation code `data`, and `msg.value` as inputs. The salt value is 549 | * calculated pseudo-randomly using a diverse selection of block and transaction properties. This 550 | * approach does not guarantee true randomness! Note that if `msg.value` is non-zero, the initialiser 551 | * function called via `data` must be `payable`. 552 | * @param implementation The 20-byte implementation contract address. 553 | * @param data The initialisation code that is passed to the deployed proxy contract. 554 | * @return proxy The 20-byte address where the clone was deployed. 555 | * @custom:security This function allows for reentrancy, however we refrain from adding 556 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 557 | * level that potentially malicious reentrant calls do not affect your smart contract system. 558 | */ 559 | function deployCreate2Clone(address implementation, bytes memory data) public payable returns (address proxy) { 560 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 561 | // `deployCreate2Clone`. 562 | proxy = deployCreate2Clone({salt: _generateSalt(), implementation: implementation, data: data}); 563 | } 564 | 565 | /** 566 | * @dev Returns the address where a contract will be stored if deployed via `deployer` using 567 | * the `CREATE2` opcode. Any change in the `initCodeHash` or `salt` values will result in a new 568 | * destination address. This implementation is based on OpenZeppelin: 569 | * https://web.archive.org/web/20230921113703/https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/181d518609a9f006fcb97af63e6952e603cf100e/contracts/utils/Create2.sol. 570 | * @param salt The 32-byte random value used to create the contract address. 571 | * @param initCodeHash The 32-byte bytecode digest of the contract creation bytecode. 572 | * @param deployer The 20-byte deployer address. 573 | * @return computedAddress The 20-byte address where a contract will be stored. 574 | */ 575 | function computeCreate2Address( 576 | bytes32 salt, 577 | bytes32 initCodeHash, 578 | address deployer 579 | ) public pure returns (address computedAddress) { 580 | assembly ("memory-safe") { 581 | // | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... | 582 | // |----------------------|---------------------------------------------------------------------------| 583 | // | initCodeHash | CCCCCCCCCCCCC...CC | 584 | // | salt | BBBBBBBBBBBBB...BB | 585 | // | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA | 586 | // | 0xFF | FF | 587 | // |----------------------|---------------------------------------------------------------------------| 588 | // | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC | 589 | // | keccak256(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ | 590 | let ptr := mload(0x40) 591 | mstore(add(ptr, 0x40), initCodeHash) 592 | mstore(add(ptr, 0x20), salt) 593 | mstore(ptr, deployer) 594 | let start := add(ptr, 0x0b) 595 | mstore8(start, 0xff) 596 | computedAddress := keccak256(start, 85) 597 | } 598 | } 599 | 600 | /** 601 | * @dev Returns the address where a contract will be stored if deployed via this contract using 602 | * the `CREATE2` opcode. Any change in the `initCodeHash` or `salt` values will result in a new 603 | * destination address. 604 | * @param salt The 32-byte random value used to create the contract address. 605 | * @param initCodeHash The 32-byte bytecode digest of the contract creation bytecode. 606 | * @return computedAddress The 20-byte address where a contract will be stored. 607 | */ 608 | function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) public view returns (address computedAddress) { 609 | computedAddress = computeCreate2Address({salt: salt, initCodeHash: initCodeHash, deployer: _SELF}); 610 | } 611 | 612 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 613 | /* CREATE3 */ 614 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 615 | 616 | /** 617 | * @dev Deploys a new contract via employing the `CREATE3` pattern (i.e. without an initcode 618 | * factor) and using the salt value `salt`, the creation bytecode `initCode`, and `msg.value` 619 | * as inputs. In order to save deployment costs, we do not sanity check the `initCode` length. 620 | * Note that if `msg.value` is non-zero, `initCode` must have a `payable` constructor. This 621 | * implementation is based on Solmate: 622 | * https://web.archive.org/web/20230921113832/https://raw.githubusercontent.com/transmissions11/solmate/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/CREATE3.sol. 623 | * @param salt The 32-byte random value used to create the proxy contract address. 624 | * @param initCode The creation bytecode. 625 | * @return newContract The 20-byte address where the contract was deployed. 626 | * @custom:security We strongly recommend implementing a permissioned deploy protection by setting 627 | * the first 20 bytes equal to `msg.sender` in the `salt` to prevent maliciously intended frontrun 628 | * proxy deployments on other chains. 629 | */ 630 | function deployCreate3(bytes32 salt, bytes memory initCode) public payable returns (address newContract) { 631 | bytes32 guardedSalt = _guard({salt: salt}); 632 | bytes memory proxyChildBytecode = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3"; 633 | address proxy; 634 | assembly ("memory-safe") { 635 | proxy := create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), guardedSalt) 636 | } 637 | if (proxy == address(0)) { 638 | revert FailedContractCreation({emitter: _SELF}); 639 | } 640 | emit Create3ProxyContractCreation({newContract: proxy, salt: guardedSalt}); 641 | 642 | newContract = computeCreate3Address({salt: guardedSalt}); 643 | (bool success, ) = proxy.call{value: msg.value}(initCode); 644 | _requireSuccessfulContractCreation({success: success, newContract: newContract}); 645 | emit ContractCreation({newContract: newContract}); 646 | } 647 | 648 | /** 649 | * @dev Deploys a new contract via employing the `CREATE3` pattern (i.e. without an initcode 650 | * factor) and using the salt value `salt`, the creation bytecode `initCode`, and `msg.value` 651 | * as inputs. The salt value is calculated pseudo-randomly using a diverse selection of block 652 | * and transaction properties. This approach does not guarantee true randomness! In order to save 653 | * deployment costs, we do not sanity check the `initCode` length. Note that if `msg.value` is 654 | * non-zero, `initCode` must have a `payable` constructor. This implementation is based on Solmate: 655 | * https://web.archive.org/web/20230921113832/https://raw.githubusercontent.com/transmissions11/solmate/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/CREATE3.sol. 656 | * @param initCode The creation bytecode. 657 | * @return newContract The 20-byte address where the contract was deployed. 658 | */ 659 | function deployCreate3(bytes memory initCode) public payable returns (address newContract) { 660 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 661 | // `deployCreate3`. 662 | newContract = deployCreate3({salt: _generateSalt(), initCode: initCode}); 663 | } 664 | 665 | /** 666 | * @dev Deploys and initialises a new contract via employing the `CREATE3` pattern (i.e. without 667 | * an initcode factor) and using the salt value `salt`, the creation bytecode `initCode`, the 668 | * initialisation code `data`, the struct for the `payable` amounts `values`, the refund address 669 | * `refundAddress`, and `msg.value` as inputs. In order to save deployment costs, we do not sanity 670 | * check the `initCode` length. Note that if `values.constructorAmount` is non-zero, `initCode` must 671 | * have a `payable` constructor. This implementation is based on Solmate: 672 | * https://web.archive.org/web/20230921113832/https://raw.githubusercontent.com/transmissions11/solmate/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/CREATE3.sol. 673 | * @param salt The 32-byte random value used to create the proxy contract address. 674 | * @param initCode The creation bytecode. 675 | * @param data The initialisation code that is passed to the deployed contract. 676 | * @param values The specific `payable` amounts for the deployment and initialisation call. 677 | * @param refundAddress The 20-byte address where any excess ether is returned to. 678 | * @return newContract The 20-byte address where the contract was deployed. 679 | * @custom:security This function allows for reentrancy, however we refrain from adding 680 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 681 | * level that potentially malicious reentrant calls do not affect your smart contract system. 682 | * Furthermore, we strongly recommend implementing a permissioned deploy protection by setting 683 | * the first 20 bytes equal to `msg.sender` in the `salt` to prevent maliciously intended frontrun 684 | * proxy deployments on other chains. 685 | */ 686 | function deployCreate3AndInit( 687 | bytes32 salt, 688 | bytes memory initCode, 689 | bytes memory data, 690 | Values memory values, 691 | address refundAddress 692 | ) public payable returns (address newContract) { 693 | bytes32 guardedSalt = _guard({salt: salt}); 694 | bytes memory proxyChildBytecode = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3"; 695 | address proxy; 696 | assembly ("memory-safe") { 697 | proxy := create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), guardedSalt) 698 | } 699 | if (proxy == address(0)) { 700 | revert FailedContractCreation({emitter: _SELF}); 701 | } 702 | emit Create3ProxyContractCreation({newContract: proxy, salt: guardedSalt}); 703 | 704 | newContract = computeCreate3Address({salt: guardedSalt}); 705 | (bool success, ) = proxy.call{value: values.constructorAmount}(initCode); 706 | _requireSuccessfulContractCreation({success: success, newContract: newContract}); 707 | emit ContractCreation({newContract: newContract}); 708 | 709 | bytes memory returnData; 710 | (success, returnData) = newContract.call{value: values.initCallAmount}(data); 711 | if (!success) { 712 | revert FailedContractInitialisation({emitter: _SELF, revertData: returnData}); 713 | } 714 | 715 | if (_SELF.balance != 0) { 716 | // Any wei amount previously forced into this contract (e.g. by using the `SELFDESTRUCT` 717 | // opcode) will be part of the refund transaction. 718 | (success, returnData) = refundAddress.call{value: _SELF.balance}(""); 719 | if (!success) { 720 | revert FailedEtherTransfer({emitter: _SELF, revertData: returnData}); 721 | } 722 | } 723 | } 724 | 725 | /** 726 | * @dev Deploys and initialises a new contract via employing the `CREATE3` pattern (i.e. without 727 | * an initcode factor) and using the salt value `salt`, the creation bytecode `initCode`, the 728 | * initialisation code `data`, the struct for the `payable` amounts `values`, and `msg.value` as 729 | * inputs. In order to save deployment costs, we do not sanity check the `initCode` length. Note 730 | * that if `values.constructorAmount` is non-zero, `initCode` must have a `payable` constructor, 731 | * and any excess ether is returned to `msg.sender`. This implementation is based on Solmate: 732 | * https://web.archive.org/web/20230921113832/https://raw.githubusercontent.com/transmissions11/solmate/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/CREATE3.sol. 733 | * @param salt The 32-byte random value used to create the proxy contract address. 734 | * @param initCode The creation bytecode. 735 | * @param data The initialisation code that is passed to the deployed contract. 736 | * @param values The specific `payable` amounts for the deployment and initialisation call. 737 | * @return newContract The 20-byte address where the contract was deployed. 738 | * @custom:security This function allows for reentrancy, however we refrain from adding 739 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 740 | * level that potentially malicious reentrant calls do not affect your smart contract system. 741 | * Furthermore, we strongly recommend implementing a permissioned deploy protection by setting 742 | * the first 20 bytes equal to `msg.sender` in the `salt` to prevent maliciously intended frontrun 743 | * proxy deployments on other chains. 744 | */ 745 | function deployCreate3AndInit( 746 | bytes32 salt, 747 | bytes memory initCode, 748 | bytes memory data, 749 | Values memory values 750 | ) public payable returns (address newContract) { 751 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 752 | // `deployCreate3AndInit`. 753 | newContract = deployCreate3AndInit({ 754 | salt: salt, 755 | initCode: initCode, 756 | data: data, 757 | values: values, 758 | refundAddress: msg.sender 759 | }); 760 | } 761 | 762 | /** 763 | * @dev Deploys and initialises a new contract via employing the `CREATE3` pattern (i.e. without 764 | * an initcode factor) and using the creation bytecode `initCode`, the initialisation code `data`, 765 | * the struct for the `payable` amounts `values`, the refund address `refundAddress`, and `msg.value` 766 | * as inputs. The salt value is calculated pseudo-randomly using a diverse selection of block and 767 | * transaction properties. This approach does not guarantee true randomness! In order to save deployment 768 | * costs, we do not sanity check the `initCode` length. Note that if `values.constructorAmount` is non-zero, 769 | * `initCode` must have a `payable` constructor. This implementation is based on Solmate: 770 | * https://web.archive.org/web/20230921113832/https://raw.githubusercontent.com/transmissions11/solmate/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/CREATE3.sol. 771 | * @param initCode The creation bytecode. 772 | * @param data The initialisation code that is passed to the deployed contract. 773 | * @param values The specific `payable` amounts for the deployment and initialisation call. 774 | * @param refundAddress The 20-byte address where any excess ether is returned to. 775 | * @return newContract The 20-byte address where the contract was deployed. 776 | * @custom:security This function allows for reentrancy, however we refrain from adding 777 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 778 | * level that potentially malicious reentrant calls do not affect your smart contract system. 779 | */ 780 | function deployCreate3AndInit( 781 | bytes memory initCode, 782 | bytes memory data, 783 | Values memory values, 784 | address refundAddress 785 | ) public payable returns (address newContract) { 786 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 787 | // `deployCreate3AndInit`. 788 | newContract = deployCreate3AndInit({ 789 | salt: _generateSalt(), 790 | initCode: initCode, 791 | data: data, 792 | values: values, 793 | refundAddress: refundAddress 794 | }); 795 | } 796 | 797 | /** 798 | * @dev Deploys and initialises a new contract via employing the `CREATE3` pattern (i.e. without 799 | * an initcode factor) and using the creation bytecode `initCode`, the initialisation code `data`, 800 | * the struct for the `payable` amounts `values`, `msg.value` as inputs. The salt value is calculated 801 | * pseudo-randomly using a diverse selection of block and transaction properties. This approach does 802 | * not guarantee true randomness! In order to save deployment costs, we do not sanity check the `initCode` 803 | * length. Note that if `values.constructorAmount` is non-zero, `initCode` must have a `payable` constructor, 804 | * and any excess ether is returned to `msg.sender`. This implementation is based on Solmate: 805 | * https://web.archive.org/web/20230921113832/https://raw.githubusercontent.com/transmissions11/solmate/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/CREATE3.sol. 806 | * @param initCode The creation bytecode. 807 | * @param data The initialisation code that is passed to the deployed contract. 808 | * @param values The specific `payable` amounts for the deployment and initialisation call. 809 | * @return newContract The 20-byte address where the contract was deployed. 810 | * @custom:security This function allows for reentrancy, however we refrain from adding 811 | * a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol 812 | * level that potentially malicious reentrant calls do not affect your smart contract system. 813 | */ 814 | function deployCreate3AndInit( 815 | bytes memory initCode, 816 | bytes memory data, 817 | Values memory values 818 | ) public payable returns (address newContract) { 819 | // Note that the safeguarding function `_guard` is called as part of the overloaded function 820 | // `deployCreate3AndInit`. 821 | newContract = deployCreate3AndInit({ 822 | salt: _generateSalt(), 823 | initCode: initCode, 824 | data: data, 825 | values: values, 826 | refundAddress: msg.sender 827 | }); 828 | } 829 | 830 | /** 831 | * @dev Returns the address where a contract will be stored if deployed via `deployer` using 832 | * the `CREATE3` pattern (i.e. without an initcode factor). Any change in the `salt` value will 833 | * result in a new destination address. This implementation is based on Solady: 834 | * https://web.archive.org/web/20230921114120/https://raw.githubusercontent.com/Vectorized/solady/1c1ac4ad9c8558001e92d8d1a7722ef67bec75df/src/utils/CREATE3.sol. 835 | * @param salt The 32-byte random value used to create the proxy contract address. 836 | * @param deployer The 20-byte deployer address. 837 | * @return computedAddress The 20-byte address where a contract will be stored. 838 | */ 839 | function computeCreate3Address(bytes32 salt, address deployer) public pure returns (address computedAddress) { 840 | assembly ("memory-safe") { 841 | let ptr := mload(0x40) 842 | mstore(0x00, deployer) 843 | mstore8(0x0b, 0xff) 844 | mstore(0x20, salt) 845 | mstore( 846 | 0x40, 847 | hex"21_c3_5d_be_1b_34_4a_24_88_cf_33_21_d6_ce_54_2f_8e_9f_30_55_44_ff_09_e4_99_3a_62_31_9a_49_7c_1f" 848 | ) 849 | mstore(0x14, keccak256(0x0b, 0x55)) 850 | mstore(0x40, ptr) 851 | mstore(0x00, 0xd694) 852 | mstore8(0x34, 0x01) 853 | computedAddress := keccak256(0x1e, 0x17) 854 | } 855 | } 856 | 857 | /** 858 | * @dev Returns the address where a contract will be stored if deployed via this contract using 859 | * the `CREATE3` pattern (i.e. without an initcode factor). Any change in the `salt` value will 860 | * result in a new destination address. This implementation is based on Solady: 861 | * https://web.archive.org/web/20230921114120/https://raw.githubusercontent.com/Vectorized/solady/1c1ac4ad9c8558001e92d8d1a7722ef67bec75df/src/utils/CREATE3.sol. 862 | * @param salt The 32-byte random value used to create the proxy contract address. 863 | * @return computedAddress The 20-byte address where a contract will be stored. 864 | */ 865 | function computeCreate3Address(bytes32 salt) public view returns (address computedAddress) { 866 | computedAddress = computeCreate3Address({salt: salt, deployer: _SELF}); 867 | } 868 | 869 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 870 | /* HELPER FUNCTIONS */ 871 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 872 | 873 | /** 874 | * @dev Implements different safeguarding mechanisms depending on the encoded values in the salt 875 | * (`||` stands for byte-wise concatenation): 876 | * => salt (32 bytes) = 0xbebebebebebebebebebebebebebebebebebebebe||ff||1212121212121212121212 877 | * - The first 20 bytes (i.e. `bebebebebebebebebebebebebebebebebebebebe`) may be used to 878 | * implement a permissioned deploy protection by setting them equal to `msg.sender`, 879 | * - The 21st byte (i.e. `ff`) may be used to implement a cross-chain redeploy protection by 880 | * setting it equal to `0x01`, 881 | * - The last random 11 bytes (i.e. `1212121212121212121212`) allow for 2**88 bits of entropy 882 | * for mining a salt. 883 | * @param salt The 32-byte random value used to create the contract address. 884 | * @return guardedSalt The guarded 32-byte random value used to create the contract address. 885 | */ 886 | function _guard(bytes32 salt) internal view returns (bytes32 guardedSalt) { 887 | (SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag) = _parseSalt({salt: salt}); 888 | 889 | if (senderBytes == SenderBytes.MsgSender && redeployProtectionFlag == RedeployProtectionFlag.True) { 890 | // Configures a permissioned deploy protection as well as a cross-chain redeploy protection. 891 | guardedSalt = keccak256(abi.encode(msg.sender, block.chainid, salt)); 892 | } else if (senderBytes == SenderBytes.MsgSender && redeployProtectionFlag == RedeployProtectionFlag.False) { 893 | // Configures solely a permissioned deploy protection. 894 | guardedSalt = _efficientHash({a: bytes32(uint256(uint160(msg.sender))), b: salt}); 895 | } else if (senderBytes == SenderBytes.MsgSender) { 896 | // Reverts if the 21st byte is greater than `0x01` in order to enforce developer explicitness. 897 | revert InvalidSalt({emitter: _SELF}); 898 | } else if (senderBytes == SenderBytes.ZeroAddress && redeployProtectionFlag == RedeployProtectionFlag.True) { 899 | // Configures solely a cross-chain redeploy protection. In order to prevent a pseudo-randomly 900 | // generated cross-chain redeploy protection, we enforce the zero address check for the first 20 bytes. 901 | guardedSalt = _efficientHash({a: bytes32(block.chainid), b: salt}); 902 | } else if ( 903 | senderBytes == SenderBytes.ZeroAddress && redeployProtectionFlag == RedeployProtectionFlag.Unspecified 904 | ) { 905 | // Reverts if the 21st byte is greater than `0x01` in order to enforce developer explicitness. 906 | revert InvalidSalt({emitter: _SELF}); 907 | } else { 908 | // For the non-pseudo-random cases, the salt value `salt` is hashed to prevent the safeguard mechanisms 909 | // from being bypassed. Otherwise, the salt value `salt` is not modified. 910 | guardedSalt = (salt != _generateSalt()) ? keccak256(abi.encode(salt)) : salt; 911 | } 912 | } 913 | 914 | /** 915 | * @dev Returns the enum for the selection of a permissioned deploy protection as well as a 916 | * cross-chain redeploy protection. 917 | * @param salt The 32-byte random value used to create the contract address. 918 | * @return senderBytes The 8-byte enum for the selection of a permissioned deploy protection. 919 | * @return redeployProtectionFlag The 8-byte enum for the selection of a cross-chain redeploy 920 | * protection. 921 | */ 922 | function _parseSalt( 923 | bytes32 salt 924 | ) internal view returns (SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag) { 925 | if (address(bytes20(salt)) == msg.sender && bytes1(salt[20]) == hex"01") { 926 | (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.True); 927 | } else if (address(bytes20(salt)) == msg.sender && bytes1(salt[20]) == hex"00") { 928 | (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.False); 929 | } else if (address(bytes20(salt)) == msg.sender) { 930 | (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.Unspecified); 931 | } else if (address(bytes20(salt)) == address(0) && bytes1(salt[20]) == hex"01") { 932 | (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.True); 933 | } else if (address(bytes20(salt)) == address(0) && bytes1(salt[20]) == hex"00") { 934 | (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.False); 935 | } else if (address(bytes20(salt)) == address(0)) { 936 | (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.Unspecified); 937 | } else if (bytes1(salt[20]) == hex"01") { 938 | (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.True); 939 | } else if (bytes1(salt[20]) == hex"00") { 940 | (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.False); 941 | } else { 942 | (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.Unspecified); 943 | } 944 | } 945 | 946 | /** 947 | * @dev Returns the `keccak256` hash of `a` and `b` after concatenation. 948 | * @param a The first 32-byte value to be concatenated and hashed. 949 | * @param b The second 32-byte value to be concatenated and hashed. 950 | * @return hash The 32-byte `keccak256` hash of `a` and `b`. 951 | */ 952 | function _efficientHash(bytes32 a, bytes32 b) internal pure returns (bytes32 hash) { 953 | assembly ("memory-safe") { 954 | mstore(0x00, a) 955 | mstore(0x20, b) 956 | hash := keccak256(0x00, 0x40) 957 | } 958 | } 959 | 960 | /** 961 | * @dev Generates pseudo-randomly a salt value using a diverse selection of block and 962 | * transaction properties. 963 | * @return salt The 32-byte pseudo-random salt value. 964 | */ 965 | function _generateSalt() internal view returns (bytes32 salt) { 966 | unchecked { 967 | salt = keccak256( 968 | abi.encode( 969 | // We don't use `block.number - 256` (the maximum value on the EVM) to accommodate 970 | // any chains that may try to reduce the amount of available historical block hashes. 971 | // We also don't subtract 1 to mitigate any risks arising from consecutive block 972 | // producers on a PoS chain. Therefore, we use `block.number - 32` as a reasonable 973 | // compromise, one we expect should work on most chains, which is 1 epoch on Ethereum 974 | // mainnet. Please note that if you use this function between the genesis block and block 975 | // number 31, the block property `blockhash` will return zero, but the returned salt value 976 | // `salt` will still have a non-zero value due to the hashing characteristic and the other 977 | // remaining properties. 978 | blockhash(block.number - 32), 979 | block.coinbase, 980 | block.number, 981 | block.timestamp, 982 | block.prevrandao, 983 | block.chainid, 984 | msg.sender 985 | ) 986 | ); 987 | } 988 | } 989 | 990 | /** 991 | * @dev Ensures that `newContract` is a non-zero byte contract. 992 | * @param success The Boolean success condition. 993 | * @param newContract The 20-byte address where the contract was deployed. 994 | */ 995 | function _requireSuccessfulContractCreation(bool success, address newContract) internal view { 996 | // Note that reverting if `newContract == address(0)` isn't strictly necessary here, as if 997 | // the deployment fails, `success == false` should already hold. However, since the `CreateX` 998 | // contract should be usable and safe on a wide range of chains, this check is cheap enough 999 | // that there is no harm in including it (security > gas optimisations). It can potentially 1000 | // protect against unexpected chain behaviour or a hypothetical compiler bug that doesn't surface 1001 | // the call success status properly. 1002 | if (!success || newContract == address(0) || newContract.code.length == 0) { 1003 | revert FailedContractCreation({emitter: _SELF}); 1004 | } 1005 | } 1006 | 1007 | /** 1008 | * @dev Ensures that `newContract` is a non-zero byte contract. 1009 | * @param newContract The 20-byte address where the contract was deployed. 1010 | */ 1011 | function _requireSuccessfulContractCreation(address newContract) internal view { 1012 | if (newContract == address(0) || newContract.code.length == 0) { 1013 | revert FailedContractCreation({emitter: _SELF}); 1014 | } 1015 | } 1016 | 1017 | /** 1018 | * @dev Ensures that the contract initialisation call to `implementation` has been successful. 1019 | * @param success The Boolean success condition. 1020 | * @param returnData The return data from the contract initialisation call. 1021 | * @param implementation The 20-byte address where the implementation was deployed. 1022 | */ 1023 | function _requireSuccessfulContractInitialisation( 1024 | bool success, 1025 | bytes memory returnData, 1026 | address implementation 1027 | ) internal view { 1028 | if (!success || implementation.code.length == 0) { 1029 | revert FailedContractInitialisation({emitter: _SELF, revertData: returnData}); 1030 | } 1031 | } 1032 | } 1033 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { extendConfig, subtask, task } from "hardhat/config"; 3 | import { xdeployConfigExtender } from "./internal/config"; 4 | import { TaskArgs } from "./internal/types"; 5 | import { 6 | networksInfo, 7 | networks, 8 | getTxHashLink, 9 | getAddressLink, 10 | } from "./utils/networks"; 11 | import { 12 | CREATE2_DEPLOYER_ADDRESS, 13 | PLUGIN_NAME, 14 | TASK_VERIFY_NETWORK_ARGUMENTS, 15 | TASK_VERIFY_SUPPORTED_NETWORKS, 16 | TASK_VERIFY_EQUAL_ARGS_NETWORKS, 17 | TASK_VERIFY_SALT, 18 | TASK_VERIFY_SIGNER, 19 | TASK_VERIFY_CONTRACT, 20 | TASK_VERIFY_GASLIMIT, 21 | } from "./utils/constants"; 22 | import { RESET, GREEN, RED, YELLOW } from "./utils/colour-codes"; 23 | import { createNetworkInfoTable } from "./utils/table"; 24 | import "./internal/type-extensions"; 25 | import abi from "./abi/CreateX.json"; 26 | 27 | import { NomicLabsHardhatPluginError } from "hardhat/plugins"; 28 | import "@nomicfoundation/hardhat-ethers"; 29 | import * as fs from "fs"; 30 | import path from "path"; 31 | 32 | extendConfig(xdeployConfigExtender); 33 | 34 | task("xdeploy", "Deploys the contract across all predefined networks") 35 | .addFlag( 36 | "listNetworks", 37 | "List all supported networks and corresponding chain IDs", 38 | ) 39 | .setAction(async (taskArgs: TaskArgs, hre) => { 40 | if (taskArgs.listNetworks) { 41 | createNetworkInfoTable(networksInfo); 42 | return; 43 | } 44 | 45 | await hre.run(TASK_VERIFY_NETWORK_ARGUMENTS); 46 | await hre.run(TASK_VERIFY_SUPPORTED_NETWORKS); 47 | await hre.run(TASK_VERIFY_EQUAL_ARGS_NETWORKS); 48 | await hre.run(TASK_VERIFY_SALT); 49 | await hre.run(TASK_VERIFY_SIGNER); 50 | await hre.run(TASK_VERIFY_CONTRACT); 51 | await hre.run(TASK_VERIFY_GASLIMIT); 52 | 53 | await hre.run("compile"); 54 | 55 | if (hre.config.xdeploy.rpcUrls && hre.config.xdeploy.networks) { 56 | const providers: any[] = []; 57 | const wallets: any[] = []; 58 | const signers: any[] = []; 59 | const create2Deployer: any[] = []; 60 | const createReceipt: any[] = []; 61 | const result: any[] = []; 62 | const dir = "./deployments"; 63 | 64 | let initcode: any; 65 | let computedContractAddress: any; 66 | let chainId: any; 67 | 68 | console.log( 69 | "\nThe deployment is starting... Please bear with me, this may take a minute or two. Anyway, WAGMI!", 70 | ); 71 | 72 | if ( 73 | hre.config.xdeploy.constructorArgsPath && 74 | hre.config.xdeploy.contract 75 | ) { 76 | const args = await import( 77 | path.normalize( 78 | path.join( 79 | hre.config.paths.root, 80 | hre.config.xdeploy.constructorArgsPath, 81 | ), 82 | ) 83 | ); 84 | 85 | const contract = await hre.ethers.getContractFactory( 86 | hre.config.xdeploy.contract, 87 | ); 88 | 89 | const ext = hre.config.xdeploy.constructorArgsPath.split(".").pop(); 90 | if (ext === "ts") { 91 | initcode = await contract.getDeployTransaction(...args.data); 92 | } else if (ext === "js") { 93 | initcode = await contract.getDeployTransaction(...args.default); 94 | } 95 | } else if ( 96 | !hre.config.xdeploy.constructorArgsPath && 97 | hre.config.xdeploy.contract 98 | ) { 99 | const contract = await hre.ethers.getContractFactory( 100 | hre.config.xdeploy.contract, 101 | ); 102 | initcode = await contract.getDeployTransaction(); 103 | } 104 | 105 | for (let i = 0; i < hre.config.xdeploy.rpcUrls.length; i++) { 106 | providers[i] = new hre.ethers.JsonRpcProvider( 107 | hre.config.xdeploy.rpcUrls[i], 108 | ); 109 | 110 | wallets[i] = new hre.ethers.Wallet( 111 | hre.config.xdeploy.signer, 112 | providers[i], 113 | ); 114 | 115 | signers[i] = wallets[i].connect(providers[i]); 116 | 117 | const network = hre.config.xdeploy.networks[i]; 118 | 119 | if (!["hardhat", "localhost"].includes(network)) { 120 | create2Deployer[i] = new hre.ethers.Contract( 121 | CREATE2_DEPLOYER_ADDRESS, 122 | abi, 123 | signers[i], 124 | ); 125 | 126 | if (hre.config.xdeploy.salt) { 127 | try { 128 | let counter = 0; 129 | computedContractAddress = await create2Deployer[ 130 | i 131 | ].computeCreate2Address( 132 | hre.ethers.keccak256(hre.ethers.id(hre.config.xdeploy.salt)), 133 | hre.ethers.keccak256(initcode.data), 134 | ); 135 | if (counter === 0) { 136 | console.log( 137 | `\nYour deployment parameters will lead to the following contract address: ${GREEN}${computedContractAddress}${RESET}\n` + 138 | `\n${YELLOW}=> If this does not match your expectation, given a previous deployment, you have either changed the value of${RESET}\n` + 139 | `${YELLOW}the salt parameter or the bytecode of the contract!${RESET}\n`, 140 | ); 141 | } 142 | ++counter; 143 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 144 | } catch (err) { 145 | throw new NomicLabsHardhatPluginError( 146 | PLUGIN_NAME, 147 | `The contract address could not be computed. Please check your contract name and constructor arguments.`, 148 | ); 149 | } 150 | 151 | if ( 152 | (await providers[i].getCode(computedContractAddress)) !== "0x" 153 | ) { 154 | throw new NomicLabsHardhatPluginError( 155 | PLUGIN_NAME, 156 | `The address of the contract you want to deploy already has existing bytecode on ${network}. 157 | It is very likely that you have deployed this contract before with the same salt parameter value. 158 | Please try using a different salt value.`, 159 | ); 160 | } 161 | 162 | try { 163 | createReceipt[i] = await create2Deployer[i].deployCreate2( 164 | hre.ethers.id(hre.config.xdeploy.salt), 165 | initcode.data, 166 | { gasLimit: hre.config.xdeploy.gasLimit }, 167 | ); 168 | 169 | chainId = createReceipt[i].chainId; 170 | 171 | createReceipt[i] = await createReceipt[i].wait(); 172 | 173 | result[i] = { 174 | network: network, 175 | chainId: chainId.toString(), 176 | contract: hre.config.xdeploy.contract, 177 | txHash: createReceipt[i].hash, 178 | txHashLink: getTxHashLink(network, createReceipt[i].hash), 179 | address: computedContractAddress, 180 | addressLink: getAddressLink(network, computedContractAddress), 181 | receipt: createReceipt[i].toJSON(), 182 | deployed: true, 183 | error: undefined, 184 | }; 185 | 186 | if (!fs.existsSync(dir)) { 187 | fs.mkdirSync(dir); 188 | } 189 | 190 | const saveDir = path.normalize( 191 | path.join( 192 | hre.config.paths.root, 193 | "deployments", 194 | `${hre.config.xdeploy.contract}_${network}_deployment.json`, 195 | ), 196 | ); 197 | 198 | fs.writeFileSync(saveDir, JSON.stringify(result[i])); 199 | 200 | console.log( 201 | `\n${GREEN}----------------------------------------------------------${RESET}\n` + 202 | `${GREEN}><><><>< XDEPLOY DEPLOYMENT ${ 203 | i + 1 204 | } ><><><><${RESET}\n` + 205 | `${GREEN}----------------------------------------------------------${RESET}\n\n` + 206 | `Deployment status: ${GREEN}successful${RESET}\n\n` + 207 | `Network: ${GREEN}${result[i].network}${RESET}\n\n` + 208 | `Chain ID: ${GREEN}${result[i].chainId}${RESET}\n\n` + 209 | `Contract name: ${GREEN}${result[i].contract}${RESET}\n\n` + 210 | `Contract creation transaction hash: ${GREEN}${result[i].txHashLink}${RESET}\n\n` + 211 | `Contract address: ${GREEN}${result[i].addressLink}${RESET}\n\n` + 212 | `Transaction details written to: ${GREEN}${saveDir}${RESET}\n`, 213 | ); 214 | } catch (err) { 215 | result[i] = { 216 | network: network, 217 | chainId: undefined, 218 | contract: hre.config.xdeploy.contract, 219 | txHash: undefined, 220 | txHashLink: undefined, 221 | address: computedContractAddress, 222 | addressLink: undefined, 223 | receipt: undefined, 224 | deployed: false, 225 | error: err, 226 | }; 227 | 228 | if (!fs.existsSync(dir)) { 229 | fs.mkdirSync(dir); 230 | } 231 | 232 | const saveDir = path.normalize( 233 | path.join( 234 | hre.config.paths.root, 235 | "deployments", 236 | `${hre.config.xdeploy.contract}_${network}_deployment_debug.json`, 237 | ), 238 | ); 239 | 240 | fs.writeFileSync(saveDir, JSON.stringify(result[i])); 241 | 242 | console.log( 243 | `\n${RED}----------------------------------------------------------${RESET}\n` + 244 | `${RED}><><><>< XDEPLOY DEPLOYMENT ${ 245 | i + 1 246 | } ><><><><${RESET}\n` + 247 | `${RED}----------------------------------------------------------${RESET}\n\n` + 248 | `Deployment status: ${RED}failed${RESET}\n\n` + 249 | `Network: ${RED}${result[i].network}${RESET}\n\n` + 250 | `Contract name: ${RED}${result[i].contract}${RESET}\n\n` + 251 | `Error details written to: ${RED}${saveDir}${RESET}\n\n` + 252 | `${RED}=> Debugging hint: Many deployment errors are due to a too low gasLimit or a reused salt parameter value.${RESET}\n`, 253 | ); 254 | } 255 | } 256 | } else if (["hardhat", "localhost"].includes(network)) { 257 | let hhcreate2Deployer = await hre.ethers.getContractFactory( 258 | "Create2DeployerLocal", 259 | ); 260 | 261 | if (network === "localhost") { 262 | hhcreate2Deployer = await hre.ethers.getContractFactory( 263 | "Create2DeployerLocal", 264 | signers[i], 265 | ); 266 | } 267 | 268 | create2Deployer[i] = await hhcreate2Deployer.deploy(); 269 | 270 | if (hre.config.xdeploy.salt) { 271 | try { 272 | let counter = 0; 273 | computedContractAddress = await create2Deployer[ 274 | i 275 | ].computeCreate2Address( 276 | hre.ethers.keccak256(hre.ethers.id(hre.config.xdeploy.salt)), 277 | hre.ethers.keccak256(initcode.data), 278 | ); 279 | if (counter === 0) { 280 | console.log( 281 | `\nYour deployment parameters will lead to the following contract address: ${GREEN}${computedContractAddress}${RESET}\n` + 282 | `\n${YELLOW}=> If this does not match your expectation, given a previous deployment, you have either changed the value of${RESET}\n` + 283 | `${YELLOW}the salt parameter or the bytecode of the contract!${RESET}\n`, 284 | ); 285 | } 286 | ++counter; 287 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 288 | } catch (err) { 289 | throw new NomicLabsHardhatPluginError( 290 | PLUGIN_NAME, 291 | `The contract address could not be computed. Please check your contract name and constructor arguments.`, 292 | ); 293 | } 294 | 295 | try { 296 | createReceipt[i] = await create2Deployer[i].deployCreate2( 297 | hre.ethers.id(hre.config.xdeploy.salt), 298 | initcode.data, 299 | { gasLimit: hre.config.xdeploy.gasLimit }, 300 | ); 301 | 302 | chainId = createReceipt[i].chainId; 303 | 304 | createReceipt[i] = await createReceipt[i].wait(); 305 | 306 | result[i] = { 307 | network: network, 308 | chainId: chainId.toString(), 309 | contract: hre.config.xdeploy.contract, 310 | txHash: createReceipt[i].hash, 311 | txHashLink: networksInfo[network].url, 312 | address: computedContractAddress, 313 | addressLink: networksInfo[network].url, 314 | receipt: createReceipt[i].toJSON(), 315 | deployed: true, 316 | error: undefined, 317 | }; 318 | 319 | if (!fs.existsSync(dir)) { 320 | fs.mkdirSync(dir); 321 | } 322 | 323 | const saveDir = path.normalize( 324 | path.join( 325 | hre.config.paths.root, 326 | "deployments", 327 | `${hre.config.xdeploy.contract}_${network}_deployment.json`, 328 | ), 329 | ); 330 | 331 | fs.writeFileSync(saveDir, JSON.stringify(result[i])); 332 | 333 | console.log( 334 | `\n${GREEN}----------------------------------------------------------${RESET}\n` + 335 | `${GREEN}><><><>< XDEPLOY DEPLOYMENT ${ 336 | i + 1 337 | } ><><><><${RESET}\n` + 338 | `${GREEN}----------------------------------------------------------${RESET}\n\n` + 339 | `Deployment status: ${GREEN}successful${RESET}\n\n` + 340 | `Network: ${GREEN}${result[i].network}${RESET}\n\n` + 341 | `Chain ID: ${GREEN}${result[i].chainId}${RESET}\n\n` + 342 | `Contract name: ${GREEN}${result[i].contract}${RESET}\n\n` + 343 | `Contract creation transaction: ${GREEN}${result[i].txHash}${RESET}\n\n` + 344 | `Contract address: ${GREEN}${result[i].address}${RESET}\n\n` + 345 | `Transaction details written to: ${GREEN}${saveDir}${RESET}\n`, 346 | ); 347 | } catch (err) { 348 | result[i] = { 349 | network: network, 350 | chainId: undefined, 351 | contract: hre.config.xdeploy.contract, 352 | txHash: undefined, 353 | txHashLink: undefined, 354 | address: computedContractAddress, 355 | addressLink: undefined, 356 | receipt: undefined, 357 | deployed: false, 358 | error: err, 359 | }; 360 | 361 | if (!fs.existsSync(dir)) { 362 | fs.mkdirSync(dir); 363 | } 364 | 365 | const saveDir = path.normalize( 366 | path.join( 367 | hre.config.paths.root, 368 | "deployments", 369 | `${hre.config.xdeploy.contract}_${network}_deployment_debug.json`, 370 | ), 371 | ); 372 | 373 | fs.writeFileSync(saveDir, JSON.stringify(result[i])); 374 | 375 | console.log( 376 | `\n${RED}----------------------------------------------------------${RESET}\n` + 377 | `${RED}><><><>< XDEPLOY DEPLOYMENT ${ 378 | i + 1 379 | } ><><><><${RESET}\n` + 380 | `${RED}----------------------------------------------------------${RESET}\n\n` + 381 | `Deployment status: ${RED}failed${RESET}\n\n` + 382 | `Network: ${RED}${result[i].network}${RESET}\n\n` + 383 | `Contract name: ${RED}${result[i].contract}${RESET}\n\n` + 384 | `Error details written to: ${RED}${saveDir}${RESET}\n\n` + 385 | `${RED}=> Debugging hint: Many deployment errors are due to a too low gasLimit or a reused salt parameter value.${RESET}\n`, 386 | ); 387 | } 388 | } 389 | } 390 | } 391 | } 392 | }); 393 | 394 | subtask(TASK_VERIFY_NETWORK_ARGUMENTS).setAction(async (_, hre) => { 395 | if ( 396 | !hre.config.xdeploy.networks || 397 | hre.config.xdeploy.networks.length === 0 398 | ) { 399 | throw new NomicLabsHardhatPluginError( 400 | PLUGIN_NAME, 401 | `Please provide at least one deployment network via the hardhat config. 402 | E.g.: { [...], xdeploy: { networks: ["sepolia", "holesky"] }, [...] } 403 | The current supported networks are ${networks}.`, 404 | ); 405 | } 406 | }); 407 | 408 | subtask(TASK_VERIFY_SUPPORTED_NETWORKS).setAction(async (_, hre) => { 409 | const unsupported = hre?.config?.xdeploy?.networks?.filter( 410 | (v) => !networksInfo[v], 411 | ); 412 | if (unsupported && unsupported.length > 0) { 413 | throw new NomicLabsHardhatPluginError( 414 | PLUGIN_NAME, 415 | `You have tried to configure a network that this plugin does not yet support, 416 | or you have misspelled the network name. The currently supported networks are 417 | ${networks}.`, 418 | ); 419 | } 420 | }); 421 | 422 | subtask(TASK_VERIFY_EQUAL_ARGS_NETWORKS).setAction(async (_, hre) => { 423 | if ( 424 | hre.config.xdeploy.networks && 425 | hre.config.xdeploy.rpcUrls && 426 | hre.config.xdeploy.rpcUrls.length !== hre.config.xdeploy.networks.length 427 | ) { 428 | throw new NomicLabsHardhatPluginError( 429 | PLUGIN_NAME, 430 | `The parameters "network" and "rpcUrls" do not have the same length. 431 | Please ensure that both parameters have the same length, i.e. for each 432 | network there is a corresponding rpcUrls entry.`, 433 | ); 434 | } 435 | }); 436 | 437 | subtask(TASK_VERIFY_SALT).setAction(async (_, hre) => { 438 | if (!hre.config.xdeploy.salt || hre.config.xdeploy.salt === "") { 439 | throw new NomicLabsHardhatPluginError( 440 | PLUGIN_NAME, 441 | `Please provide an arbitrary value as salt. 442 | E.g.: { [...], xdeploy: { salt: "WAGMI" }, [...] }.`, 443 | ); 444 | } 445 | }); 446 | 447 | subtask(TASK_VERIFY_SIGNER).setAction(async (_, hre) => { 448 | if (!hre.config.xdeploy.signer || hre.config.xdeploy.signer === "") { 449 | throw new NomicLabsHardhatPluginError( 450 | PLUGIN_NAME, 451 | `Please provide a signer private key. We recommend using Hardhat configuration variables. 452 | See https://hardhat.org/hardhat-runner/docs/guides/configuration-variables. 453 | E.g.: { [...], xdeploy: { signer: vars.get("PRIVATE_KEY", "") }, [...] }.`, 454 | ); 455 | } 456 | }); 457 | 458 | subtask(TASK_VERIFY_CONTRACT).setAction(async (_, hre) => { 459 | if (!hre.config.xdeploy.contract || hre.config.xdeploy.contract === "") { 460 | throw new NomicLabsHardhatPluginError( 461 | PLUGIN_NAME, 462 | `Please specify the contract name of the smart contract to be deployed. 463 | E.g.: { [...], xdeploy: { contract: "ERC20" }, [...] }.`, 464 | ); 465 | } 466 | }); 467 | 468 | subtask(TASK_VERIFY_GASLIMIT).setAction(async (_, hre) => { 469 | if ( 470 | hre.config.xdeploy.gasLimit && 471 | hre.config.xdeploy.gasLimit > 15 * 10 ** 6 472 | ) { 473 | throw new NomicLabsHardhatPluginError( 474 | PLUGIN_NAME, 475 | `Please specify a lower gasLimit. Each block has currently 476 | a target size of 15 million gas.`, 477 | ); 478 | } 479 | }); 480 | -------------------------------------------------------------------------------- /src/internal/config.ts: -------------------------------------------------------------------------------- 1 | import { ConfigExtender } from "hardhat/types"; 2 | import { GASLIMIT } from "../utils/constants"; 3 | 4 | export const xdeployConfigExtender: ConfigExtender = (config, userConfig) => { 5 | const defaultConfig = { 6 | contract: undefined, 7 | constructorArgsPath: undefined, 8 | salt: undefined, 9 | signer: undefined, 10 | networks: [], 11 | rpcUrls: [], 12 | gasLimit: GASLIMIT, 13 | }; 14 | 15 | if (userConfig.xdeploy) { 16 | const customConfig = userConfig.xdeploy; 17 | config.xdeploy = { 18 | ...defaultConfig, 19 | ...customConfig, 20 | }; 21 | } else { 22 | config.xdeploy = defaultConfig; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/internal/type-extensions.ts: -------------------------------------------------------------------------------- 1 | import "hardhat/types/config"; 2 | import { XdeployConfig } from "./types"; 3 | 4 | declare module "hardhat/types/config" { 5 | interface HardhatUserConfig { 6 | xdeploy?: XdeployConfig; 7 | } 8 | 9 | interface HardhatConfig { 10 | xdeploy: XdeployConfig; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/internal/types.ts: -------------------------------------------------------------------------------- 1 | import { SupportedNetwork } from "../utils/networks"; 2 | 3 | /* eslint-disable @typescript-eslint/no-explicit-any */ 4 | export interface XdeployConfig { 5 | contract?: string; 6 | constructorArgsPath?: string; 7 | salt?: string; 8 | signer?: any; 9 | networks?: SupportedNetwork[]; 10 | rpcUrls?: any[]; 11 | gasLimit?: number; 12 | } 13 | 14 | export interface TaskArgs { 15 | listNetworks: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/colour-codes.ts: -------------------------------------------------------------------------------- 1 | export const RESET = "\x1b[0m"; 2 | export const BRIGHT = "\x1b[1m"; 3 | export const GREEN = "\x1b[32m"; 4 | export const RED = "\x1b[31m"; 5 | export const YELLOW = "\x1b[33m"; 6 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const CREATE2_DEPLOYER_ADDRESS = 2 | "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed"; 3 | export const GASLIMIT = 1.5 * 10 ** 6; 4 | export const PLUGIN_NAME = "xdeployer"; 5 | export const TASK_VERIFY_NETWORK_ARGUMENTS = "verify:get-network-arguments"; 6 | export const TASK_VERIFY_SUPPORTED_NETWORKS = "verify:get-supported-networks"; 7 | export const TASK_VERIFY_EQUAL_ARGS_NETWORKS = "verify:get-equal-args-networks"; 8 | export const TASK_VERIFY_SALT = "verify:get-salt"; 9 | export const TASK_VERIFY_SIGNER = "verify:get-signer"; 10 | export const TASK_VERIFY_CONTRACT = "verify:get-contract"; 11 | export const TASK_VERIFY_GASLIMIT = "verify:get-gaslimit"; 12 | -------------------------------------------------------------------------------- /src/utils/networks.ts: -------------------------------------------------------------------------------- 1 | // List of supported networks with corresponding block explorer links and chain IDs 2 | export const networksInfo = { 3 | localhost: { url: "N/A", chainId: undefined }, 4 | hardhat: { url: "N/A", chainId: 31337 }, 5 | sepolia: { url: "https://sepolia.etherscan.io", chainId: 11155111 }, 6 | holesky: { url: "https://holesky.etherscan.io", chainId: 17000 }, 7 | hoodi: { url: "https://hoodi.etherscan.io", chainId: 560048 }, 8 | bscTestnet: { url: "https://testnet.bscscan.com", chainId: 97 }, 9 | optimismSepolia: { 10 | url: "https://sepolia-optimism.etherscan.io", 11 | chainId: 11155420, 12 | }, 13 | arbitrumSepolia: { url: "https://sepolia.arbiscan.io", chainId: 421614 }, 14 | amoy: { url: "https://amoy.polygonscan.com", chainId: 80002 }, 15 | polygonZkEVMTestnet: { 16 | url: "https://cardona-zkevm.polygonscan.com", 17 | chainId: 2442, 18 | }, 19 | fantomTestnet: { 20 | url: "https://explorer.testnet.fantom.network", 21 | chainId: 4002, 22 | }, 23 | fuji: { url: "https://testnet.snowscan.xyz", chainId: 43113 }, 24 | chiado: { url: "https://gnosis-chiado.blockscout.com", chainId: 10200 }, 25 | moonbaseAlpha: { url: "https://moonbase.moonscan.io", chainId: 1287 }, 26 | alfajores: { url: "https://alfajores.celoscan.io", chainId: 44787 }, 27 | auroraTestnet: { 28 | url: "https://explorer.testnet.aurora.dev", 29 | chainId: 1313161555, 30 | }, 31 | harmonyTestnet: { 32 | url: "https://explorer.testnet.harmony.one", 33 | chainId: 1666700000, 34 | }, 35 | spark: { url: "https://explorer.fusespark.io", chainId: 123 }, 36 | cronosTestnet: { url: "https://cronos.org/explorer/testnet3", chainId: 338 }, 37 | evmosTestnet: { 38 | url: "https://www.mintscan.io/evmos-testnet", 39 | chainId: 9000, 40 | }, 41 | bobaTestnet: { url: "https://testnet.bobascan.com", chainId: 2888 }, 42 | cantoTestnet: { url: "https://testnet.tuber.build", chainId: 7701 }, 43 | baseSepolia: { url: "https://sepolia.basescan.org", chainId: 84532 }, 44 | mantleTestnet: { url: "https://sepolia.mantlescan.xyz", chainId: 5003 }, 45 | filecoinTestnet: { 46 | url: "https://calibration.filfox.info/en", 47 | chainId: 314159, 48 | }, 49 | scrollSepolia: { url: "https://sepolia.scrollscan.com", chainId: 534351 }, 50 | lineaTestnet: { url: "https://sepolia.lineascan.build", chainId: 59141 }, 51 | zoraSepolia: { 52 | url: "https://sepolia.explorer.zora.energy", 53 | chainId: 999999999, 54 | }, 55 | luksoTestnet: { 56 | url: "https://explorer.execution.testnet.lukso.network", 57 | chainId: 4201, 58 | }, 59 | mantaTestnet: { 60 | url: "https://pacific-explorer.sepolia-testnet.manta.network", 61 | chainId: 3441006, 62 | }, 63 | blastTestnet: { url: "https://sepolia.blastscan.io", chainId: 168587773 }, 64 | dosTestnet: { url: "https://test.doscan.io", chainId: 3939 }, 65 | fraxtalTestnet: { url: "https://holesky.fraxscan.com", chainId: 2522 }, 66 | metisTestnet: { 67 | url: "https://sepolia-explorer.metisdevops.link", 68 | chainId: 59902, 69 | }, 70 | modeTestnet: { url: "https://sepolia.explorer.mode.network", chainId: 919 }, 71 | seiArcticDevnet: { 72 | url: "https://seitrace.com/?chain=arctic-1", 73 | chainId: 713715, 74 | }, 75 | seiAtlanticTestnet: { 76 | url: "https://seitrace.com/?chain=atlantic-2", 77 | chainId: 1328, 78 | }, 79 | xlayerTestnet: { 80 | url: "https://www.oklink.com/x-layer-testnet", 81 | chainId: 195, 82 | }, 83 | bobTestnet: { url: "https://testnet-explorer.gobob.xyz", chainId: 111 }, 84 | coreTestnet: { url: "https://scan.test.btcs.network", chainId: 1115 }, 85 | telosTestnet: { url: "https://testnet.teloscan.io", chainId: 41 }, 86 | rootstockTestnet: { 87 | url: "https://rootstock-testnet.blockscout.com", 88 | chainId: 31, 89 | }, 90 | chilizTestnet: { url: "https://testnet.chiliscan.com", chainId: 88882 }, 91 | taraxaTestnet: { url: "https://testnet.explorer.taraxa.io", chainId: 842 }, 92 | taikoTestnet: { url: "https://hekla.taikoscan.io", chainId: 167009 }, 93 | zetaChainTestnet: { 94 | url: "https://athens.explorer.zetachain.com", 95 | chainId: 7001, 96 | }, 97 | "5ireChainTestnet": { url: "https://testnet.5irescan.io", chainId: 997 }, 98 | sapphireTestnet: { 99 | url: "https://explorer.oasis.io/testnet/sapphire", 100 | chainId: 23295, 101 | }, 102 | worldChainTestnet: { 103 | url: "https://sepolia.worldscan.org", 104 | chainId: 4801, 105 | }, 106 | plumeTestnet: { 107 | url: "https://testnet-explorer.plume.org", 108 | chainId: 98867, 109 | }, 110 | unichainTestnet: { 111 | url: "https://sepolia.uniscan.xyz", 112 | chainId: 1301, 113 | }, 114 | xdcTestnet: { 115 | url: "https://testnet.xdcscan.com", 116 | chainId: 51, 117 | }, 118 | sxTestnet: { 119 | url: "https://explorerl2.toronto.sx.technology", 120 | chainId: 79479957, 121 | }, 122 | liskTestnet: { 123 | url: "https://sepolia-blockscout.lisk.com", 124 | chainId: 4202, 125 | }, 126 | metalL2Testnet: { 127 | url: "https://testnet.explorer.metall2.com", 128 | chainId: 1740, 129 | }, 130 | superseedTestnet: { 131 | url: "https://sepolia-explorer.superseed.xyz", 132 | chainId: 53302, 133 | }, 134 | storyTestnet: { 135 | url: "https://aeneid.storyscan.io", 136 | chainId: 1315, 137 | }, 138 | sonicTestnet: { 139 | url: "https://testnet.sonicscan.org", 140 | chainId: 57054, 141 | }, 142 | flowTestnet: { 143 | url: "https://evm-testnet.flowscan.io", 144 | chainId: 545, 145 | }, 146 | inkTestnet: { 147 | url: "https://explorer-sepolia.inkonchain.com", 148 | chainId: 763373, 149 | }, 150 | morphTestnet: { 151 | url: "https://explorer-holesky.morphl2.io", 152 | chainId: 2810, 153 | }, 154 | shapeTestnet: { 155 | url: "https://sepolia.shapescan.xyz", 156 | chainId: 11011, 157 | }, 158 | etherlinkTestnet: { 159 | url: "https://testnet.explorer.etherlink.com", 160 | chainId: 128123, 161 | }, 162 | soneiumTestnet: { 163 | url: "https://soneium-minato.blockscout.com", 164 | chainId: 1946, 165 | }, 166 | swellTestnet: { 167 | url: "https://swell-testnet-explorer.alt.technology", 168 | chainId: 1924, 169 | }, 170 | hemiTestnet: { 171 | url: "https://testnet.explorer.hemi.xyz", 172 | chainId: 743111, 173 | }, 174 | berachainTestnet: { 175 | url: "https://testnet.berascan.com", 176 | chainId: 80069, 177 | }, 178 | monadTestnet: { 179 | url: "https://testnet.monadexplorer.com", 180 | chainId: 10143, 181 | }, 182 | cornTestnet: { 183 | url: "https://testnet.cornscan.io", 184 | chainId: 21000001, 185 | }, 186 | arenazTestnet: { 187 | url: "https://arena-z.blockscout.com", 188 | chainId: 9897, 189 | }, 190 | iotexTestnet: { 191 | url: "https://testnet.iotexscan.io", 192 | chainId: 4690, 193 | }, 194 | hychainTestnet: { 195 | url: "https://testnet.explorer.hychain.com", 196 | chainId: 29112, 197 | }, 198 | zircuitTestnet: { 199 | url: "https://explorer.garfield-testnet.zircuit.com", 200 | chainId: 48898, 201 | }, 202 | megaETHTestnet: { 203 | url: "https://www.megaexplorer.xyz", 204 | chainId: 6342, 205 | }, 206 | bitlayerTestnet: { 207 | url: "https://testnet.btrscan.com", 208 | chainId: 200810, 209 | }, 210 | roninTestnet: { 211 | url: "https://saigon-app.roninchain.com", 212 | chainId: 2021, 213 | }, 214 | zkSyncTestnet: { 215 | url: "https://sepolia-era.zksync.network", 216 | chainId: 300, 217 | }, 218 | immutableZkEVMTestnet: { 219 | url: "https://explorer.testnet.immutable.com", 220 | chainId: 13473, 221 | }, 222 | abstractTestnet: { 223 | url: "https://sepolia.abscan.org", 224 | chainId: 11124, 225 | }, 226 | hyperevmTestnet: { 227 | url: "https://testnet.purrsec.com", 228 | chainId: 998, 229 | }, 230 | apeChainTestnet: { 231 | url: "https://curtis.apescan.io", 232 | chainId: 33111, 233 | }, 234 | ethMain: { url: "https://etherscan.io", chainId: 1 }, 235 | bscMain: { url: "https://bscscan.com", chainId: 56 }, 236 | optimismMain: { url: "https://optimistic.etherscan.io", chainId: 10 }, 237 | arbitrumOne: { url: "https://arbiscan.io", chainId: 42161 }, 238 | arbitrumNova: { url: "https://nova.arbiscan.io", chainId: 42170 }, 239 | polygon: { url: "https://polygonscan.com", chainId: 137 }, 240 | polygonZkEVMMain: { url: "https://zkevm.polygonscan.com", chainId: 1101 }, 241 | fantomMain: { url: "https://explorer.fantom.network", chainId: 250 }, 242 | avalanche: { url: "https://snowscan.xyz", chainId: 43114 }, 243 | gnosis: { url: "https://gnosisscan.io", chainId: 100 }, 244 | moonriver: { url: "https://moonriver.moonscan.io", chainId: 1285 }, 245 | moonbeam: { url: "https://moonbeam.moonscan.io", chainId: 1284 }, 246 | celo: { url: "https://celoscan.io", chainId: 42220 }, 247 | auroraMain: { 248 | url: "https://explorer.mainnet.aurora.dev", 249 | chainId: 1313161554, 250 | }, 251 | harmonyMain: { url: "https://explorer.harmony.one", chainId: 1666600000 }, 252 | fuse: { url: "https://explorer.fuse.io", chainId: 122 }, 253 | cronosMain: { url: "https://cronoscan.com", chainId: 25 }, 254 | evmosMain: { url: "https://www.mintscan.io/evmos", chainId: 9001 }, 255 | bobaMain: { url: "https://bobascan.com", chainId: 288 }, 256 | cantoMain: { url: "https://tuber.build", chainId: 7700 }, 257 | baseMain: { url: "https://basescan.org", chainId: 8453 }, 258 | mantleMain: { url: "https://mantlescan.xyz", chainId: 5000 }, 259 | filecoinMain: { url: "https://filfox.info/en", chainId: 314 }, 260 | scrollMain: { url: "https://scrollscan.com", chainId: 534352 }, 261 | lineaMain: { url: "https://lineascan.build", chainId: 59144 }, 262 | zoraMain: { url: "https://explorer.zora.energy", chainId: 7777777 }, 263 | luksoMain: { 264 | url: "https://explorer.execution.mainnet.lukso.network", 265 | chainId: 42, 266 | }, 267 | mantaMain: { url: "https://pacific-explorer.manta.network", chainId: 169 }, 268 | blastMain: { url: "https://blastscan.io", chainId: 81457 }, 269 | dosMain: { url: "https://doscan.io", chainId: 7979 }, 270 | fraxtalMain: { url: "https://fraxscan.com", chainId: 252 }, 271 | enduranceMain: { 272 | url: "https://explorer-endurance.fusionist.io", 273 | chainId: 648, 274 | }, 275 | kavaMain: { url: "https://kavascan.com", chainId: 2222 }, 276 | metisMain: { url: "https://andromeda-explorer.metis.io", chainId: 1088 }, 277 | modeMain: { url: "https://explorer.mode.network", chainId: 34443 }, 278 | seiMain: { url: "https://seitrace.com/?chain=pacific-1", chainId: 1329 }, 279 | xlayerMain: { url: "https://www.oklink.com/x-layer", chainId: 196 }, 280 | bobMain: { url: "https://explorer.gobob.xyz", chainId: 60808 }, 281 | coreMain: { url: "https://scan.coredao.org", chainId: 1116 }, 282 | telosMain: { url: "https://www.teloscan.io", chainId: 40 }, 283 | rootstockMain: { url: "https://rootstock.blockscout.com", chainId: 30 }, 284 | chilizMain: { url: "https://chiliscan.com", chainId: 88888 }, 285 | taraxaMain: { url: "https://mainnet.explorer.taraxa.io", chainId: 841 }, 286 | gravityAlphaMain: { url: "https://explorer.gravity.xyz", chainId: 1625 }, 287 | taikoMain: { url: "https://taikoscan.io", chainId: 167000 }, 288 | zetaChainMain: { url: "https://explorer.zetachain.com", chainId: 7000 }, 289 | "5ireChainMain": { url: "https://5irescan.io", chainId: 995 }, 290 | sapphireMain: { 291 | url: "https://explorer.oasis.io/mainnet/sapphire", 292 | chainId: 23294, 293 | }, 294 | worldChainMain: { 295 | url: "https://worldscan.org", 296 | chainId: 480, 297 | }, 298 | plumeMain: { 299 | url: "https://explorer.plume.org", 300 | chainId: 98866, 301 | }, 302 | unichainMain: { 303 | url: "https://uniscan.xyz", 304 | chainId: 130, 305 | }, 306 | xdcMain: { 307 | url: "https://xdcscan.com", 308 | chainId: 50, 309 | }, 310 | sxMain: { 311 | url: "https://explorerl2.sx.technology", 312 | chainId: 4162, 313 | }, 314 | liskMain: { 315 | url: "https://blockscout.lisk.com", 316 | chainId: 1135, 317 | }, 318 | metalL2Main: { 319 | url: "https://explorer.metall2.com", 320 | chainId: 1750, 321 | }, 322 | superseedMain: { 323 | url: "https://explorer.superseed.xyz", 324 | chainId: 5330, 325 | }, 326 | sonicMain: { 327 | url: "https://sonicscan.org", 328 | chainId: 146, 329 | }, 330 | flowMain: { 331 | url: "https://evm.flowscan.io", 332 | chainId: 747, 333 | }, 334 | inkMain: { 335 | url: "https://explorer.inkonchain.com", 336 | chainId: 57073, 337 | }, 338 | morphMain: { 339 | url: "https://explorer.morphl2.io", 340 | chainId: 2818, 341 | }, 342 | shapeMain: { 343 | url: "https://shapescan.xyz", 344 | chainId: 360, 345 | }, 346 | etherlinkMain: { 347 | url: "https://explorer.etherlink.com", 348 | chainId: 42793, 349 | }, 350 | soneiumMain: { 351 | url: "https://soneium.blockscout.com", 352 | chainId: 1868, 353 | }, 354 | swellMain: { 355 | url: "https://explorer.swellnetwork.io", 356 | chainId: 1923, 357 | }, 358 | hemiMain: { 359 | url: "https://explorer.hemi.xyz", 360 | chainId: 43111, 361 | }, 362 | berachainMain: { 363 | url: "https://berascan.com", 364 | chainId: 80094, 365 | }, 366 | cornMain: { 367 | url: "https://cornscan.io", 368 | chainId: 21000000, 369 | }, 370 | arenazMain: { 371 | url: "https://explorer.arena-z.gg", 372 | chainId: 7897, 373 | }, 374 | iotexMain: { 375 | url: "https://iotexscan.io", 376 | chainId: 4689, 377 | }, 378 | hychainMain: { 379 | url: "https://explorer.hychain.com", 380 | chainId: 2911, 381 | }, 382 | zircuitMain: { 383 | url: "https://explorer.zircuit.com", 384 | chainId: 48900, 385 | }, 386 | bitlayerMain: { 387 | url: "https://www.btrscan.com", 388 | chainId: 200901, 389 | }, 390 | roninMain: { 391 | url: "https://app.roninchain.com", 392 | chainId: 2020, 393 | }, 394 | zkSyncMain: { 395 | url: "https://era.zksync.network", 396 | chainId: 324, 397 | }, 398 | immutableZkEVMMain: { 399 | url: "https://explorer.immutable.com", 400 | chainId: 13371, 401 | }, 402 | abstractMain: { 403 | url: "https://abscan.org", 404 | chainId: 2741, 405 | }, 406 | hyperevmMain: { 407 | url: "https://purrsec.com", 408 | chainId: 999, 409 | }, 410 | kaiaMain: { 411 | url: "https://kaiascope.com", 412 | chainId: 8217, 413 | }, 414 | apeChainMain: { 415 | url: "https://apescan.io", 416 | chainId: 33139, 417 | }, 418 | } as const; 419 | 420 | // Mapping of Sei networks to their chain query identifiers required for constructing the 421 | // correct Seitrace URLs 422 | export const seitraceMap = { 423 | seiArcticDevnet: "arctic-1", 424 | seiAtlanticTestnet: "atlantic-2", 425 | seiMain: "pacific-1", 426 | } as const; 427 | 428 | // Define a type `SupportedSeitraceNetwork` that represents the keys from `seitraceMap`, representing 429 | // the supported Seitrace networks. 430 | export type SupportedSeitraceNetwork = keyof typeof seitraceMap; 431 | 432 | // Define a type `SupportedNetwork` that represents the union of all possible network names 433 | // from the `networksInfo` object. This type ensures that any value assigned to a variable 434 | // of type `SupportedNetwork` is a valid network key in `networksInfo`. It provides 435 | // compile-time safety by preventing the use of unsupported or misspelled network names, 436 | // allowing us to catch potential errors early and ensure consistency when working with 437 | // network-specific functions or data 438 | export type SupportedNetwork = keyof typeof networksInfo; 439 | 440 | // Extract the network keys as an array of strings for use in the main plugin script 441 | export const networks = Object.keys(networksInfo) as SupportedNetwork[]; 442 | 443 | // Generate the transaction hash link 444 | export const getTxHashLink = (network: SupportedNetwork, hash: string) => { 445 | const baseUrl = new URL(networksInfo[network].url).origin; 446 | return network.startsWith("filecoin") 447 | ? `${baseUrl}/message/${hash}` 448 | : network.startsWith("sei") 449 | ? `${baseUrl}/tx/${hash}?chain=${seitraceMap[network as SupportedSeitraceNetwork]}` 450 | : `${baseUrl}/tx/${hash}`; 451 | }; 452 | 453 | // Generate the contract address link 454 | export const getAddressLink = (network: SupportedNetwork, address: string) => { 455 | const baseUrl = new URL(networksInfo[network].url).origin; 456 | return `${baseUrl}/${network.startsWith("sei") || network.startsWith("kaia") ? "account" : "address"}/${address}${ 457 | network.startsWith("sei") 458 | ? `?chain=${seitraceMap[network as SupportedSeitraceNetwork]}` 459 | : "" 460 | }`; 461 | }; 462 | -------------------------------------------------------------------------------- /src/utils/table.ts: -------------------------------------------------------------------------------- 1 | import { RESET, BRIGHT, GREEN, YELLOW } from "./colour-codes"; 2 | 3 | export function createNetworkInfoTable( 4 | networksInfo: Record, 5 | ) { 6 | const networks = Object.entries(networksInfo); 7 | 8 | // Calculate column widths 9 | const networkWidth = Math.max( 10 | ...networks.map(([name]) => name.length), 11 | "Network Name".length, 12 | ); 13 | const chainIdWidth = Math.max( 14 | ...networks.map(([, info]) => info.chainId?.toString().length ?? 0), 15 | "Chain ID".length, 16 | ); 17 | const urlWidth = Math.max( 18 | ...networks.map(([, info]) => info.url.length), 19 | "Block Explorer URL".length, 20 | ); 21 | 22 | // Create table border 23 | const horizontalBorder = `${BRIGHT}+${"-".repeat(networkWidth + 2)}+${"-".repeat(chainIdWidth + 2)}+${"-".repeat(urlWidth + 2)}+${RESET}`; 24 | 25 | // Create table header 26 | const header = [ 27 | `${GREEN}${"Network Name".padEnd(networkWidth)}${RESET}`, 28 | `${GREEN}${"Chain ID".padEnd(chainIdWidth)}${RESET}`, 29 | `${GREEN}${"Block Explorer URL".padEnd(urlWidth)}${RESET}`, 30 | ]; 31 | 32 | // Print colourful title 33 | console.log(`\n${YELLOW}Supported Networks${RESET}\n`); 34 | 35 | // Print table 36 | console.log(horizontalBorder); 37 | console.log( 38 | `${BRIGHT}|${RESET} ${header.join(` ${BRIGHT}|${RESET} `)} ${BRIGHT}|${RESET}`, 39 | ); 40 | console.log(horizontalBorder); 41 | 42 | // Create table rows 43 | networks.forEach(([network, info]) => { 44 | const rowColor = RESET; 45 | const row = [ 46 | `${rowColor}${network.padEnd(networkWidth)}${RESET}`, 47 | `${rowColor}${(info.chainId?.toString() ?? "N/A").padEnd(chainIdWidth)}${RESET}`, 48 | `${rowColor}${info.url.padEnd(urlWidth)}${RESET}`, 49 | ]; 50 | console.log( 51 | `${BRIGHT}|${RESET} ${row.join(` ${BRIGHT}|${RESET} `)} ${BRIGHT}|${RESET}`, 52 | ); 53 | console.log(horizontalBorder); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | artifacts 3 | deployments 4 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/contracts/Create2DeployerLocal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {CreateX} from "./CreateX.sol"; 5 | 6 | contract Create2DeployerLocal is CreateX {} 7 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/contracts/ERC20Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/token/ERC20Mock.sol. 3 | pragma solidity ^0.8.23; 4 | 5 | import {ERC20} from "./imported/ERC20.sol"; 6 | 7 | // Mock class using ERC20 8 | contract ERC20Mock is ERC20 { 9 | constructor( 10 | string memory name_, 11 | string memory symbol_, 12 | address initialAccount_, 13 | uint256 initialBalance_ 14 | ) payable ERC20(name_, symbol_) { 15 | _mint(initialAccount_, initialBalance_); 16 | } 17 | 18 | function mint(address account, uint256 amount) external { 19 | _mint(account, amount); 20 | } 21 | 22 | function burn(address account, uint256 amount) external { 23 | _burn(account, amount); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/contracts/imported/Context.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Context.sol. 3 | pragma solidity ^0.8.23; 4 | 5 | /** 6 | * @dev Provides information about the current execution context, including the 7 | * sender of the transaction and its data. While these are generally available 8 | * via msg.sender and msg.data, they should not be accessed in such a direct 9 | * manner, since when dealing with meta-transactions the account sending and 10 | * paying for execution may not be the actual sender (as far as an application 11 | * is concerned). 12 | * 13 | * This contract is only required for intermediate, library-like contracts. 14 | */ 15 | abstract contract Context { 16 | function _msgSender() internal view virtual returns (address) { 17 | return msg.sender; 18 | } 19 | 20 | function _msgData() internal view virtual returns (bytes calldata) { 21 | return msg.data; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/contracts/imported/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol. 3 | pragma solidity ^0.8.23; 4 | 5 | import {IERC20} from "./IERC20.sol"; 6 | import {IERC20Metadata} from "./IERC20Metadata.sol"; 7 | import {Context} from "./Context.sol"; 8 | 9 | /** 10 | * @dev Implementation of the {IERC20} interface. 11 | * 12 | * This implementation is agnostic to the way tokens are created. This means 13 | * that a supply mechanism has to be added in a derived contract using {_mint}. 14 | * For a generic mechanism see {ERC20PresetMinterPauser}. 15 | * 16 | * TIP: For a detailed writeup see our guide 17 | * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How 18 | * to implement supply mechanisms]. 19 | * 20 | * The default value of {decimals} is 18. To change this, you should override 21 | * this function so it returns a different value. 22 | * 23 | * We have followed general OpenZeppelin Contracts guidelines: functions revert 24 | * instead returning `false` on failure. This behavior is nonetheless 25 | * conventional and does not conflict with the expectations of ERC20 26 | * applications. 27 | * 28 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 29 | * This allows applications to reconstruct the allowance for all accounts just 30 | * by listening to said events. Other implementations of the EIP may not emit 31 | * these events, as it isn't required by the specification. 32 | * 33 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} 34 | * functions have been added to mitigate the well-known issues around setting 35 | * allowances. See {IERC20-approve}. 36 | */ 37 | contract ERC20 is Context, IERC20, IERC20Metadata { 38 | mapping(address => uint256) private _balances; 39 | 40 | mapping(address => mapping(address => uint256)) private _allowances; 41 | 42 | uint256 private _totalSupply; 43 | 44 | string private _name; 45 | string private _symbol; 46 | 47 | /** 48 | * @dev Sets the values for {name} and {symbol}. 49 | * 50 | * All two of these values are immutable: they can only be set once during 51 | * construction. 52 | */ 53 | constructor(string memory name_, string memory symbol_) { 54 | _name = name_; 55 | _symbol = symbol_; 56 | } 57 | 58 | /** 59 | * @dev Returns the name of the token. 60 | */ 61 | function name() public view virtual override returns (string memory) { 62 | return _name; 63 | } 64 | 65 | /** 66 | * @dev Returns the symbol of the token, usually a shorter version of the 67 | * name. 68 | */ 69 | function symbol() public view virtual override returns (string memory) { 70 | return _symbol; 71 | } 72 | 73 | /** 74 | * @dev Returns the number of decimals used to get its user representation. 75 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 76 | * be displayed to a user as `5.05` (`505 / 10 ** 2`). 77 | * 78 | * Tokens usually opt for a value of 18, imitating the relationship between 79 | * Ether and Wei. This is the default value returned by this function, unless 80 | * it's overridden. 81 | * 82 | * NOTE: This information is only used for _display_ purposes: it in 83 | * no way affects any of the arithmetic of the contract, including 84 | * {IERC20-balanceOf} and {IERC20-transfer}. 85 | */ 86 | function decimals() public view virtual override returns (uint8) { 87 | return 18; 88 | } 89 | 90 | /** 91 | * @dev See {IERC20-totalSupply}. 92 | */ 93 | function totalSupply() public view virtual override returns (uint256) { 94 | return _totalSupply; 95 | } 96 | 97 | /** 98 | * @dev See {IERC20-balanceOf}. 99 | */ 100 | function balanceOf(address account) public view virtual override returns (uint256) { 101 | return _balances[account]; 102 | } 103 | 104 | /** 105 | * @dev See {IERC20-transfer}. 106 | * 107 | * Requirements: 108 | * 109 | * - `to` cannot be the zero address. 110 | * - the caller must have a balance of at least `amount`. 111 | */ 112 | function transfer(address to, uint256 amount) public virtual override returns (bool) { 113 | address owner = _msgSender(); 114 | _transfer(owner, to, amount); 115 | return true; 116 | } 117 | 118 | /** 119 | * @dev See {IERC20-allowance}. 120 | */ 121 | function allowance(address owner, address spender) public view virtual override returns (uint256) { 122 | return _allowances[owner][spender]; 123 | } 124 | 125 | /** 126 | * @dev See {IERC20-approve}. 127 | * 128 | * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on 129 | * `transferFrom`. This is semantically equivalent to an infinite approval. 130 | * 131 | * Requirements: 132 | * 133 | * - `spender` cannot be the zero address. 134 | */ 135 | function approve(address spender, uint256 amount) public virtual override returns (bool) { 136 | address owner = _msgSender(); 137 | _approve(owner, spender, amount); 138 | return true; 139 | } 140 | 141 | /** 142 | * @dev See {IERC20-transferFrom}. 143 | * 144 | * Emits an {Approval} event indicating the updated allowance. This is not 145 | * required by the EIP. See the note at the beginning of {ERC20}. 146 | * 147 | * NOTE: Does not update the allowance if the current allowance 148 | * is the maximum `uint256`. 149 | * 150 | * Requirements: 151 | * 152 | * - `from` and `to` cannot be the zero address. 153 | * - `from` must have a balance of at least `amount`. 154 | * - the caller must have allowance for ``from``'s tokens of at least 155 | * `amount`. 156 | */ 157 | function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { 158 | address spender = _msgSender(); 159 | _spendAllowance(from, spender, amount); 160 | _transfer(from, to, amount); 161 | return true; 162 | } 163 | 164 | /** 165 | * @dev Atomically increases the allowance granted to `spender` by the caller. 166 | * 167 | * This is an alternative to {approve} that can be used as a mitigation for 168 | * problems described in {IERC20-approve}. 169 | * 170 | * Emits an {Approval} event indicating the updated allowance. 171 | * 172 | * Requirements: 173 | * 174 | * - `spender` cannot be the zero address. 175 | */ 176 | function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { 177 | address owner = _msgSender(); 178 | _approve(owner, spender, allowance(owner, spender) + addedValue); 179 | return true; 180 | } 181 | 182 | /** 183 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 184 | * 185 | * This is an alternative to {approve} that can be used as a mitigation for 186 | * problems described in {IERC20-approve}. 187 | * 188 | * Emits an {Approval} event indicating the updated allowance. 189 | * 190 | * Requirements: 191 | * 192 | * - `spender` cannot be the zero address. 193 | * - `spender` must have allowance for the caller of at least 194 | * `subtractedValue`. 195 | */ 196 | function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { 197 | address owner = _msgSender(); 198 | uint256 currentAllowance = allowance(owner, spender); 199 | require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); 200 | unchecked { 201 | _approve(owner, spender, currentAllowance - subtractedValue); 202 | } 203 | 204 | return true; 205 | } 206 | 207 | /** 208 | * @dev Moves `amount` of tokens from `from` to `to`. 209 | * 210 | * This internal function is equivalent to {transfer}, and can be used to 211 | * e.g. implement automatic token fees, slashing mechanisms, etc. 212 | * 213 | * Emits a {Transfer} event. 214 | * 215 | * Requirements: 216 | * 217 | * - `from` cannot be the zero address. 218 | * - `to` cannot be the zero address. 219 | * - `from` must have a balance of at least `amount`. 220 | */ 221 | function _transfer(address from, address to, uint256 amount) internal virtual { 222 | require(from != address(0), "ERC20: transfer from the zero address"); 223 | require(to != address(0), "ERC20: transfer to the zero address"); 224 | 225 | _beforeTokenTransfer(from, to, amount); 226 | 227 | uint256 fromBalance = _balances[from]; 228 | require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); 229 | unchecked { 230 | _balances[from] = fromBalance - amount; 231 | // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by 232 | // decrementing then incrementing. 233 | _balances[to] += amount; 234 | } 235 | 236 | emit Transfer(from, to, amount); 237 | 238 | _afterTokenTransfer(from, to, amount); 239 | } 240 | 241 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 242 | * the total supply. 243 | * 244 | * Emits a {Transfer} event with `from` set to the zero address. 245 | * 246 | * Requirements: 247 | * 248 | * - `account` cannot be the zero address. 249 | */ 250 | function _mint(address account, uint256 amount) internal virtual { 251 | require(account != address(0), "ERC20: mint to the zero address"); 252 | 253 | _beforeTokenTransfer(address(0), account, amount); 254 | 255 | _totalSupply += amount; 256 | unchecked { 257 | // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. 258 | _balances[account] += amount; 259 | } 260 | emit Transfer(address(0), account, amount); 261 | 262 | _afterTokenTransfer(address(0), account, amount); 263 | } 264 | 265 | /** 266 | * @dev Destroys `amount` tokens from `account`, reducing the 267 | * total supply. 268 | * 269 | * Emits a {Transfer} event with `to` set to the zero address. 270 | * 271 | * Requirements: 272 | * 273 | * - `account` cannot be the zero address. 274 | * - `account` must have at least `amount` tokens. 275 | */ 276 | function _burn(address account, uint256 amount) internal virtual { 277 | require(account != address(0), "ERC20: burn from the zero address"); 278 | 279 | _beforeTokenTransfer(account, address(0), amount); 280 | 281 | uint256 accountBalance = _balances[account]; 282 | require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); 283 | unchecked { 284 | _balances[account] = accountBalance - amount; 285 | // Overflow not possible: amount <= accountBalance <= totalSupply. 286 | _totalSupply -= amount; 287 | } 288 | 289 | emit Transfer(account, address(0), amount); 290 | 291 | _afterTokenTransfer(account, address(0), amount); 292 | } 293 | 294 | /** 295 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. 296 | * 297 | * This internal function is equivalent to `approve`, and can be used to 298 | * e.g. set automatic allowances for certain subsystems, etc. 299 | * 300 | * Emits an {Approval} event. 301 | * 302 | * Requirements: 303 | * 304 | * - `owner` cannot be the zero address. 305 | * - `spender` cannot be the zero address. 306 | */ 307 | function _approve(address owner, address spender, uint256 amount) internal virtual { 308 | require(owner != address(0), "ERC20: approve from the zero address"); 309 | require(spender != address(0), "ERC20: approve to the zero address"); 310 | 311 | _allowances[owner][spender] = amount; 312 | emit Approval(owner, spender, amount); 313 | } 314 | 315 | /** 316 | * @dev Updates `owner` s allowance for `spender` based on spent `amount`. 317 | * 318 | * Does not update the allowance amount in case of infinite allowance. 319 | * Revert if not enough allowance is available. 320 | * 321 | * Might emit an {Approval} event. 322 | */ 323 | function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { 324 | uint256 currentAllowance = allowance(owner, spender); 325 | if (currentAllowance != type(uint256).max) { 326 | require(currentAllowance >= amount, "ERC20: insufficient allowance"); 327 | unchecked { 328 | _approve(owner, spender, currentAllowance - amount); 329 | } 330 | } 331 | } 332 | 333 | /** 334 | * @dev Hook that is called before any transfer of tokens. This includes 335 | * minting and burning. 336 | * 337 | * Calling conditions: 338 | * 339 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 340 | * will be transferred to `to`. 341 | * - when `from` is zero, `amount` tokens will be minted for `to`. 342 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 343 | * - `from` and `to` are never both zero. 344 | * 345 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 346 | */ 347 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} 348 | 349 | /** 350 | * @dev Hook that is called after any transfer of tokens. This includes 351 | * minting and burning. 352 | * 353 | * Calling conditions: 354 | * 355 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 356 | * has been transferred to `to`. 357 | * - when `from` is zero, `amount` tokens have been minted for `to`. 358 | * - when `to` is zero, `amount` of ``from``'s tokens have been burned. 359 | * - `from` and `to` are never both zero. 360 | * 361 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 362 | */ 363 | function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} 364 | } 365 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/contracts/imported/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol 3 | pragma solidity ^0.8.23; 4 | 5 | /** 6 | * @dev Interface of the ERC20 standard as defined in the EIP. 7 | */ 8 | interface IERC20 { 9 | /** 10 | * @dev Returns the amount of tokens in existence. 11 | */ 12 | function totalSupply() external view returns (uint256); 13 | 14 | /** 15 | * @dev Returns the amount of tokens owned by `account`. 16 | */ 17 | function balanceOf(address account) external view returns (uint256); 18 | 19 | /** 20 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 21 | * 22 | * Returns a boolean value indicating whether the operation succeeded. 23 | * 24 | * Emits a {Transfer} event. 25 | */ 26 | function transfer(address recipient, uint256 amount) external returns (bool); 27 | 28 | /** 29 | * @dev Returns the remaining number of tokens that `spender` will be 30 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 31 | * zero by default. 32 | * 33 | * This value changes when {approve} or {transferFrom} are called. 34 | */ 35 | function allowance(address owner, address spender) external view returns (uint256); 36 | 37 | /** 38 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 39 | * 40 | * Returns a boolean value indicating whether the operation succeeded. 41 | * 42 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 43 | * that someone may use both the old and the new allowance by unfortunate 44 | * transaction ordering. One possible solution to mitigate this race 45 | * condition is to first reduce the spender's allowance to 0 and set the 46 | * desired value afterwards: 47 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 48 | * 49 | * Emits an {Approval} event. 50 | */ 51 | function approve(address spender, uint256 amount) external returns (bool); 52 | 53 | /** 54 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 55 | * allowance mechanism. `amount` is then deducted from the caller's 56 | * allowance. 57 | * 58 | * Returns a boolean value indicating whether the operation succeeded. 59 | * 60 | * Emits a {Transfer} event. 61 | */ 62 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 63 | 64 | /** 65 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 66 | * another (`to`). 67 | * 68 | * Note that `value` may be zero. 69 | */ 70 | event Transfer(address indexed from, address indexed to, uint256 value); 71 | 72 | /** 73 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 74 | * a call to {approve}. `value` is the new allowance. 75 | */ 76 | event Approval(address indexed owner, address indexed spender, uint256 value); 77 | } 78 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/contracts/imported/IERC20Metadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol. 3 | pragma solidity ^0.8.23; 4 | 5 | import {IERC20} from "./IERC20.sol"; 6 | 7 | /** 8 | * @dev Interface for the optional metadata functions from the ERC20 standard. 9 | * 10 | * _Available since v4.1._ 11 | */ 12 | interface IERC20Metadata is IERC20 { 13 | /** 14 | * @dev Returns the name of the token. 15 | */ 16 | function name() external view returns (string memory); 17 | 18 | /** 19 | * @dev Returns the symbol of the token. 20 | */ 21 | function symbol() external view returns (string memory); 22 | 23 | /** 24 | * @dev Returns the decimals places of the token. 25 | */ 26 | function decimals() external view returns (uint8); 27 | } 28 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/deploy-args.ts: -------------------------------------------------------------------------------- 1 | const data = [ 2 | "MyToken", 3 | "MTKN", 4 | "0x9F3f11d72d96910df008Cfe3aBA40F361D2EED03", 5 | 100_000_000_000_000_000_000n, 6 | ]; 7 | export { data }; 8 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-with-constructor/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/types"; 2 | import { vars } from "hardhat/config"; 3 | import "../../../src/index"; 4 | import { ethers } from "ethers"; 5 | 6 | const config: HardhatUserConfig = { 7 | solidity: { 8 | version: "0.8.30", 9 | settings: { 10 | optimizer: { 11 | enabled: true, 12 | runs: 999_999, 13 | }, 14 | evmVersion: "paris", // Prevent using the `PUSH0` and `cancun` opcodes. 15 | }, 16 | }, 17 | networks: { 18 | hardhat: { 19 | accounts: [ 20 | { 21 | privateKey: vars.get( 22 | "PRIVATE_KEY", 23 | // `If there is no `PRIVATE_KEY` entry, we use one of the standard accounts of the Hardhat network. 24 | "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", 25 | ), 26 | balance: "100000000000000000000", 27 | }, 28 | ], 29 | hardfork: "cancun", 30 | }, 31 | }, 32 | defaultNetwork: "hardhat", 33 | xdeploy: { 34 | contract: "ERC20Mock", 35 | constructorArgsPath: "./deploy-args.ts", 36 | salt: ethers.id(Date.now().toString()), 37 | signer: vars.get("XDEPLOYER_TEST_ACCOUNT", ""), 38 | networks: ["hardhat"], 39 | rpcUrls: ["hardhat"], 40 | gasLimit: 1.2 * 10 ** 6, 41 | }, 42 | }; 43 | 44 | export default config; 45 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-without-constructor/.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | artifacts 3 | deployments 4 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-without-constructor/contracts/Create2DeployerLocal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {CreateX} from "./CreateX.sol"; 5 | 6 | contract Create2DeployerLocal is CreateX {} 7 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-without-constructor/contracts/SimpleContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.23; 3 | 4 | contract SimpleContract { 5 | string public mark = "WAGMI"; 6 | 7 | constructor() {} 8 | } 9 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project-without-constructor/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/types"; 2 | import { vars } from "hardhat/config"; 3 | import "../../../src/index"; 4 | import { ethers } from "ethers"; 5 | 6 | const config: HardhatUserConfig = { 7 | solidity: { 8 | version: "0.8.30", 9 | settings: { 10 | optimizer: { 11 | enabled: true, 12 | runs: 999_999, 13 | }, 14 | evmVersion: "paris", // Prevent using the `PUSH0` and `cancun` opcodes. 15 | }, 16 | }, 17 | networks: { 18 | hardhat: { 19 | accounts: [ 20 | { 21 | privateKey: vars.get( 22 | "PRIVATE_KEY", 23 | // `If there is no `PRIVATE_KEY` entry, we use one of the standard accounts of the Hardhat network. 24 | "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", 25 | ), 26 | balance: "100000000000000000000", 27 | }, 28 | ], 29 | hardfork: "cancun", 30 | }, 31 | }, 32 | defaultNetwork: "hardhat", 33 | xdeploy: { 34 | contract: "SimpleContract", 35 | salt: ethers.id(Date.now().toString()), 36 | signer: vars.get("XDEPLOYER_TEST_ACCOUNT", ""), 37 | networks: ["hardhat"], 38 | rpcUrls: ["hardhat"], 39 | gasLimit: 1.2 * 10 ** 6, 40 | }, 41 | }; 42 | 43 | export default config; 44 | -------------------------------------------------------------------------------- /test/fixture-projects/sepolia-holesky-project-with-constructor/.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | artifacts 3 | deployments 4 | -------------------------------------------------------------------------------- /test/fixture-projects/sepolia-holesky-project-with-constructor/contracts/ERC20Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/token/ERC20Mock.sol. 3 | pragma solidity ^0.8.23; 4 | 5 | import {ERC20} from "./imported/ERC20.sol"; 6 | 7 | // Mock class using ERC20 8 | contract ERC20Mock is ERC20 { 9 | constructor( 10 | string memory name_, 11 | string memory symbol_, 12 | address initialAccount_, 13 | uint256 initialBalance_ 14 | ) payable ERC20(name_, symbol_) { 15 | _mint(initialAccount_, initialBalance_); 16 | } 17 | 18 | function mint(address account, uint256 amount) external { 19 | _mint(account, amount); 20 | } 21 | 22 | function burn(address account, uint256 amount) external { 23 | _burn(account, amount); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/fixture-projects/sepolia-holesky-project-with-constructor/contracts/imported/Context.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Context.sol. 3 | pragma solidity ^0.8.23; 4 | 5 | /** 6 | * @dev Provides information about the current execution context, including the 7 | * sender of the transaction and its data. While these are generally available 8 | * via msg.sender and msg.data, they should not be accessed in such a direct 9 | * manner, since when dealing with meta-transactions the account sending and 10 | * paying for execution may not be the actual sender (as far as an application 11 | * is concerned). 12 | * 13 | * This contract is only required for intermediate, library-like contracts. 14 | */ 15 | abstract contract Context { 16 | function _msgSender() internal view virtual returns (address) { 17 | return msg.sender; 18 | } 19 | 20 | function _msgData() internal view virtual returns (bytes calldata) { 21 | return msg.data; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/fixture-projects/sepolia-holesky-project-with-constructor/contracts/imported/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol. 3 | pragma solidity ^0.8.23; 4 | 5 | import {IERC20} from "./IERC20.sol"; 6 | import {IERC20Metadata} from "./IERC20Metadata.sol"; 7 | import {Context} from "./Context.sol"; 8 | 9 | /** 10 | * @dev Implementation of the {IERC20} interface. 11 | * 12 | * This implementation is agnostic to the way tokens are created. This means 13 | * that a supply mechanism has to be added in a derived contract using {_mint}. 14 | * For a generic mechanism see {ERC20PresetMinterPauser}. 15 | * 16 | * TIP: For a detailed writeup see our guide 17 | * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How 18 | * to implement supply mechanisms]. 19 | * 20 | * The default value of {decimals} is 18. To change this, you should override 21 | * this function so it returns a different value. 22 | * 23 | * We have followed general OpenZeppelin Contracts guidelines: functions revert 24 | * instead returning `false` on failure. This behavior is nonetheless 25 | * conventional and does not conflict with the expectations of ERC20 26 | * applications. 27 | * 28 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 29 | * This allows applications to reconstruct the allowance for all accounts just 30 | * by listening to said events. Other implementations of the EIP may not emit 31 | * these events, as it isn't required by the specification. 32 | * 33 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} 34 | * functions have been added to mitigate the well-known issues around setting 35 | * allowances. See {IERC20-approve}. 36 | */ 37 | contract ERC20 is Context, IERC20, IERC20Metadata { 38 | mapping(address => uint256) private _balances; 39 | 40 | mapping(address => mapping(address => uint256)) private _allowances; 41 | 42 | uint256 private _totalSupply; 43 | 44 | string private _name; 45 | string private _symbol; 46 | 47 | /** 48 | * @dev Sets the values for {name} and {symbol}. 49 | * 50 | * All two of these values are immutable: they can only be set once during 51 | * construction. 52 | */ 53 | constructor(string memory name_, string memory symbol_) { 54 | _name = name_; 55 | _symbol = symbol_; 56 | } 57 | 58 | /** 59 | * @dev Returns the name of the token. 60 | */ 61 | function name() public view virtual override returns (string memory) { 62 | return _name; 63 | } 64 | 65 | /** 66 | * @dev Returns the symbol of the token, usually a shorter version of the 67 | * name. 68 | */ 69 | function symbol() public view virtual override returns (string memory) { 70 | return _symbol; 71 | } 72 | 73 | /** 74 | * @dev Returns the number of decimals used to get its user representation. 75 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 76 | * be displayed to a user as `5.05` (`505 / 10 ** 2`). 77 | * 78 | * Tokens usually opt for a value of 18, imitating the relationship between 79 | * Ether and Wei. This is the default value returned by this function, unless 80 | * it's overridden. 81 | * 82 | * NOTE: This information is only used for _display_ purposes: it in 83 | * no way affects any of the arithmetic of the contract, including 84 | * {IERC20-balanceOf} and {IERC20-transfer}. 85 | */ 86 | function decimals() public view virtual override returns (uint8) { 87 | return 18; 88 | } 89 | 90 | /** 91 | * @dev See {IERC20-totalSupply}. 92 | */ 93 | function totalSupply() public view virtual override returns (uint256) { 94 | return _totalSupply; 95 | } 96 | 97 | /** 98 | * @dev See {IERC20-balanceOf}. 99 | */ 100 | function balanceOf(address account) public view virtual override returns (uint256) { 101 | return _balances[account]; 102 | } 103 | 104 | /** 105 | * @dev See {IERC20-transfer}. 106 | * 107 | * Requirements: 108 | * 109 | * - `to` cannot be the zero address. 110 | * - the caller must have a balance of at least `amount`. 111 | */ 112 | function transfer(address to, uint256 amount) public virtual override returns (bool) { 113 | address owner = _msgSender(); 114 | _transfer(owner, to, amount); 115 | return true; 116 | } 117 | 118 | /** 119 | * @dev See {IERC20-allowance}. 120 | */ 121 | function allowance(address owner, address spender) public view virtual override returns (uint256) { 122 | return _allowances[owner][spender]; 123 | } 124 | 125 | /** 126 | * @dev See {IERC20-approve}. 127 | * 128 | * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on 129 | * `transferFrom`. This is semantically equivalent to an infinite approval. 130 | * 131 | * Requirements: 132 | * 133 | * - `spender` cannot be the zero address. 134 | */ 135 | function approve(address spender, uint256 amount) public virtual override returns (bool) { 136 | address owner = _msgSender(); 137 | _approve(owner, spender, amount); 138 | return true; 139 | } 140 | 141 | /** 142 | * @dev See {IERC20-transferFrom}. 143 | * 144 | * Emits an {Approval} event indicating the updated allowance. This is not 145 | * required by the EIP. See the note at the beginning of {ERC20}. 146 | * 147 | * NOTE: Does not update the allowance if the current allowance 148 | * is the maximum `uint256`. 149 | * 150 | * Requirements: 151 | * 152 | * - `from` and `to` cannot be the zero address. 153 | * - `from` must have a balance of at least `amount`. 154 | * - the caller must have allowance for ``from``'s tokens of at least 155 | * `amount`. 156 | */ 157 | function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { 158 | address spender = _msgSender(); 159 | _spendAllowance(from, spender, amount); 160 | _transfer(from, to, amount); 161 | return true; 162 | } 163 | 164 | /** 165 | * @dev Atomically increases the allowance granted to `spender` by the caller. 166 | * 167 | * This is an alternative to {approve} that can be used as a mitigation for 168 | * problems described in {IERC20-approve}. 169 | * 170 | * Emits an {Approval} event indicating the updated allowance. 171 | * 172 | * Requirements: 173 | * 174 | * - `spender` cannot be the zero address. 175 | */ 176 | function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { 177 | address owner = _msgSender(); 178 | _approve(owner, spender, allowance(owner, spender) + addedValue); 179 | return true; 180 | } 181 | 182 | /** 183 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 184 | * 185 | * This is an alternative to {approve} that can be used as a mitigation for 186 | * problems described in {IERC20-approve}. 187 | * 188 | * Emits an {Approval} event indicating the updated allowance. 189 | * 190 | * Requirements: 191 | * 192 | * - `spender` cannot be the zero address. 193 | * - `spender` must have allowance for the caller of at least 194 | * `subtractedValue`. 195 | */ 196 | function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { 197 | address owner = _msgSender(); 198 | uint256 currentAllowance = allowance(owner, spender); 199 | require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); 200 | unchecked { 201 | _approve(owner, spender, currentAllowance - subtractedValue); 202 | } 203 | 204 | return true; 205 | } 206 | 207 | /** 208 | * @dev Moves `amount` of tokens from `from` to `to`. 209 | * 210 | * This internal function is equivalent to {transfer}, and can be used to 211 | * e.g. implement automatic token fees, slashing mechanisms, etc. 212 | * 213 | * Emits a {Transfer} event. 214 | * 215 | * Requirements: 216 | * 217 | * - `from` cannot be the zero address. 218 | * - `to` cannot be the zero address. 219 | * - `from` must have a balance of at least `amount`. 220 | */ 221 | function _transfer(address from, address to, uint256 amount) internal virtual { 222 | require(from != address(0), "ERC20: transfer from the zero address"); 223 | require(to != address(0), "ERC20: transfer to the zero address"); 224 | 225 | _beforeTokenTransfer(from, to, amount); 226 | 227 | uint256 fromBalance = _balances[from]; 228 | require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); 229 | unchecked { 230 | _balances[from] = fromBalance - amount; 231 | // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by 232 | // decrementing then incrementing. 233 | _balances[to] += amount; 234 | } 235 | 236 | emit Transfer(from, to, amount); 237 | 238 | _afterTokenTransfer(from, to, amount); 239 | } 240 | 241 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 242 | * the total supply. 243 | * 244 | * Emits a {Transfer} event with `from` set to the zero address. 245 | * 246 | * Requirements: 247 | * 248 | * - `account` cannot be the zero address. 249 | */ 250 | function _mint(address account, uint256 amount) internal virtual { 251 | require(account != address(0), "ERC20: mint to the zero address"); 252 | 253 | _beforeTokenTransfer(address(0), account, amount); 254 | 255 | _totalSupply += amount; 256 | unchecked { 257 | // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. 258 | _balances[account] += amount; 259 | } 260 | emit Transfer(address(0), account, amount); 261 | 262 | _afterTokenTransfer(address(0), account, amount); 263 | } 264 | 265 | /** 266 | * @dev Destroys `amount` tokens from `account`, reducing the 267 | * total supply. 268 | * 269 | * Emits a {Transfer} event with `to` set to the zero address. 270 | * 271 | * Requirements: 272 | * 273 | * - `account` cannot be the zero address. 274 | * - `account` must have at least `amount` tokens. 275 | */ 276 | function _burn(address account, uint256 amount) internal virtual { 277 | require(account != address(0), "ERC20: burn from the zero address"); 278 | 279 | _beforeTokenTransfer(account, address(0), amount); 280 | 281 | uint256 accountBalance = _balances[account]; 282 | require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); 283 | unchecked { 284 | _balances[account] = accountBalance - amount; 285 | // Overflow not possible: amount <= accountBalance <= totalSupply. 286 | _totalSupply -= amount; 287 | } 288 | 289 | emit Transfer(account, address(0), amount); 290 | 291 | _afterTokenTransfer(account, address(0), amount); 292 | } 293 | 294 | /** 295 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. 296 | * 297 | * This internal function is equivalent to `approve`, and can be used to 298 | * e.g. set automatic allowances for certain subsystems, etc. 299 | * 300 | * Emits an {Approval} event. 301 | * 302 | * Requirements: 303 | * 304 | * - `owner` cannot be the zero address. 305 | * - `spender` cannot be the zero address. 306 | */ 307 | function _approve(address owner, address spender, uint256 amount) internal virtual { 308 | require(owner != address(0), "ERC20: approve from the zero address"); 309 | require(spender != address(0), "ERC20: approve to the zero address"); 310 | 311 | _allowances[owner][spender] = amount; 312 | emit Approval(owner, spender, amount); 313 | } 314 | 315 | /** 316 | * @dev Updates `owner` s allowance for `spender` based on spent `amount`. 317 | * 318 | * Does not update the allowance amount in case of infinite allowance. 319 | * Revert if not enough allowance is available. 320 | * 321 | * Might emit an {Approval} event. 322 | */ 323 | function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { 324 | uint256 currentAllowance = allowance(owner, spender); 325 | if (currentAllowance != type(uint256).max) { 326 | require(currentAllowance >= amount, "ERC20: insufficient allowance"); 327 | unchecked { 328 | _approve(owner, spender, currentAllowance - amount); 329 | } 330 | } 331 | } 332 | 333 | /** 334 | * @dev Hook that is called before any transfer of tokens. This includes 335 | * minting and burning. 336 | * 337 | * Calling conditions: 338 | * 339 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 340 | * will be transferred to `to`. 341 | * - when `from` is zero, `amount` tokens will be minted for `to`. 342 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 343 | * - `from` and `to` are never both zero. 344 | * 345 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 346 | */ 347 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} 348 | 349 | /** 350 | * @dev Hook that is called after any transfer of tokens. This includes 351 | * minting and burning. 352 | * 353 | * Calling conditions: 354 | * 355 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 356 | * has been transferred to `to`. 357 | * - when `from` is zero, `amount` tokens have been minted for `to`. 358 | * - when `to` is zero, `amount` of ``from``'s tokens have been burned. 359 | * - `from` and `to` are never both zero. 360 | * 361 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 362 | */ 363 | function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} 364 | } 365 | -------------------------------------------------------------------------------- /test/fixture-projects/sepolia-holesky-project-with-constructor/contracts/imported/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol 3 | pragma solidity ^0.8.23; 4 | 5 | /** 6 | * @dev Interface of the ERC20 standard as defined in the EIP. 7 | */ 8 | interface IERC20 { 9 | /** 10 | * @dev Returns the amount of tokens in existence. 11 | */ 12 | function totalSupply() external view returns (uint256); 13 | 14 | /** 15 | * @dev Returns the amount of tokens owned by `account`. 16 | */ 17 | function balanceOf(address account) external view returns (uint256); 18 | 19 | /** 20 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 21 | * 22 | * Returns a boolean value indicating whether the operation succeeded. 23 | * 24 | * Emits a {Transfer} event. 25 | */ 26 | function transfer(address recipient, uint256 amount) external returns (bool); 27 | 28 | /** 29 | * @dev Returns the remaining number of tokens that `spender` will be 30 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 31 | * zero by default. 32 | * 33 | * This value changes when {approve} or {transferFrom} are called. 34 | */ 35 | function allowance(address owner, address spender) external view returns (uint256); 36 | 37 | /** 38 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 39 | * 40 | * Returns a boolean value indicating whether the operation succeeded. 41 | * 42 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 43 | * that someone may use both the old and the new allowance by unfortunate 44 | * transaction ordering. One possible solution to mitigate this race 45 | * condition is to first reduce the spender's allowance to 0 and set the 46 | * desired value afterwards: 47 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 48 | * 49 | * Emits an {Approval} event. 50 | */ 51 | function approve(address spender, uint256 amount) external returns (bool); 52 | 53 | /** 54 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 55 | * allowance mechanism. `amount` is then deducted from the caller's 56 | * allowance. 57 | * 58 | * Returns a boolean value indicating whether the operation succeeded. 59 | * 60 | * Emits a {Transfer} event. 61 | */ 62 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 63 | 64 | /** 65 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 66 | * another (`to`). 67 | * 68 | * Note that `value` may be zero. 69 | */ 70 | event Transfer(address indexed from, address indexed to, uint256 value); 71 | 72 | /** 73 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 74 | * a call to {approve}. `value` is the new allowance. 75 | */ 76 | event Approval(address indexed owner, address indexed spender, uint256 value); 77 | } 78 | -------------------------------------------------------------------------------- /test/fixture-projects/sepolia-holesky-project-with-constructor/contracts/imported/IERC20Metadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol. 3 | pragma solidity ^0.8.23; 4 | 5 | import {IERC20} from "./IERC20.sol"; 6 | 7 | /** 8 | * @dev Interface for the optional metadata functions from the ERC20 standard. 9 | * 10 | * _Available since v4.1._ 11 | */ 12 | interface IERC20Metadata is IERC20 { 13 | /** 14 | * @dev Returns the name of the token. 15 | */ 16 | function name() external view returns (string memory); 17 | 18 | /** 19 | * @dev Returns the symbol of the token. 20 | */ 21 | function symbol() external view returns (string memory); 22 | 23 | /** 24 | * @dev Returns the decimals places of the token. 25 | */ 26 | function decimals() external view returns (uint8); 27 | } 28 | -------------------------------------------------------------------------------- /test/fixture-projects/sepolia-holesky-project-with-constructor/deploy-args.ts: -------------------------------------------------------------------------------- 1 | const data = [ 2 | "MyToken", 3 | "MTKN", 4 | "0x9F3f11d72d96910df008Cfe3aBA40F361D2EED03", 5 | 100_000_000_000_000_000_000n, 6 | ]; 7 | export { data }; 8 | -------------------------------------------------------------------------------- /test/fixture-projects/sepolia-holesky-project-with-constructor/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/types"; 2 | import { vars } from "hardhat/config"; 3 | import "../../../src/index"; 4 | import { ethers } from "ethers"; 5 | 6 | const config: HardhatUserConfig = { 7 | solidity: { 8 | version: "0.8.30", 9 | settings: { 10 | optimizer: { 11 | enabled: true, 12 | runs: 999_999, 13 | }, 14 | evmVersion: "paris", // Prevent using the `PUSH0` and `cancun` opcodes. 15 | }, 16 | }, 17 | networks: { 18 | hardhat: { 19 | accounts: [ 20 | { 21 | privateKey: vars.get( 22 | "PRIVATE_KEY", 23 | // `If there is no `PRIVATE_KEY` entry, we use one of the standard accounts of the Hardhat network. 24 | "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", 25 | ), 26 | balance: "100000000000000000000", 27 | }, 28 | ], 29 | hardfork: "cancun", 30 | }, 31 | }, 32 | defaultNetwork: "hardhat", 33 | xdeploy: { 34 | contract: "ERC20Mock", 35 | constructorArgsPath: "./deploy-args.ts", 36 | salt: ethers.id(Date.now().toString()), 37 | signer: vars.get("XDEPLOYER_TEST_ACCOUNT", ""), 38 | networks: ["sepolia", "holesky"], 39 | rpcUrls: [ 40 | vars.get("ETH_SEPOLIA_TESTNET_URL", "https://rpc.sepolia.org"), 41 | vars.get("ETH_HOLESKY_TESTNET_URL", "https://holesky.rpc.thirdweb.com"), 42 | ], 43 | gasLimit: 1.2 * 10 ** 6, 44 | }, 45 | }; 46 | 47 | export default config; 48 | -------------------------------------------------------------------------------- /test/helpers.ts: -------------------------------------------------------------------------------- 1 | import { resetHardhatContext } from "hardhat/plugins-testing"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import path from "path"; 4 | 5 | declare module "mocha" { 6 | interface Context { 7 | hre: HardhatRuntimeEnvironment; 8 | } 9 | } 10 | 11 | export function useEnvironment(fixtureProjectName: string) { 12 | beforeEach("Loading hardhat environment", function () { 13 | process.chdir(path.join(__dirname, "fixture-projects", fixtureProjectName)); 14 | // eslint-disable-next-line @typescript-eslint/no-require-imports 15 | this.hre = require("hardhat"); 16 | }); 17 | 18 | afterEach("Resetting hardhat", function () { 19 | resetHardhatContext(); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test/xdeploy-list-networks.test.ts: -------------------------------------------------------------------------------- 1 | import { useEnvironment } from "./helpers"; 2 | 3 | describe("Plugin test xdeploy: Print the supported networks table", function () { 4 | useEnvironment("hardhat-project-with-constructor"); 5 | it("list supported networks table successfully", async function () { 6 | return this.hre.run("xdeploy", { listNetworks: true }); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/xdeploy-sepolia-holesky-with-constructor.test.ts: -------------------------------------------------------------------------------- 1 | import { useEnvironment } from "./helpers"; 2 | import { assert, expect } from "chai"; 3 | import { NomicLabsHardhatPluginError } from "hardhat/plugins"; 4 | import { SupportedNetwork } from "./utils/networks"; 5 | 6 | describe("Plugin test xdeploy: Deploy on Sepolia and Holešky with constructor", function () { 7 | useEnvironment("sepolia-holesky-project-with-constructor"); 8 | it("calling xdeploy successfully", async function () { 9 | return this.hre.run("xdeploy"); 10 | }); 11 | 12 | it("should fail due to missing network arguments - version 1", async function () { 13 | this.hre.config.xdeploy.networks = []; 14 | return this.hre 15 | .run("xdeploy") 16 | .then(() => { 17 | assert.fail("deployment request should fail"); 18 | }) 19 | .catch((reason) => { 20 | expect(reason).to.be.an.instanceOf( 21 | NomicLabsHardhatPluginError, 22 | "missing network arguments should throw a plugin error", 23 | ); 24 | expect(reason.message) 25 | .to.be.a("string") 26 | .and.include( 27 | "Please provide at least one deployment network via the hardhat config.", 28 | ); 29 | }); 30 | }); 31 | 32 | it("should fail due to missing network arguments - version 2", async function () { 33 | this.hre.config.xdeploy.networks = undefined; 34 | return this.hre 35 | .run("xdeploy") 36 | .then(() => { 37 | assert.fail("deployment request should fail"); 38 | }) 39 | .catch((reason) => { 40 | expect(reason).to.be.an.instanceOf( 41 | NomicLabsHardhatPluginError, 42 | "missing network arguments should throw a plugin error", 43 | ); 44 | expect(reason.message) 45 | .to.be.a("string") 46 | .and.include( 47 | "Please provide at least one deployment network via the hardhat config.", 48 | ); 49 | }); 50 | }); 51 | 52 | it("should fail due to unsupported network argument", async function () { 53 | this.hre.config.xdeploy.networks = [ 54 | "hardhat", 55 | "WAGMI", 56 | ] as SupportedNetwork[]; 57 | return this.hre 58 | .run("xdeploy") 59 | .then(() => { 60 | assert.fail("deployment request should fail"); 61 | }) 62 | .catch((reason) => { 63 | expect(reason).to.be.an.instanceOf( 64 | NomicLabsHardhatPluginError, 65 | "unsupported network arguments should throw a plugin error", 66 | ); 67 | expect(reason.message) 68 | .to.be.a("string") 69 | .and.include( 70 | "You have tried to configure a network that this plugin does not yet support,", 71 | ); 72 | }); 73 | }); 74 | 75 | it("should fail due to unequal length of `networks` and `rpcUrls`", async function () { 76 | this.hre.config.xdeploy.networks = ["hardhat"]; 77 | this.hre.config.xdeploy.rpcUrls = [ 78 | "hardhat", 79 | "https://mainnet.infura.io/v3/506b137aa", 80 | ]; 81 | return this.hre 82 | .run("xdeploy") 83 | .then(() => { 84 | assert.fail("deployment request should fail"); 85 | }) 86 | .catch((reason) => { 87 | expect(reason).to.be.an.instanceOf( 88 | NomicLabsHardhatPluginError, 89 | "unequal length of `networks` and `rpcUrls` arguments should throw a plugin error", 90 | ); 91 | expect(reason.message) 92 | .to.be.a("string") 93 | .and.include( 94 | "Please ensure that both parameters have the same length", 95 | ); 96 | }); 97 | }); 98 | 99 | it("should fail due to missing salt value - version 1", async function () { 100 | this.hre.config.xdeploy.salt = undefined; 101 | return this.hre 102 | .run("xdeploy") 103 | .then(() => { 104 | assert.fail("deployment request should fail"); 105 | }) 106 | .catch((reason) => { 107 | expect(reason).to.be.an.instanceOf( 108 | NomicLabsHardhatPluginError, 109 | "missing salt value should throw a plugin error", 110 | ); 111 | expect(reason.message) 112 | .to.be.a("string") 113 | .and.include("Please provide an arbitrary value as salt."); 114 | }); 115 | }); 116 | 117 | it("should fail due to missing salt value - version 2", async function () { 118 | this.hre.config.xdeploy.salt = ""; 119 | return this.hre 120 | .run("xdeploy") 121 | .then(() => { 122 | assert.fail("deployment request should fail"); 123 | }) 124 | .catch((reason) => { 125 | expect(reason).to.be.an.instanceOf( 126 | NomicLabsHardhatPluginError, 127 | "missing salt value should throw a plugin error", 128 | ); 129 | expect(reason.message) 130 | .to.be.a("string") 131 | .and.include("Please provide an arbitrary value as salt."); 132 | }); 133 | }); 134 | 135 | it("should fail due to missing signer - version 1", async function () { 136 | this.hre.config.xdeploy.signer = undefined; 137 | return this.hre 138 | .run("xdeploy") 139 | .then(() => { 140 | assert.fail("deployment request should fail"); 141 | }) 142 | .catch((reason) => { 143 | expect(reason).to.be.an.instanceOf( 144 | NomicLabsHardhatPluginError, 145 | "missing signer value should throw a plugin error", 146 | ); 147 | expect(reason.message) 148 | .to.be.a("string") 149 | .and.include("Please provide a signer private key."); 150 | }); 151 | }); 152 | 153 | it("should fail due to missing signer - version 2", async function () { 154 | this.hre.config.xdeploy.signer = ""; 155 | return this.hre 156 | .run("xdeploy") 157 | .then(() => { 158 | assert.fail("deployment request should fail"); 159 | }) 160 | .catch((reason) => { 161 | expect(reason).to.be.an.instanceOf( 162 | NomicLabsHardhatPluginError, 163 | "missing signer value should throw a plugin error", 164 | ); 165 | expect(reason.message) 166 | .to.be.a("string") 167 | .and.include("Please provide a signer private key."); 168 | }); 169 | }); 170 | 171 | it("should fail due to missing contract - version 1", async function () { 172 | this.hre.config.xdeploy.contract = undefined; 173 | return this.hre 174 | .run("xdeploy") 175 | .then(() => { 176 | assert.fail("deployment request should fail"); 177 | }) 178 | .catch((reason) => { 179 | expect(reason).to.be.an.instanceOf( 180 | NomicLabsHardhatPluginError, 181 | "missing contract value should throw a plugin error", 182 | ); 183 | expect(reason.message) 184 | .to.be.a("string") 185 | .and.include( 186 | "Please specify the contract name of the smart contract to be deployed.", 187 | ); 188 | }); 189 | }); 190 | 191 | it("should fail due to missing contract - version 2", async function () { 192 | this.hre.config.xdeploy.contract = ""; 193 | return this.hre 194 | .run("xdeploy") 195 | .then(() => { 196 | assert.fail("deployment request should fail"); 197 | }) 198 | .catch((reason) => { 199 | expect(reason).to.be.an.instanceOf( 200 | NomicLabsHardhatPluginError, 201 | "missing contract value should throw a plugin error", 202 | ); 203 | expect(reason.message) 204 | .to.be.a("string") 205 | .and.include( 206 | "Please specify the contract name of the smart contract to be deployed.", 207 | ); 208 | }); 209 | }); 210 | 211 | it("should fail due to exceeding gasLimit", async function () { 212 | this.hre.config.xdeploy.gasLimit = 15.1 * 10 ** 6; 213 | return this.hre 214 | .run("xdeploy") 215 | .then(() => { 216 | assert.fail("deployment request should fail"); 217 | }) 218 | .catch((reason) => { 219 | expect(reason).to.be.an.instanceOf( 220 | NomicLabsHardhatPluginError, 221 | "too high gasLimit should throw a plugin error", 222 | ); 223 | expect(reason.message) 224 | .to.be.a("string") 225 | .and.include("Please specify a lower gasLimit."); 226 | }); 227 | }); 228 | 229 | it("should fail due to existing bytecode", async function () { 230 | this.hre.config.xdeploy.salt = "wagmi"; 231 | return this.hre 232 | .run("xdeploy") 233 | .then(() => { 234 | assert.fail("deployment request should fail"); 235 | }) 236 | .catch((reason) => { 237 | expect(reason).to.be.an.instanceOf( 238 | NomicLabsHardhatPluginError, 239 | "existing bytecode should throw a plugin error", 240 | ); 241 | expect(reason.message) 242 | .to.be.a("string") 243 | .and.include( 244 | "The address of the contract you want to deploy already has existing bytecode", 245 | ); 246 | }); 247 | }); 248 | }); 249 | -------------------------------------------------------------------------------- /test/xdeploy-with-constructor.test.ts: -------------------------------------------------------------------------------- 1 | import { useEnvironment } from "./helpers"; 2 | import { assert, expect } from "chai"; 3 | import { NomicLabsHardhatPluginError } from "hardhat/plugins"; 4 | import { SupportedNetwork } from "./utils/networks"; 5 | 6 | describe("Plugin test xdeploy: Deploy on Hardhat network with constructorr", function () { 7 | useEnvironment("hardhat-project-with-constructor"); 8 | it("calling xdeploy successfully", async function () { 9 | return this.hre.run("xdeploy"); 10 | }); 11 | 12 | it("should fail due to missing network arguments - version 1", async function () { 13 | this.hre.config.xdeploy.networks = []; 14 | return this.hre 15 | .run("xdeploy") 16 | .then(() => { 17 | assert.fail("deployment request should fail"); 18 | }) 19 | .catch((reason) => { 20 | expect(reason).to.be.an.instanceOf( 21 | NomicLabsHardhatPluginError, 22 | "missing network arguments should throw a plugin error", 23 | ); 24 | expect(reason.message) 25 | .to.be.a("string") 26 | .and.include( 27 | "Please provide at least one deployment network via the hardhat config.", 28 | ); 29 | }); 30 | }); 31 | 32 | it("should fail due to missing network arguments - version 2", async function () { 33 | this.hre.config.xdeploy.networks = undefined; 34 | return this.hre 35 | .run("xdeploy") 36 | .then(() => { 37 | assert.fail("deployment request should fail"); 38 | }) 39 | .catch((reason) => { 40 | expect(reason).to.be.an.instanceOf( 41 | NomicLabsHardhatPluginError, 42 | "missing network arguments should throw a plugin error", 43 | ); 44 | expect(reason.message) 45 | .to.be.a("string") 46 | .and.include( 47 | "Please provide at least one deployment network via the hardhat config.", 48 | ); 49 | }); 50 | }); 51 | 52 | it("should fail due to unsupported network argument", async function () { 53 | this.hre.config.xdeploy.networks = [ 54 | "hardhat", 55 | "WAGMI", 56 | ] as SupportedNetwork[]; 57 | return this.hre 58 | .run("xdeploy") 59 | .then(() => { 60 | assert.fail("deployment request should fail"); 61 | }) 62 | .catch((reason) => { 63 | expect(reason).to.be.an.instanceOf( 64 | NomicLabsHardhatPluginError, 65 | "unsupported network arguments should throw a plugin error", 66 | ); 67 | expect(reason.message) 68 | .to.be.a("string") 69 | .and.include( 70 | "You have tried to configure a network that this plugin does not yet support,", 71 | ); 72 | }); 73 | }); 74 | 75 | it("should fail due to unequal length of `networks` and `rpcUrls`", async function () { 76 | this.hre.config.xdeploy.networks = ["hardhat"]; 77 | this.hre.config.xdeploy.rpcUrls = [ 78 | "hardhat", 79 | "https://mainnet.infura.io/v3/506b137aa", 80 | ]; 81 | return this.hre 82 | .run("xdeploy") 83 | .then(() => { 84 | assert.fail("deployment request should fail"); 85 | }) 86 | .catch((reason) => { 87 | expect(reason).to.be.an.instanceOf( 88 | NomicLabsHardhatPluginError, 89 | "unequal length of `networks` and `rpcUrls` arguments should throw a plugin error", 90 | ); 91 | expect(reason.message) 92 | .to.be.a("string") 93 | .and.include( 94 | "Please ensure that both parameters have the same length", 95 | ); 96 | }); 97 | }); 98 | 99 | it("should fail due to missing salt value - version 1", async function () { 100 | this.hre.config.xdeploy.salt = undefined; 101 | return this.hre 102 | .run("xdeploy") 103 | .then(() => { 104 | assert.fail("deployment request should fail"); 105 | }) 106 | .catch((reason) => { 107 | expect(reason).to.be.an.instanceOf( 108 | NomicLabsHardhatPluginError, 109 | "missing salt value should throw a plugin error", 110 | ); 111 | expect(reason.message) 112 | .to.be.a("string") 113 | .and.include("Please provide an arbitrary value as salt."); 114 | }); 115 | }); 116 | 117 | it("should fail due to missing salt value - version 2", async function () { 118 | this.hre.config.xdeploy.salt = ""; 119 | return this.hre 120 | .run("xdeploy") 121 | .then(() => { 122 | assert.fail("deployment request should fail"); 123 | }) 124 | .catch((reason) => { 125 | expect(reason).to.be.an.instanceOf( 126 | NomicLabsHardhatPluginError, 127 | "missing salt value should throw a plugin error", 128 | ); 129 | expect(reason.message) 130 | .to.be.a("string") 131 | .and.include("Please provide an arbitrary value as salt."); 132 | }); 133 | }); 134 | 135 | it("should fail due to missing signer - version 1", async function () { 136 | this.hre.config.xdeploy.signer = undefined; 137 | return this.hre 138 | .run("xdeploy") 139 | .then(() => { 140 | assert.fail("deployment request should fail"); 141 | }) 142 | .catch((reason) => { 143 | expect(reason).to.be.an.instanceOf( 144 | NomicLabsHardhatPluginError, 145 | "missing signer value should throw a plugin error", 146 | ); 147 | expect(reason.message) 148 | .to.be.a("string") 149 | .and.include("Please provide a signer private key."); 150 | }); 151 | }); 152 | 153 | it("should fail due to missing signer - version 2", async function () { 154 | this.hre.config.xdeploy.signer = ""; 155 | return this.hre 156 | .run("xdeploy") 157 | .then(() => { 158 | assert.fail("deployment request should fail"); 159 | }) 160 | .catch((reason) => { 161 | expect(reason).to.be.an.instanceOf( 162 | NomicLabsHardhatPluginError, 163 | "missing signer value should throw a plugin error", 164 | ); 165 | expect(reason.message) 166 | .to.be.a("string") 167 | .and.include("Please provide a signer private key."); 168 | }); 169 | }); 170 | 171 | it("should fail due to missing contract - version 1", async function () { 172 | this.hre.config.xdeploy.contract = undefined; 173 | return this.hre 174 | .run("xdeploy") 175 | .then(() => { 176 | assert.fail("deployment request should fail"); 177 | }) 178 | .catch((reason) => { 179 | expect(reason).to.be.an.instanceOf( 180 | NomicLabsHardhatPluginError, 181 | "missing contract value should throw a plugin error", 182 | ); 183 | expect(reason.message) 184 | .to.be.a("string") 185 | .and.include( 186 | "Please specify the contract name of the smart contract to be deployed.", 187 | ); 188 | }); 189 | }); 190 | 191 | it("should fail due to missing contract - version 2", async function () { 192 | this.hre.config.xdeploy.contract = ""; 193 | return this.hre 194 | .run("xdeploy") 195 | .then(() => { 196 | assert.fail("deployment request should fail"); 197 | }) 198 | .catch((reason) => { 199 | expect(reason).to.be.an.instanceOf( 200 | NomicLabsHardhatPluginError, 201 | "missing contract value should throw a plugin error", 202 | ); 203 | expect(reason.message) 204 | .to.be.a("string") 205 | .and.include( 206 | "Please specify the contract name of the smart contract to be deployed.", 207 | ); 208 | }); 209 | }); 210 | 211 | it("should fail due to exceeding gasLimit", async function () { 212 | this.hre.config.xdeploy.gasLimit = 15.1 * 10 ** 6; 213 | return this.hre 214 | .run("xdeploy") 215 | .then(() => { 216 | assert.fail("deployment request should fail"); 217 | }) 218 | .catch((reason) => { 219 | expect(reason).to.be.an.instanceOf( 220 | NomicLabsHardhatPluginError, 221 | "too high gasLimit should throw a plugin error", 222 | ); 223 | expect(reason.message) 224 | .to.be.a("string") 225 | .and.include("Please specify a lower gasLimit."); 226 | }); 227 | }); 228 | }); 229 | -------------------------------------------------------------------------------- /test/xdeploy-without-constructor.test.ts: -------------------------------------------------------------------------------- 1 | import { useEnvironment } from "./helpers"; 2 | import { assert, expect } from "chai"; 3 | import { NomicLabsHardhatPluginError } from "hardhat/plugins"; 4 | import { SupportedNetwork } from "./utils/networks"; 5 | 6 | describe("Plugin test xdeploy: Deploy on Hardhat network without constructor", function () { 7 | useEnvironment("hardhat-project-without-constructor"); 8 | it("calling xdeploy successfully", async function () { 9 | return this.hre.run("xdeploy"); 10 | }); 11 | 12 | it("should fail due to missing network arguments - version 1", async function () { 13 | this.hre.config.xdeploy.networks = []; 14 | return this.hre 15 | .run("xdeploy") 16 | .then(() => { 17 | assert.fail("deployment request should fail"); 18 | }) 19 | .catch((reason) => { 20 | expect(reason).to.be.an.instanceOf( 21 | NomicLabsHardhatPluginError, 22 | "missing network arguments should throw a plugin error", 23 | ); 24 | expect(reason.message) 25 | .to.be.a("string") 26 | .and.include( 27 | "Please provide at least one deployment network via the hardhat config.", 28 | ); 29 | }); 30 | }); 31 | 32 | it("should fail due to missing network arguments - version 2", async function () { 33 | this.hre.config.xdeploy.networks = undefined; 34 | return this.hre 35 | .run("xdeploy") 36 | .then(() => { 37 | assert.fail("deployment request should fail"); 38 | }) 39 | .catch((reason) => { 40 | expect(reason).to.be.an.instanceOf( 41 | NomicLabsHardhatPluginError, 42 | "missing network arguments should throw a plugin error", 43 | ); 44 | expect(reason.message) 45 | .to.be.a("string") 46 | .and.include( 47 | "Please provide at least one deployment network via the hardhat config.", 48 | ); 49 | }); 50 | }); 51 | 52 | it("should fail due to unsupported network argument", async function () { 53 | this.hre.config.xdeploy.networks = [ 54 | "hardhat", 55 | "WAGMI", 56 | ] as SupportedNetwork[]; 57 | return this.hre 58 | .run("xdeploy") 59 | .then(() => { 60 | assert.fail("deployment request should fail"); 61 | }) 62 | .catch((reason) => { 63 | expect(reason).to.be.an.instanceOf( 64 | NomicLabsHardhatPluginError, 65 | "unsupported network arguments should throw a plugin error", 66 | ); 67 | expect(reason.message) 68 | .to.be.a("string") 69 | .and.include( 70 | "You have tried to configure a network that this plugin does not yet support,", 71 | ); 72 | }); 73 | }); 74 | 75 | it("should fail due to unequal length of `networks` and `rpcUrls`", async function () { 76 | this.hre.config.xdeploy.networks = ["hardhat"]; 77 | this.hre.config.xdeploy.rpcUrls = [ 78 | "hardhat", 79 | "https://mainnet.infura.io/v3/506b137aa", 80 | ]; 81 | return this.hre 82 | .run("xdeploy") 83 | .then(() => { 84 | assert.fail("deployment request should fail"); 85 | }) 86 | .catch((reason) => { 87 | expect(reason).to.be.an.instanceOf( 88 | NomicLabsHardhatPluginError, 89 | "unequal length of `networks` and `rpcUrls` arguments should throw a plugin error", 90 | ); 91 | expect(reason.message) 92 | .to.be.a("string") 93 | .and.include( 94 | "Please ensure that both parameters have the same length", 95 | ); 96 | }); 97 | }); 98 | 99 | it("should fail due to missing salt value - version 1", async function () { 100 | this.hre.config.xdeploy.salt = undefined; 101 | return this.hre 102 | .run("xdeploy") 103 | .then(() => { 104 | assert.fail("deployment request should fail"); 105 | }) 106 | .catch((reason) => { 107 | expect(reason).to.be.an.instanceOf( 108 | NomicLabsHardhatPluginError, 109 | "missing salt value should throw a plugin error", 110 | ); 111 | expect(reason.message) 112 | .to.be.a("string") 113 | .and.include("Please provide an arbitrary value as salt."); 114 | }); 115 | }); 116 | 117 | it("should fail due to missing salt value - version 2", async function () { 118 | this.hre.config.xdeploy.salt = ""; 119 | return this.hre 120 | .run("xdeploy") 121 | .then(() => { 122 | assert.fail("deployment request should fail"); 123 | }) 124 | .catch((reason) => { 125 | expect(reason).to.be.an.instanceOf( 126 | NomicLabsHardhatPluginError, 127 | "missing salt value should throw a plugin error", 128 | ); 129 | expect(reason.message) 130 | .to.be.a("string") 131 | .and.include("Please provide an arbitrary value as salt."); 132 | }); 133 | }); 134 | 135 | it("should fail due to missing signer - version 1", async function () { 136 | this.hre.config.xdeploy.signer = undefined; 137 | return this.hre 138 | .run("xdeploy") 139 | .then(() => { 140 | assert.fail("deployment request should fail"); 141 | }) 142 | .catch((reason) => { 143 | expect(reason).to.be.an.instanceOf( 144 | NomicLabsHardhatPluginError, 145 | "missing signer value should throw a plugin error", 146 | ); 147 | expect(reason.message) 148 | .to.be.a("string") 149 | .and.include("Please provide a signer private key."); 150 | }); 151 | }); 152 | 153 | it("should fail due to missing signer - version 2", async function () { 154 | this.hre.config.xdeploy.signer = ""; 155 | return this.hre 156 | .run("xdeploy") 157 | .then(() => { 158 | assert.fail("deployment request should fail"); 159 | }) 160 | .catch((reason) => { 161 | expect(reason).to.be.an.instanceOf( 162 | NomicLabsHardhatPluginError, 163 | "missing signer value should throw a plugin error", 164 | ); 165 | expect(reason.message) 166 | .to.be.a("string") 167 | .and.include("Please provide a signer private key."); 168 | }); 169 | }); 170 | 171 | it("should fail due to missing contract - version 1", async function () { 172 | this.hre.config.xdeploy.contract = undefined; 173 | return this.hre 174 | .run("xdeploy") 175 | .then(() => { 176 | assert.fail("deployment request should fail"); 177 | }) 178 | .catch((reason) => { 179 | expect(reason).to.be.an.instanceOf( 180 | NomicLabsHardhatPluginError, 181 | "missing contract value should throw a plugin error", 182 | ); 183 | expect(reason.message) 184 | .to.be.a("string") 185 | .and.include( 186 | "Please specify the contract name of the smart contract to be deployed.", 187 | ); 188 | }); 189 | }); 190 | 191 | it("should fail due to missing contract - version 2", async function () { 192 | this.hre.config.xdeploy.contract = ""; 193 | return this.hre 194 | .run("xdeploy") 195 | .then(() => { 196 | assert.fail("deployment request should fail"); 197 | }) 198 | .catch((reason) => { 199 | expect(reason).to.be.an.instanceOf( 200 | NomicLabsHardhatPluginError, 201 | "missing contract value should throw a plugin error", 202 | ); 203 | expect(reason.message) 204 | .to.be.a("string") 205 | .and.include( 206 | "Please specify the contract name of the smart contract to be deployed.", 207 | ); 208 | }); 209 | }); 210 | 211 | it("should fail due to exceeding gasLimit", async function () { 212 | this.hre.config.xdeploy.gasLimit = 15.1 * 10 ** 6; 213 | return this.hre 214 | .run("xdeploy") 215 | .then(() => { 216 | assert.fail("deployment request should fail"); 217 | }) 218 | .catch((reason) => { 219 | expect(reason).to.be.an.instanceOf( 220 | NomicLabsHardhatPluginError, 221 | "too high gasLimit should throw a plugin error", 222 | ); 223 | expect(reason.message) 224 | .to.be.a("string") 225 | .and.include("Please specify a lower gasLimit."); 226 | }); 227 | }); 228 | }); 229 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "commonjs", 5 | "allowJs": true, 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "outDir": "./dist", 9 | "declaration": true, 10 | "declarationMap": true, 11 | "sourceMap": true, 12 | "rootDirs": ["./src", "./test"], 13 | "forceConsistentCasingInFileNames": true, 14 | "skipLibCheck": true, 15 | "resolveJsonModule": true 16 | }, 17 | "include": ["./test", "./src"], 18 | "files": ["eslint.config.js"], 19 | "exclude": ["dist", "node_modules"] 20 | } 21 | --------------------------------------------------------------------------------