├── .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 | [](https://github.com/pcaversaccio/xdeployer/actions/workflows/test.yml)
4 | [](https://opensource.org/license/mit)
5 | [](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 |
--------------------------------------------------------------------------------