├── interface
├── public
│ ├── .nojekyll
│ ├── viem.png
│ ├── favicon.ico
│ ├── ethersjs.png
│ ├── solidity.png
│ ├── json.svg
│ ├── prism-dark.css
│ └── prism-light.css
├── .prettierignore
├── postcss.config.js
├── prismjs.d.ts
├── .prettierrc.yml
├── src
│ ├── lib
│ │ └── utils.ts
│ ├── components
│ │ ├── layout
│ │ │ ├── ExternalLink.tsx
│ │ │ ├── Layout.tsx
│ │ │ ├── Head.tsx
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ ├── Header.tsx
│ │ │ └── Footer.tsx
│ │ └── ui
│ │ │ ├── LoadingSpinner.tsx
│ │ │ └── Notification.tsx
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── index.tsx
│ │ ├── abi.tsx
│ │ └── deployments.tsx
│ └── styles
│ │ └── globals.css
├── README.md
├── next-seo.config.ts
├── tsconfig.json
├── .gitignore
├── LICENSE
├── next.config.mjs
├── eslint.config.js
└── package.json
├── .prettierrc.yml
├── remappings.txt
├── FUNDING.json
├── artifacts
└── src
│ ├── CreateX.sol
│ └── CreateX.dbg.json
│ └── ICreateX.sol
│ └── ICreateX.dbg.json
├── test
├── internal
│ ├── CreateX._efficientHash.tree
│ ├── CreateX._generateSalt.tree
│ ├── CreateX._requireSuccessfulContractInitialisation.tree
│ ├── CreateX._requireSuccessfulContractCreation_1Arg.tree
│ ├── CreateX._requireSuccessfulContractCreation_2Args.tree
│ ├── CreateX._efficientHash.t.sol
│ ├── CreateX._guard.tree
│ ├── CreateX._parseSalt.tree
│ ├── CreateX._requireSuccessfulContractCreation_1Arg.t.sol
│ ├── CreateX._requireSuccessfulContractInitialisation.t.sol
│ ├── CreateX._requireSuccessfulContractCreation_2Args.t.sol
│ ├── CreateX._generateSalt.t.sol
│ └── CreateX._guard.t.sol
├── public
│ ├── CREATE2
│ │ ├── CreateX.computeCreate2Address_2Args.tree
│ │ ├── CreateX.computeCreate2Address_3Args.tree
│ │ ├── CreateX.deployCreate2_1Arg.tree
│ │ ├── CreateX.deployCreate2_2Args.tree
│ │ ├── CreateX.deployCreate2Clone_2Args.tree
│ │ ├── CreateX.deployCreate2Clone_3Args.tree
│ │ ├── CreateX.deployCreate2AndInit_3Args.tree
│ │ ├── CreateX.deployCreate2AndInit_5Args.tree
│ │ ├── CreateX.deployCreate2AndInit_4Args_CustomiseSalt.tree
│ │ ├── CreateX.computeCreate2Address_2Args.t.sol
│ │ ├── CreateX.deployCreate2AndInit_4Args_CustomiseRefundAddress.tree
│ │ ├── CreateX.computeCreate2Address_3Args.t.sol
│ │ └── CreateX.deployCreate2_1Arg.t.sol
│ ├── CREATE3
│ │ ├── CreateX.computeCreate3Address_1Arg.tree
│ │ ├── CreateX.computeCreate3Address_2Args.tree
│ │ ├── CreateX.deployCreate3_1Arg.tree
│ │ ├── CreateX.deployCreate3_2Args.tree
│ │ ├── CreateX.deployCreate3AndInit_3Args.tree
│ │ ├── CreateX.deployCreate3AndInit_5Args.tree
│ │ ├── CreateX.deployCreate3AndInit_4Args_CustomiseSalt.tree
│ │ ├── CreateX.deployCreate3AndInit_4Args_CustomiseRefundAddress.tree
│ │ ├── CreateX.computeCreate3Address_1Arg.t.sol
│ │ └── CreateX.computeCreate3Address_2Args.t.sol
│ └── CREATE
│ │ ├── CreateX.computeCreateAddress_1Arg.tree
│ │ ├── CreateX.computeCreateAddress_2Args.tree
│ │ ├── CreateX.deployCreate.tree
│ │ ├── CreateX.deployCreateClone.tree
│ │ ├── CreateX.deployCreateAndInit_3Args.tree
│ │ ├── CreateX.deployCreateAndInit_4Args.tree
│ │ ├── CreateX.computeCreateAddress_1Arg.t.sol
│ │ ├── CreateX.computeCreateAddress_2Args.t.sol
│ │ ├── CreateX.deployCreate.t.sol
│ │ └── CreateX.deployCreateClone.t.sol
├── .solhint-tests.json
├── mocks
│ ├── ImplementationContract.sol
│ └── ERC20MockPayable.sol
├── invariants
│ └── CreateX_Invariants.t.sol
└── utils
│ └── BaseTest.sol
├── .prettierignore
├── .solhintignore
├── pnpm-workspace.yaml
├── .github
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.yml
│ ├── bug_report.yml
│ └── deployment_request.yml
├── pull_request_template.md
└── workflows
│ ├── codeql.yml
│ ├── deploy.yml
│ ├── checks.yml
│ └── test-createx.yml
├── .editorconfig
├── src
├── .solhint.json
└── ICreateX.sol
├── .gitmodules
├── renovate.json
├── foundry.lock
├── tsconfig.json
├── slither.config.json
├── .gitignore
├── eslint.config.js
├── foundry.toml
├── abis
└── src
│ ├── CreateX.sol
│ └── CreateX.json
│ └── ICreateX.sol
│ └── ICreateX.json
├── SECURITY.md
└── scripts
├── presign.ts
└── deploy.ts
/interface/public/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | - "prettier-plugin-solidity"
3 |
--------------------------------------------------------------------------------
/interface/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | next-env.d.ts
4 | dist
5 |
--------------------------------------------------------------------------------
/interface/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["@tailwindcss/postcss"],
3 | };
4 |
--------------------------------------------------------------------------------
/interface/public/viem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcaversaccio/createx/HEAD/interface/public/viem.png
--------------------------------------------------------------------------------
/interface/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcaversaccio/createx/HEAD/interface/public/favicon.ico
--------------------------------------------------------------------------------
/interface/public/ethersjs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcaversaccio/createx/HEAD/interface/public/ethersjs.png
--------------------------------------------------------------------------------
/interface/public/solidity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcaversaccio/createx/HEAD/interface/public/solidity.png
--------------------------------------------------------------------------------
/remappings.txt:
--------------------------------------------------------------------------------
1 | solady/=lib/solady/src/
2 | forge-std/=lib/forge-std/src/
3 | openzeppelin/=lib/openzeppelin-contracts/contracts/
4 |
--------------------------------------------------------------------------------
/FUNDING.json:
--------------------------------------------------------------------------------
1 | {
2 | "opRetro": {
3 | "projectId": "0x0008577196fa6ec286440b418e2f6305fc10e62ce759625d826be272ee6b45a3"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/interface/prismjs.d.ts:
--------------------------------------------------------------------------------
1 | declare module "prismjs";
2 | declare module "prismjs/themes/*" {
3 | const content: string;
4 | export default content;
5 | }
6 |
--------------------------------------------------------------------------------
/artifacts/src/CreateX.sol/CreateX.dbg.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-dbg-1",
3 | "buildInfo": "..\\..\\build-info\\c6d951136e35b73916c5666d481ae5c8.json"
4 | }
5 |
--------------------------------------------------------------------------------
/artifacts/src/ICreateX.sol/ICreateX.dbg.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-dbg-1",
3 | "buildInfo": "..\\..\\build-info\\c6d951136e35b73916c5666d481ae5c8.json"
4 | }
5 |
--------------------------------------------------------------------------------
/test/internal/CreateX._efficientHash.tree:
--------------------------------------------------------------------------------
1 | CreateX_EfficientHash_Internal_Test
2 | ├── It matches the output of a high-level hash.
3 | └── It should never revert.
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | pnpm-lock.yaml
3 | interface
4 | lib
5 | cache
6 | typechain-types
7 | artifacts
8 | forge-artifacts
9 | coverage
10 | bin
11 | out
12 |
--------------------------------------------------------------------------------
/.solhintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | pnpm-lock.yaml
3 | interface
4 | lib
5 | cache
6 | typechain-types
7 | artifacts
8 | forge-artifacts
9 | coverage
10 | bin
11 | out
12 |
--------------------------------------------------------------------------------
/test/internal/CreateX._generateSalt.tree:
--------------------------------------------------------------------------------
1 | CreateX_GenerateSalt_Internal_Test
2 | ├── It should be a function of multiple block properties and the caller.
3 | └── It should never revert.
4 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.computeCreate2Address_2Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_ComputeCreate2Address_2Args_Public_Test
2 | ├── It returns the 20-byte address where a contract will be stored.
3 | └── It should never revert.
4 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.computeCreate2Address_3Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_ComputeCreate2Address_3Args_Public_Test
2 | ├── It returns the 20-byte address where a contract will be stored.
3 | └── It should never revert.
4 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.computeCreate3Address_1Arg.tree:
--------------------------------------------------------------------------------
1 | CreateX_ComputeCreate3Address_1Arg_Public_Test
2 | ├── It returns the 20-byte address where a contract will be stored.
3 | └── It should never revert.
4 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.computeCreate3Address_2Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_ComputeCreate3Address_2Args_Public_Test
2 | ├── It returns the 20-byte address where a contract will be stored.
3 | └── It should never revert.
4 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | sharedWorkspaceLockfile: true
2 | packages:
3 | - interface
4 | onlyBuiltDependencies:
5 | - "@tailwindcss/oxide"
6 | - keccak
7 | - secp256k1
8 | - sharp
9 | - unrs-resolver
10 |
--------------------------------------------------------------------------------
/interface/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | - "@trivago/prettier-plugin-sort-imports"
3 | - "prettier-plugin-tailwindcss"
4 | importOrder:
5 | ["^react(.*)", "next/(.*)", "", "@/(.*)", "^[./]"]
6 | importOrderSortSpecifiers: true
7 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "monthly"
7 | day: "monday"
8 | target-branch: "main"
9 | labels:
10 | - "dependencies 🔁"
11 | assignees:
12 | - "pcaversaccio"
13 |
--------------------------------------------------------------------------------
/.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 = true
10 |
11 | [*.sol]
12 | indent_size = 4
13 | max_line_length = 120
14 |
15 | [*.tree]
16 | indent_size = 1
17 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.computeCreateAddress_1Arg.tree:
--------------------------------------------------------------------------------
1 | CreateX_ComputeCreateAddress_1Arg_Public_Test
2 | ├── When the nonce value does not exceed 18446744073709551614
3 | │ └── It returns the 20-byte address where a contract will be stored.
4 | └── When the nonce value exceeds 18446744073709551614
5 | └── It should revert.
6 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.computeCreateAddress_2Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_ComputeCreateAddress_2Args_Public_Test
2 | ├── When the nonce value does not exceed 18446744073709551614
3 | │ └── It returns the 20-byte address where a contract will be stored.
4 | └── When the nonce value exceeds 18446744073709551614
5 | └── It should revert.
6 |
--------------------------------------------------------------------------------
/src/.solhint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solhint:recommended",
3 | "rules": {
4 | "compiler-version": "off",
5 | "func-visibility": ["warn", { "ignoreConstructors": true }],
6 | "avoid-low-level-calls": "off",
7 | "no-inline-assembly": "off",
8 | "gas-strict-inequalities": "off",
9 | "use-natspec": "off"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/solady"]
2 | path = lib/solady
3 | url = https://github.com/vectorized/solady.git
4 | [submodule "lib/forge-std"]
5 | path = lib/forge-std
6 | url = https://github.com/foundry-rs/forge-std.git
7 | [submodule "lib/openzeppelin-contracts"]
8 | path = lib/openzeppelin-contracts
9 | url = https://github.com/openzeppelin/openzeppelin-contracts.git
10 |
--------------------------------------------------------------------------------
/interface/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | export const classNames = (...classes: string[]) =>
2 | classes.filter(Boolean).join(" ");
3 |
4 | export const copyToClipboard = (text: string) => {
5 | navigator.clipboard.writeText(text).then(
6 | () => console.log("Copying to clipboard was successful!"),
7 | (err) => console.error("Could not copy text to clipboard: ", err),
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/test/internal/CreateX._requireSuccessfulContractInitialisation.tree:
--------------------------------------------------------------------------------
1 | CreateX_RequireSuccessfulContractInitialisation_Internal_Test
2 | ├── When the success Boolean is false
3 | │ └── It should revert.
4 | └── When the success Boolean is true
5 | ├── When the implementation address has no code
6 | │ └── It should revert.
7 | └── When the implementation address has code
8 | └── It should never revert.
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/test/internal/CreateX._requireSuccessfulContractCreation_1Arg.tree:
--------------------------------------------------------------------------------
1 | CreateX_RequireSuccessfulContractCreation_1Arg_Internal_Test
2 | ├── When the newContract address is the zero address
3 | │ └── It should revert.
4 | └── When the newContract address is not the zero address
5 | ├── When the newContract address has no code
6 | │ └── It should revert.
7 | └── When the newContract address has code
8 | └── It should never revert.
9 |
--------------------------------------------------------------------------------
/interface/README.md:
--------------------------------------------------------------------------------
1 | # [`CreateX`](../src/CreateX.sol) User Interface
2 |
3 | [Next.js](https://nextjs.org)-based user interface for [createx.rocks](https://createx.rocks).
4 |
5 | ## 🙏🏼 Acknowledgement
6 |
7 | Our code is based on a fork of [Matt Solomon](https://github.com/mds1)'s [`Multicall3`](https://github.com/mds1/multicall3) [frontend](https://github.com/mds1/multicall3-frontend), licensed under the [MIT License](https://github.com/mds1/multicall3-frontend/blob/main/LICENSE).
8 |
--------------------------------------------------------------------------------
/foundry.lock:
--------------------------------------------------------------------------------
1 | {
2 | "lib/forge-std": {
3 | "branch": {
4 | "name": "master",
5 | "rev": "26048ab55c64519ce21e032fdb49df1d5a2a7eb4"
6 | }
7 | },
8 | "lib/openzeppelin-contracts": {
9 | "branch": {
10 | "name": "master",
11 | "rev": "308c4914b5482e8ffcbbe6f00be7d7f0429856f6"
12 | }
13 | },
14 | "lib/solady": {
15 | "branch": {
16 | "name": "main",
17 | "rev": "cbcfe0009477aa329574f17e8db0a05703bb8bdd"
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2024",
4 | "module": "commonjs",
5 | "allowJs": true,
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "outDir": "dist",
9 | "declaration": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "skipLibCheck": true,
12 | "resolveJsonModule": true
13 | },
14 | "include": ["./scripts", "./typechain-types"],
15 | "files": ["./hardhat.config.ts", "eslint.config.js"]
16 | }
17 |
--------------------------------------------------------------------------------
/slither.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "compile_force_framework": "foundry",
3 | "hardhat_ignore_compile": true,
4 | "detectors_to_exclude": "pragma,solc-version,assembly,too-many-digits,low-level-calls,missing-zero-check,reentrancy-events,arbitrary-send-eth,incorrect-equality,timestamp,naming-convention",
5 | "fail_on": "none",
6 | "exclude_informational": false,
7 | "exclude_low": false,
8 | "exclude_medium": false,
9 | "exclude_high": false,
10 | "filter_paths": "lib|test|src/ICreateX.sol"
11 | }
12 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.deployCreate.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
5 | ├── When the initCode successfully creates a runtime bytecode with a zero length
6 | │ └── It should revert.
7 | └── When the initCode fails to deploy a runtime bytecode
8 | └── It should revert.
9 |
--------------------------------------------------------------------------------
/interface/src/components/layout/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import { JSX } from "react";
2 |
3 | type Props = {
4 | href: string;
5 | className?: string;
6 | } & (
7 | | { text: string; children?: never }
8 | | { text?: never; children: JSX.Element }
9 | );
10 |
11 | export const ExternalLink = ({ href, className, text, children }: Props) => {
12 | return (
13 |
19 | {text || children}
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/test/internal/CreateX._requireSuccessfulContractCreation_2Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_RequireSuccessfulContractCreation_2Args_Internal_Test
2 | ├── When the success Boolean is false
3 | │ └── It should revert.
4 | └── When the success Boolean is true
5 | ├── When the newContract address is the zero address
6 | │ └── It should revert.
7 | └── When the newContract address is not the zero address
8 | ├── When the newContract address has no code
9 | │ └── It should revert.
10 | └── When the newContract address has code
11 | └── It should never revert.
12 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2_1Arg.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate2_1Arg_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address and the salt as indexed arguments.
5 | ├── When the initCode successfully creates a runtime bytecode with a zero length
6 | │ └── It should revert.
7 | └── When the initCode fails to deploy a runtime bytecode
8 | └── It should revert.
9 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2_2Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate2_2Args_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address and the salt as indexed arguments.
5 | ├── When the initCode successfully creates a runtime bytecode with a zero length
6 | │ └── It should revert.
7 | └── When the initCode fails to deploy a runtime bytecode
8 | └── It should revert.
9 |
--------------------------------------------------------------------------------
/interface/src/components/layout/Layout.tsx:
--------------------------------------------------------------------------------
1 | import { JSX } from "react";
2 | import { Footer } from "@/components/layout/Footer";
3 | import { Header } from "@/components/layout/Header";
4 |
5 | interface Props {
6 | children: JSX.Element;
7 | }
8 |
9 | export const Layout = ({ children }: Props) => {
10 | return (
11 |
12 |
13 |
14 | {children}
15 |
16 |
17 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.deployCreateClone.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreateClone_Public_Test
2 | ├── When the EIP-1167 minimal proxy contract is successfully created
3 | │ ├── It emits the event ContractCreation with the EIP-1167 minimal proxy address as indexed argument.
4 | │ ├── When the EIP-1167 minimal proxy initialisation call is successful
5 | │ │ └── It returns the EIP-1167 minimal proxy address.
6 | │ └── When the EIP-1167 minimal proxy initialisation call is unsuccessful
7 | │ └── It should revert.
8 | └── When the EIP-1167 minimal proxy contract creation fails
9 | └── It should revert.
10 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2Clone_2Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate2Clone_2Args_Public_Test
2 | ├── When the EIP-1167 minimal proxy contract is successfully created
3 | │ ├── It emits the event ContractCreation with the EIP-1167 minimal proxy address and the salt as indexed arguments.
4 | │ ├── When the EIP-1167 minimal proxy initialisation call is successful
5 | │ │ └── It returns the EIP-1167 minimal proxy address.
6 | │ └── When the EIP-1167 minimal proxy initialisation call is unsuccessful
7 | │ └── It should revert.
8 | └── When the EIP-1167 minimal proxy contract creation fails
9 | └── It should revert.
10 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2Clone_3Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate2Clone_3Args_Public_Test
2 | ├── When the EIP-1167 minimal proxy contract is successfully created
3 | │ ├── It emits the event ContractCreation with the EIP-1167 minimal proxy address and the salt as indexed arguments.
4 | │ ├── When the EIP-1167 minimal proxy initialisation call is successful
5 | │ │ └── It returns the EIP-1167 minimal proxy address.
6 | │ └── When the EIP-1167 minimal proxy initialisation call is unsuccessful
7 | │ └── It should revert.
8 | └── When the EIP-1167 minimal proxy contract creation fails
9 | └── It should revert.
10 |
--------------------------------------------------------------------------------
/test/.solhint-tests.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solhint:recommended",
3 | "rules": {
4 | "compiler-version": "off",
5 | "func-visibility": ["warn", { "ignoreConstructors": true }],
6 | "one-contract-per-file": "off",
7 | "contract-name-capwords": "off",
8 | "func-name-mixedcase": "off",
9 | "no-empty-blocks": "off",
10 | "avoid-low-level-calls": "off",
11 | "no-inline-assembly": "off",
12 | "max-states-count": "off",
13 | "import-path-check": "off",
14 | "gas-strict-inequalities": "off",
15 | "function-max-lines": "off",
16 | "gas-small-strings": "off",
17 | "use-natspec": "off"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/interface/src/components/layout/Head.tsx:
--------------------------------------------------------------------------------
1 | import NextHead from "next/head";
2 | import { generateNextSeo } from "next-seo/pages";
3 | import { SITE_DESCRIPTION, SITE_NAME } from "@/lib/constants";
4 |
5 | interface Props {
6 | title?: string;
7 | description?: string;
8 | }
9 |
10 | export const Head = ({ title, description }: Props) => {
11 | const seoProps = {
12 | title: title ? `${title} | ${SITE_NAME}` : SITE_NAME,
13 | description: description ?? SITE_DESCRIPTION,
14 | };
15 |
16 | return (
17 |
18 | {generateNextSeo(seoProps)}
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/interface/src/components/layout/ThemeSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react";
2 | import { MoonIcon, SunIcon } from "@heroicons/react/20/solid";
3 | import { useTheme } from "next-themes";
4 |
5 | export const ThemeSwitcher = ({ className = "" }) => {
6 | const { resolvedTheme, setTheme } = useTheme();
7 |
8 | const toggleTheme = useCallback(() => {
9 | setTheme(resolvedTheme === "light" ? "dark" : "light");
10 | }, [resolvedTheme, setTheme]);
11 |
12 | return (
13 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.deployCreate3_1Arg.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate3_1Arg_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ ├── It emits the event Create3ProxyContractCreation with the proxy address and the salt as indexed arguments.
5 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
6 | ├── When the proxy contract creation fails
7 | │ └── It should revert.
8 | ├── When the initCode successfully creates a runtime bytecode with a zero length
9 | │ └── It should revert.
10 | └── When the initCode fails to deploy a runtime bytecode
11 | └── It should revert.
12 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.deployCreate3_2Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate3_2Args_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ ├── It emits the event Create3ProxyContractCreation with the proxy address and the salt as indexed arguments.
5 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
6 | ├── When the proxy contract creation fails
7 | │ └── It should revert.
8 | ├── When the initCode successfully creates a runtime bytecode with a zero length
9 | │ └── It should revert.
10 | └── When the initCode fails to deploy a runtime bytecode
11 | └── It should revert.
12 |
--------------------------------------------------------------------------------
/interface/next-seo.config.ts:
--------------------------------------------------------------------------------
1 | import type { DefaultSeoProps } from "next-seo/pages";
2 | import { SITE_DESCRIPTION, SITE_IMAGE, SITE_NAME } from "@/lib/constants";
3 |
4 | const config: DefaultSeoProps = {
5 | title: SITE_NAME,
6 | description: SITE_DESCRIPTION,
7 | openGraph: {
8 | url: "https://createx.rocks",
9 | title: "CreateX",
10 | description: SITE_DESCRIPTION,
11 | images: [
12 | {
13 | url: SITE_IMAGE,
14 | secureUrl: SITE_IMAGE,
15 | type: "image/png",
16 | width: 1200,
17 | height: 514,
18 | alt: `${SITE_NAME} – ${SITE_DESCRIPTION}`,
19 | },
20 | ],
21 | },
22 | twitter: {
23 | handle: "@pcaversaccio",
24 | cardType: "summary_large_image",
25 | },
26 | };
27 |
28 | export default config;
29 |
--------------------------------------------------------------------------------
/test/internal/CreateX._efficientHash.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../utils/BaseTest.sol";
5 |
6 | contract CreateX_EfficientHash_Internal_Test is BaseTest {
7 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
8 | /* TESTS */
9 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
10 |
11 | function testFuzz_MatchesTheOutputOfAHighLevelHashAndShouldNeverRevert(bytes32 a, bytes32 b) external view {
12 | // It should match the output of a high-level hash.
13 | // It should never revert.
14 | bytes32 expected = keccak256(abi.encodePacked(a, b));
15 | bytes32 actual = createXHarness.exposed_efficientHash(a, b);
16 | assertEq(actual, expected, "100");
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/interface/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import type { AppProps } from "next/app";
3 | import Head from "next/head";
4 | import { generateDefaultSeo } from "next-seo/pages";
5 | import { ThemeProvider } from "next-themes";
6 | import { Layout } from "@/components/layout/Layout";
7 | import "@/styles/globals.css";
8 | import DefaultSeoProps from "../../next-seo.config";
9 |
10 | function App({ Component, pageProps }: AppProps) {
11 | const [mounted, setMounted] = React.useState(false);
12 | React.useEffect(() => setMounted(true), []);
13 | return (
14 |
15 | {generateDefaultSeo(DefaultSeoProps)}
16 | {mounted && (
17 |
18 |
19 |
20 | )}
21 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/interface/src/components/ui/LoadingSpinner.tsx:
--------------------------------------------------------------------------------
1 | export const LoadingSpinner = () => {
2 | return (
3 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/interface/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "allowJs": true,
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "outDir": "dist",
9 | "declaration": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "skipLibCheck": true,
12 | "resolveJsonModule": true,
13 | "lib": ["dom", "dom.iterable", "esnext"],
14 | "noEmit": true,
15 | "moduleResolution": "bundler",
16 | "isolatedModules": true,
17 | "jsx": "react-jsx",
18 | "incremental": true,
19 | "plugins": [
20 | {
21 | "name": "next"
22 | }
23 | ],
24 | "paths": {
25 | "@/*": ["./src/*"]
26 | }
27 | },
28 | "include": [
29 | "**/*.js",
30 | "**/*.ts",
31 | "**/*.tsx",
32 | ".next/types/**/*.ts",
33 | "next.config.mjs"
34 | ],
35 | "files": ["next-env.d.ts", "eslint.config.js"],
36 | "exclude": ["node_modules"]
37 | }
38 |
--------------------------------------------------------------------------------
/test/mocks/ImplementationContract.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | /**
5 | * @title ImplementationContract
6 | * @author pcaversaccio
7 | * @dev Allows to mock a simple `payable` implementation contract for an EIP-1167 minimal proxy contract.
8 | */
9 | contract ImplementationContract {
10 | bool public isInitialised;
11 |
12 | /**
13 | * @dev Error that occurs when the initialisation function has already been called previously.
14 | * @param emitter The contract that emits the error.
15 | */
16 | error IsAlreadyInitialised(address emitter);
17 |
18 | constructor() payable {}
19 |
20 | /**
21 | * @dev An initialisation function that is called once during deployment.
22 | */
23 | function initialiser() external payable {
24 | if (isInitialised) {
25 | revert IsAlreadyInitialised(address(this));
26 | }
27 | isInitialised = true;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: "Feature Request"
2 | description: "Suggest an idea for CreateX."
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/createx/issues) to avoid duplicates.
12 | Thanks for taking the time to provide feedback on CreateX!
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: "```solidity\npragma solidity 0.8.23;\n```"
27 | id: reproduce
28 | type: textarea
29 |
--------------------------------------------------------------------------------
/interface/.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 | # dotenv environment variable files
19 | .env
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 | .env.local
24 |
25 | # Log files
26 | logs
27 | *.log
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # Yarn integrity file
34 | .yarn-integrity
35 |
36 | # Optional npm cache directory
37 | .npm
38 |
39 | # Modern Yarn files
40 | .pnp.*
41 | .yarn/*
42 | !.yarn/cache
43 | !.yarn/patches
44 | !.yarn/plugins
45 | !.yarn/releases
46 | !.yarn/sdks
47 | !.yarn/versions
48 |
49 | # Next.js
50 | .next
51 | next-env.d.ts
52 | dist
53 |
--------------------------------------------------------------------------------
/.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/createx/issues) or in the [discussions](https://github.com/pcaversaccio/createx/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/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@v6
24 |
25 | - name: Initialise CodeQL
26 | uses: github/codeql-action/init@v4
27 | with:
28 | languages: ${{ matrix.language }}
29 | queries: +security-and-quality
30 |
31 | - name: Autobuild
32 | uses: github/codeql-action/autobuild@v4
33 |
34 | - name: Perform CodeQL analysis
35 | uses: github/codeql-action/analyze@v4
36 | with:
37 | category: "/language:${{ matrix.language }}"
38 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.deployCreateAndInit_3Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreateAndInit_3Args_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
5 | │ ├── When the initialisation call is successful
6 | │ │ └── When the CreateX contract has a non-zero balance
7 | │ │ ├── When the refund transaction is successful
8 | │ │ │ └── It returns the non-zero balance to the caller address.
9 | │ │ └── When the refund transaction is unsuccessful
10 | │ │ └── It should revert.
11 | │ └── When the initialisation call is unsuccessful
12 | │ └── It should revert.
13 | ├── When the initCode successfully creates a runtime bytecode with a zero length
14 | │ └── It should revert.
15 | └── When the initCode fails to deploy a runtime bytecode
16 | └── It should revert.
17 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.deployCreateAndInit_4Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreateAndInit_4Args_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
5 | │ ├── When the initialisation call is successful
6 | │ │ └── When the CreateX contract has a non-zero balance
7 | │ │ ├── When the refund transaction is successful
8 | │ │ │ └── It returns the non-zero balance to the refundAddress address.
9 | │ │ └── When the refund transaction is unsuccessful
10 | │ │ └── It should revert.
11 | │ └── When the initialisation call is unsuccessful
12 | │ └── It should revert.
13 | ├── When the initCode successfully creates a runtime bytecode with a zero length
14 | │ └── It should revert.
15 | └── When the initCode fails to deploy a runtime bytecode
16 | └── It should revert.
17 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2AndInit_3Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate2AndInit_3Args_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address and the salt as indexed arguments.
5 | │ ├── When the initialisation call is successful
6 | │ │ └── When the CreateX contract has a non-zero balance
7 | │ │ ├── When the refund transaction is successful
8 | │ │ │ └── It returns the non-zero balance to the caller address.
9 | │ │ └── When the refund transaction is unsuccessful
10 | │ │ └── It should revert.
11 | │ └── When the initialisation call is unsuccessful
12 | │ └── It should revert.
13 | ├── When the initCode successfully creates a runtime bytecode with a zero length
14 | │ └── It should revert.
15 | └── When the initCode fails to deploy a runtime bytecode
16 | └── It should revert.
17 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2AndInit_5Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate2AndInit_5Args_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address and the salt as indexed arguments.
5 | │ ├── When the initialisation call is successful
6 | │ │ └── When the CreateX contract has a non-zero balance
7 | │ │ ├── When the refund transaction is successful
8 | │ │ │ └── It returns the non-zero balance to the refundAddress address.
9 | │ │ └── When the refund transaction is unsuccessful
10 | │ │ └── It should revert.
11 | │ └── When the initialisation call is unsuccessful
12 | │ └── It should revert.
13 | ├── When the initCode successfully creates a runtime bytecode with a zero length
14 | │ └── It should revert.
15 | └── When the initCode fails to deploy a runtime bytecode
16 | └── It should revert.
17 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2AndInit_4Args_CustomiseSalt.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate2AndInit_4Args_CustomiseSalt_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address and the salt as indexed arguments.
5 | │ ├── When the initialisation call is successful
6 | │ │ └── When the CreateX contract has a non-zero balance
7 | │ │ ├── When the refund transaction is successful
8 | │ │ │ └── It returns the non-zero balance to the caller address.
9 | │ │ └── When the refund transaction is unsuccessful
10 | │ │ └── It should revert.
11 | │ └── When the initialisation call is unsuccessful
12 | │ └── It should revert.
13 | ├── When the initCode successfully creates a runtime bytecode with a zero length
14 | │ └── It should revert.
15 | └── When the initCode fails to deploy a runtime bytecode
16 | └── It should revert.
17 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.computeCreate2Address_2Args.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {CreateX} from "../../../src/CreateX.sol";
6 |
7 | contract CreateX_ComputeCreate2Address_2Args_Public_Test is BaseTest {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* TESTS */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | function testFuzz_ReturnsThe20ByteAddressWhereAContractWillBeStoredAndShouldNeverRevert(bytes32 salt) external {
13 | vm.startPrank(createXAddr);
14 | address create2AddressComputedOnChain = address(new CreateX{salt: salt}());
15 | vm.stopPrank();
16 | // It returns the 20-byte address where a contract will be stored.
17 | // It should never revert.
18 | assertEq(
19 | createX.computeCreate2Address(salt, keccak256(type(CreateX).creationCode)),
20 | create2AddressComputedOnChain,
21 | "100"
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2AndInit_4Args_CustomiseRefundAddress.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate2AndInit_4Args_CustomiseRefundAddress_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ └── It emits the event ContractCreation with the contract address and the salt as indexed arguments.
5 | │ ├── When the initialisation call is successful
6 | │ │ └── When the CreateX contract has a non-zero balance
7 | │ │ ├── When the refund transaction is successful
8 | │ │ │ └── It returns the non-zero balance to the refundAddress address.
9 | │ │ └── When the refund transaction is unsuccessful
10 | │ │ └── It should revert.
11 | │ └── When the initialisation call is unsuccessful
12 | │ └── It should revert.
13 | ├── When the initCode successfully creates a runtime bytecode with a zero length
14 | │ └── It should revert.
15 | └── When the initCode fails to deploy a runtime bytecode
16 | └── It should revert.
17 |
--------------------------------------------------------------------------------
/interface/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-2025 Matt Solomon
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 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.computeCreate2Address_3Args.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {CreateX} from "../../../src/CreateX.sol";
6 |
7 | contract CreateX_ComputeCreate2Address_3Args_Public_Test is BaseTest {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* TESTS */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | function testFuzz_ReturnsThe20ByteAddressWhereAContractWillBeStoredAndShouldNeverRevert(
13 | bytes32 salt,
14 | address deployer
15 | ) external {
16 | vm.startPrank(deployer);
17 | address create2AddressComputedOnChain = address(new CreateX{salt: salt}());
18 | vm.stopPrank();
19 | // It returns the 20-byte address where a contract will be stored.
20 | // It should never revert.
21 | assertEq(
22 | createX.computeCreate2Address(salt, keccak256(type(CreateX).creationCode), deployer),
23 | create2AddressComputedOnChain,
24 | "100"
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: "Bug Report"
2 | description: "Report a bug for CreateX."
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/createx/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: "```console\nforge test\n```"
26 | id: reproduce
27 | type: textarea
28 | validations:
29 | required: true
30 |
31 | - attributes:
32 | label: "Relevant log output:"
33 | description: |
34 | Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
35 | render: Shell
36 | id: logs
37 | type: textarea
38 |
--------------------------------------------------------------------------------
/interface/next.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /**
4 | * @type {import('next').NextConfig}
5 | **/
6 | const nextConfig =
7 | // Check if running in a continuous integration (CI) environment.
8 | process.env.CI === "true"
9 | ? {
10 | /**
11 | * Enable static exports for the App Router.
12 | * @see https://nextjs.org/docs/pages/building-your-application/deploying/static-exports.
13 | */
14 | output: "export",
15 |
16 | /**
17 | * Disable server-based image optimization. Next.js does not support
18 | * dynamic features with static exports.
19 | * @see https://nextjs.org/docs/pages/api-reference/components/image#unoptimized.
20 | */
21 | images: {
22 | unoptimized: true,
23 | },
24 |
25 | /**
26 | * Use a custom build directory instead of `.next`.
27 | * @see https://nextjs.org/docs/pages/api-reference/next-config-js/distDir.
28 | */
29 | distDir: "dist",
30 | }
31 | : // For local development, just set a custom build directory instead of `.next`.
32 | {
33 | distDir: "dist",
34 | };
35 |
36 | export default nextConfig;
37 |
--------------------------------------------------------------------------------
/.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 | # dotenv environment variable files
19 | .env
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 | .env.local
24 |
25 | # Hardhat configuration file
26 | vars.json
27 |
28 | # Log files
29 | logs
30 | *.log
31 | npm-debug.log*
32 | yarn-debug.log*
33 | yarn-error.log*
34 | .pnpm-debug.log*
35 |
36 | # Yarn integrity file
37 | .yarn-integrity
38 |
39 | # Optional npm cache directory
40 | .npm
41 |
42 | # Modern Yarn files
43 | .pnp.*
44 | .yarn/*
45 | !.yarn/cache
46 | !.yarn/patches
47 | !.yarn/plugins
48 | !.yarn/releases
49 | !.yarn/sdks
50 | !.yarn/versions
51 |
52 | # Hardhat files
53 | cache
54 | .deps
55 |
56 | # TypeScript bindings output directory
57 | typechain-types
58 |
59 | # forge-coverage directory and lcov.info file
60 | coverage
61 | lcov.info
62 |
63 | # Foundry output directory
64 | forge-artifacts
65 |
66 | # Compiler files
67 | cache
68 | out
69 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.deployCreate3AndInit_3Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate3AndInit_3Args_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ ├── It emits the event Create3ProxyContractCreation with the proxy address and the salt as indexed arguments.
5 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
6 | │ ├── When the initialisation call is successful
7 | │ │ └── When the CreateX contract has a non-zero balance
8 | │ │ ├── When the refund transaction is successful
9 | │ │ │ └── It returns the non-zero balance to the refundAddress address.
10 | │ │ └── When the refund transaction is unsuccessful
11 | │ │ └── It should revert.
12 | │ └── When the initialisation call is unsuccessful
13 | │ └── It should revert.
14 | ├── When the proxy contract creation fails
15 | │ └── It should revert.
16 | ├── When the initCode successfully creates a runtime bytecode with a zero length
17 | │ └── It should revert.
18 | └── When the initCode fails to deploy a runtime bytecode
19 | └── It should revert.
20 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.deployCreate3AndInit_5Args.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate3AndInit_5Args_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ ├── It emits the event Create3ProxyContractCreation with the proxy address and the salt as indexed arguments.
5 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
6 | │ ├── When the initialisation call is successful
7 | │ │ └── When the CreateX contract has a non-zero balance
8 | │ │ ├── When the refund transaction is successful
9 | │ │ │ └── It returns the non-zero balance to the refundAddress address.
10 | │ │ └── When the refund transaction is unsuccessful
11 | │ │ └── It should revert.
12 | │ └── When the initialisation call is unsuccessful
13 | │ └── It should revert.
14 | ├── When the proxy contract creation fails
15 | │ └── It should revert.
16 | ├── When the initCode successfully creates a runtime bytecode with a zero length
17 | │ └── It should revert.
18 | └── When the initCode fails to deploy a runtime bytecode
19 | └── It should revert.
20 |
--------------------------------------------------------------------------------
/test/internal/CreateX._guard.tree:
--------------------------------------------------------------------------------
1 | CreateX_Guard_Internal_Test
2 | ├── When the first 20 bytes of the salt equals the caller
3 | │ ├── When the 21st byte of the salt equals 0x01
4 | │ │ └── It should return the keccak256 hash of the ABI-encoded values msg.sender, block.chainid, and the salt.
5 | │ ├── When the 21st byte of the salt equals 0x00
6 | │ │ └── It should return the keccak256 hash of the ABI-encoded values msg.sender and the salt.
7 | │ └── When the 21st byte of the salt is greater than 0x01
8 | │ └── It should revert.
9 | ├── When the first 20 bytes of the salt equals the zero address
10 | │ ├── When the 21st byte of the salt equals 0x01
11 | │ │ └── It should return the keccak256 hash of the ABI-encoded values block.chainid and the salt.
12 | │ ├── When the 21st byte of the salt equals 0x00
13 | │ │ └── It should return the keccak256 hash of the ABI-encoded value salt.
14 | │ └── When the 21st byte of the salt is greater than 0x01
15 | │ └── It should revert.
16 | └── When the first 20 bytes of the salt do not equal the caller or the zero address
17 | ├── It should return the keccak256 hash of the ABI-encoded value salt.
18 | └── When the salt value is generated pseudo-randomly
19 | └── It should return the unmodified salt value.
20 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.deployCreate3AndInit_4Args_CustomiseSalt.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate3AndInit_4Args_CustomiseSalt_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ ├── It emits the event Create3ProxyContractCreation with the proxy address and the salt as indexed arguments.
5 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
6 | │ ├── When the initialisation call is successful
7 | │ │ └── When the CreateX contract has a non-zero balance
8 | │ │ ├── When the refund transaction is successful
9 | │ │ │ └── It returns the non-zero balance to the caller address.
10 | │ │ └── When the refund transaction is unsuccessful
11 | │ │ └── It should revert.
12 | │ └── When the initialisation call is unsuccessful
13 | │ └── It should revert.
14 | ├── When the proxy contract creation fails
15 | │ └── It should revert.
16 | ├── When the initCode successfully creates a runtime bytecode with a zero length
17 | │ └── It should revert.
18 | └── When the initCode fails to deploy a runtime bytecode
19 | └── It should revert.
20 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.deployCreate3AndInit_4Args_CustomiseRefundAddress.tree:
--------------------------------------------------------------------------------
1 | CreateX_DeployCreate3AndInit_4Args_CustomiseRefundAddress_Public_Test
2 | ├── When the initCode successfully creates a runtime bytecode with a non-zero length
3 | │ ├── It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
4 | │ ├── It emits the event Create3ProxyContractCreation with the proxy address and the salt as indexed arguments.
5 | │ └── It emits the event ContractCreation with the contract address as indexed argument.
6 | │ ├── When the initialisation call is successful
7 | │ │ └── When the CreateX contract has a non-zero balance
8 | │ │ ├── When the refund transaction is successful
9 | │ │ │ └── It returns the non-zero balance to the refundAddress address.
10 | │ │ └── When the refund transaction is unsuccessful
11 | │ │ └── It should revert.
12 | │ └── When the initialisation call is unsuccessful
13 | │ └── It should revert.
14 | ├── When the proxy contract creation fails
15 | │ └── It should revert.
16 | ├── When the initCode successfully creates a runtime bytecode with a zero length
17 | │ └── It should revert.
18 | └── When the initCode fails to deploy a runtime bytecode
19 | └── It should revert.
20 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.computeCreate3Address_1Arg.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {CreateX} from "../../../src/CreateX.sol";
6 | import {CREATE3} from "solady/utils/CREATE3.sol";
7 |
8 | contract CreateX_ComputeCreate3Address_1Arg_Public_Test is BaseTest {
9 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
10 | /* TESTS */
11 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
12 |
13 | function testFuzz_ReturnsThe20ByteAddressWhereAContractWillBeStoredAndShouldNeverRevert(bytes32 salt) external {
14 | vm.startPrank(createXAddr);
15 | // We test our implementation against Solady's implementation. We have tested our own `CREATE3`
16 | // implementation extensively against `computeCreate3Address` as part of the other `CREATE3` tests.
17 | address create3AddressComputedOnChain = CREATE3.deployDeterministic(type(CreateX).creationCode, salt);
18 | vm.stopPrank();
19 | // It returns the 20-byte address where a contract will be stored.
20 | // It should never revert.
21 | assertEq(createX.computeCreate3Address(salt), create3AddressComputedOnChain, "100");
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/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 | const globals = require("globals");
6 | const { defineConfig } = require("eslint/config");
7 | /* eslint-enable @typescript-eslint/no-require-imports */
8 |
9 | module.exports = defineConfig(
10 | {
11 | files: ["**/*.{js,ts}"],
12 | extends: [
13 | eslint.configs.recommended,
14 | ...tseslint.configs.recommended,
15 | ...tseslint.configs.stylistic,
16 | eslintConfigPrettier,
17 | ],
18 | plugins: {
19 | "@typescript-eslint": tseslint.plugin,
20 | },
21 | languageOptions: {
22 | ecmaVersion: "latest",
23 | parser: tseslint.parser,
24 | globals: {
25 | ...globals.node,
26 | },
27 | parserOptions: {
28 | project: true,
29 | tsconfigRootDir: __dirname,
30 | },
31 | },
32 | },
33 | {
34 | ignores: [
35 | "node_modules/**",
36 | "pnpm-lock.yaml",
37 | "interface/**",
38 | "lib/**",
39 | "cache/**",
40 | "typechain-types/**",
41 | "artifacts/**",
42 | "forge-artifacts/**",
43 | "coverage/**",
44 | "bin/**",
45 | "out/**",
46 | ],
47 | },
48 | );
49 |
--------------------------------------------------------------------------------
/test/public/CREATE3/CreateX.computeCreate3Address_2Args.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {CreateX} from "../../../src/CreateX.sol";
6 | import {CREATE3} from "solady/utils/CREATE3.sol";
7 |
8 | contract CreateX_ComputeCreate3Address_2Args_Public_Test is BaseTest {
9 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
10 | /* TESTS */
11 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
12 |
13 | function testFuzz_ReturnsThe20ByteAddressWhereAContractWillBeStoredAndShouldNeverRevert(
14 | bytes32 salt,
15 | address deployer
16 | ) external {
17 | vm.startPrank(deployer);
18 | // We test our implementation against Solady's implementation. We have tested our own `CREATE3`
19 | // implementation extensively against `computeCreate3Address` as part of the other `CREATE3` tests.
20 | address create3AddressComputedOnChain = CREATE3.deployDeterministic(type(CreateX).creationCode, salt);
21 | vm.stopPrank();
22 | // It returns the 20-byte address where a contract will be stored.
23 | // It should never revert.
24 | assertEq(createX.computeCreate3Address(salt, deployer), create3AddressComputedOnChain, "100");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/mocks/ERC20MockPayable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol";
5 |
6 | /**
7 | * @title ERC20MockPayable
8 | * @author pcaversaccio
9 | * @notice Forked and adjusted accordingly from here:
10 | * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/token/ERC20Mock.sol.
11 | * @dev Allows to mock a simple `payable` ERC-20 implementation.
12 | */
13 | contract ERC20MockPayable is ERC20 {
14 | constructor(
15 | string memory name_,
16 | string memory symbol_,
17 | address initialAccount_,
18 | uint256 initialBalance_
19 | ) payable ERC20(name_, symbol_) {
20 | _mint({account: initialAccount_, value: initialBalance_});
21 | }
22 |
23 | /**
24 | * @dev Creates `amount` tokens and assigns them to `account`.
25 | * @param account The 20-byte account address.
26 | * @param amount The 32-byte token amount to be created.
27 | */
28 | function mint(address account, uint256 amount) public payable {
29 | _mint({account: account, value: amount});
30 | }
31 |
32 | /**
33 | * @dev Destroys `amount` tokens from `account`.
34 | * @param account The 20-byte account address.
35 | * @param amount The 32-byte token amount to be destroyed.
36 | */
37 | function burn(address account, uint256 amount) public payable {
38 | _burn({account: account, value: amount});
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/interface/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 next = require("@next/eslint-plugin-next");
5 | const react = require("eslint-plugin-react");
6 | const reactHooks = require("eslint-plugin-react-hooks");
7 | const eslintConfigPrettier = require("eslint-config-prettier");
8 | const globals = require("globals");
9 | const { defineConfig } = require("eslint/config");
10 | /* eslint-enable @typescript-eslint/no-require-imports */
11 |
12 | module.exports = defineConfig(
13 | {
14 | files: ["**/*.{js,mjs,ts,tsx}"],
15 | extends: [
16 | eslint.configs.recommended,
17 | ...tseslint.configs.recommended,
18 | ...tseslint.configs.stylistic,
19 | eslintConfigPrettier,
20 | ],
21 | plugins: {
22 | "@typescript-eslint": tseslint.plugin,
23 | "@next/next": next,
24 | react: react,
25 | "react-hooks": reactHooks,
26 | },
27 | rules: {
28 | ...next.configs.recommended.rules,
29 | ...react.configs["jsx-runtime"].rules,
30 | ...reactHooks.configs.recommended.rules,
31 | },
32 | languageOptions: {
33 | ecmaVersion: "latest",
34 | parser: tseslint.parser,
35 | globals: {
36 | ...globals.node,
37 | },
38 | parserOptions: {
39 | project: true,
40 | tsconfigRootDir: __dirname,
41 | },
42 | },
43 | },
44 | {
45 | ignores: ["node_modules/**", ".next/**", "next-env.d.ts", "dist/**"],
46 | },
47 | );
48 |
--------------------------------------------------------------------------------
/interface/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @custom-variant dark (&:is(.dark *));
4 |
5 | @theme {
6 | --background-image-gradient-radial: radial-gradient(var(--tw-gradient-stops));
7 | --background-image-gradient-conic: conic-gradient(
8 | from 180deg at 50% 50%,
9 | var(--tw-gradient-stops)
10 | );
11 | }
12 |
13 | /*
14 | The default border colour has changed to `currentColor` in Tailwind CSS v4,
15 | so we've added these compatibility styles to make sure everything still
16 | looks the same as it did with Tailwind CSS v3.
17 |
18 | If we ever want to remove these styles, we need to add an explicit border
19 | color utility to any element that depends on these defaults.
20 | */
21 | @layer base {
22 | *,
23 | ::after,
24 | ::before,
25 | ::backdrop,
26 | ::file-selector-button {
27 | border-color: var(--color-gray-200, currentColor);
28 | }
29 | }
30 |
31 | @layer base {
32 | .hyperlink {
33 | @apply text-blue-700 no-underline hover:text-blue-900 dark:text-blue-300 dark:hover:text-blue-400;
34 | }
35 |
36 | .text-primary {
37 | @apply text-gray-900 dark:text-gray-50;
38 | }
39 |
40 | .text-secondary {
41 | @apply text-gray-500 dark:text-gray-400;
42 | }
43 |
44 | .text-hover {
45 | @apply hover:text-gray-500 dark:hover:text-gray-300;
46 | }
47 |
48 | .text-accent {
49 | @apply text-blue-800 dark:text-blue-300;
50 | }
51 |
52 | .bg-primary {
53 | @apply bg-gray-100 dark:bg-gray-900;
54 | }
55 |
56 | .bg-secondary {
57 | @apply bg-gray-50 dark:bg-gray-800;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/internal/CreateX._parseSalt.tree:
--------------------------------------------------------------------------------
1 | CreateX_ParseSalt_Internal_Test
2 | ├── When the first 20 bytes of the salt equals the caller
3 | │ ├── It should return the SenderBytes.MsgSender enum as first return value.
4 | │ ├── When the 21st byte of the salt equals 0x01
5 | │ │ └── It should return the RedeployProtectionFlag.True enum as second return value.
6 | │ ├── When the 21st byte of the salt equals 0x00
7 | │ │ └── It should return the RedeployProtectionFlag.False enum as second return value.
8 | │ └── When the 21st byte of the salt is greater than 0x01
9 | │ └── It should return the RedeployProtectionFlag.Unspecified enum as second return value.
10 | ├── When the first 20 bytes of the salt equals the zero address
11 | │ ├── It should return the SenderBytes.ZeroAddress enum as first return value.
12 | │ ├── When the 21st byte of the salt equals 0x01
13 | │ │ └── It should return the RedeployProtectionFlag.True enum as second return value.
14 | │ ├── When the 21st byte of the salt equals 0x00
15 | │ │ └── It should return the RedeployProtectionFlag.False enum as second return value.
16 | │ └── When the 21st byte of the salt is greater than 0x01
17 | │ └── It should return the RedeployProtectionFlag.Unspecified enum as second return value.
18 | └── When the first 20 bytes of the salt do not equal the caller or the zero address
19 | ├── It should return the SenderBytes.Random enum as first return value.
20 | ├── When the 21st byte of the salt equals 0x01
21 | │ └── It should return the RedeployProtectionFlag.True enum as second return value.
22 | ├── When the 21st byte of the salt equals 0x00
23 | │ └── It should return the RedeployProtectionFlag.False enum as second return value.
24 | └── When the 21st byte of the salt is greater than 0x01
25 | └── It should return the RedeployProtectionFlag.Unspecified enum as second return value.
26 |
--------------------------------------------------------------------------------
/interface/public/json.svg:
--------------------------------------------------------------------------------
1 |
48 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/deployment_request.yml:
--------------------------------------------------------------------------------
1 | name: "Deployment Request"
2 | description: "Request to deploy CreateX on a new chain."
3 | title: "[New-Deployment-Request]: "
4 | labels:
5 | - new deployment ➕
6 | assignees:
7 | - pcaversaccio
8 | body:
9 | - type: markdown
10 | attributes:
11 | value: |
12 | Before opening a deployment request, consider deploying CreateX yourself.
13 | See the README section [New Deployment(s)](https://github.com/pcaversaccio/createx#new-deployments) for more information about how to do this.
14 |
15 | - attributes:
16 | label: Chain Name
17 | description: What is the name of the target chain?
18 | placeholder: "OP (Optimism) Mainnet"
19 | id: chain-name
20 | type: input
21 | validations:
22 | required: true
23 |
24 | - attributes:
25 | label: Chain ID
26 | description: What is the chain ID of the target chain?
27 | placeholder: "10"
28 | id: chain-id
29 | type: input
30 | validations:
31 | required: true
32 |
33 | - attributes:
34 | label: RPC URL
35 | description: What is the RPC URL for the target chain?
36 | placeholder: "https://mainnet.optimism.io"
37 | id: rpc-url
38 | type: input
39 | validations:
40 | required: true
41 |
42 | - attributes:
43 | label: Block Explorer URL
44 | description: What is the URL of the block explorer for the target chain?
45 | placeholder: "https://optimistic.etherscan.io"
46 | id: block-explorer-url
47 | type: input
48 | validations:
49 | required: true
50 |
51 | - attributes:
52 | label: Deployment Funds
53 | description: |
54 | Have you sent the deployment funds to the deployer account (`0xeD456e05CaAb11d66C4c797dD6c1D6f9A7F352b5`) on the target chain?
55 | Doing so is greatly appreciated and will reduce the time to deployment!
56 | options:
57 | - "Yes"
58 | - "No"
59 | id: deployment-funds
60 | type: dropdown
61 | validations:
62 | required: true
63 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.computeCreateAddress_1Arg.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {CreateX} from "../../../src/CreateX.sol";
6 |
7 | contract CreateX_ComputeCreateAddress_1Arg_Public_Test is BaseTest {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* HELPER VARIABLES */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | uint256 internal cachedNonce;
13 |
14 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
15 | /* TESTS */
16 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
17 |
18 | modifier whenTheNonceValueDoesNotExceed18446744073709551614(uint64 nonce) {
19 | vm.assume(nonce != 0 && nonce < type(uint64).max);
20 | vm.setNonceUnsafe(createXAddr, nonce);
21 | _;
22 | }
23 |
24 | function testFuzz_WhenTheNonceValueDoesNotExceed18446744073709551614(
25 | uint64 nonce
26 | ) external whenTheNonceValueDoesNotExceed18446744073709551614(nonce) {
27 | vm.startPrank(createXAddr);
28 | address createAddressComputedOnChain = address(new CreateX());
29 | vm.stopPrank();
30 | // It returns the 20-byte address where a contract will be stored.
31 | assertEq(createX.computeCreateAddress(nonce), createAddressComputedOnChain, "100");
32 | }
33 |
34 | modifier whenTheNonceValueExceeds18446744073709551614(uint256 nonce) {
35 | cachedNonce = bound(nonce, type(uint64).max, type(uint256).max);
36 | _;
37 | }
38 |
39 | function testFuzz_WhenTheNonceValueExceeds18446744073709551614(
40 | uint256 nonce
41 | ) external whenTheNonceValueExceeds18446744073709551614(nonce) {
42 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.InvalidNonceValue.selector, createXAddr);
43 | vm.expectRevert(expectedErr);
44 | // It should revert.
45 | createX.computeCreateAddress(cachedNonce);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/interface/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "createx-interface",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "Next.js-based user interface for createx.rocks.",
6 | "keywords": [
7 | "frontend",
8 | "interface",
9 | "nextjs",
10 | "react",
11 | "typescript"
12 | ],
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/pcaversaccio/createx.git"
16 | },
17 | "homepage": "https://github.com/pcaversaccio/createx/tree/main/interface#readme",
18 | "bugs": {
19 | "url": "https://github.com/pcaversaccio/createx/issues"
20 | },
21 | "author": "pcaversaccio (https://pcaversaccio.com), Matt Solomon (https://mattsolomon.dev)",
22 | "license": "MIT",
23 | "scripts": {
24 | "dev": "npx next dev",
25 | "build": "npx next build",
26 | "start": "pnpm build && npx next start",
27 | "prettier:check": "npx prettier -c \"**/*.{js,mjs,ts,tsx,css,md,json,yml,yaml}\"",
28 | "prettier:fix": "npx prettier -w \"**/*.{js,mjs,ts,tsx,css,md,json,yml,yaml}\"",
29 | "lint:check": "pnpm prettier:check && npx eslint .",
30 | "lint:fix": "pnpm prettier:fix && npx eslint . --fix"
31 | },
32 | "dependencies": {
33 | "@headlessui/react": "^2.2.9",
34 | "@heroicons/react": "^2.2.0",
35 | "next": "^16.1.1",
36 | "next-themes": "^0.4.6",
37 | "prismjs": "^1.30.0",
38 | "react": "^19.2.3",
39 | "react-dom": "^19.2.3",
40 | "sharp": "^0.34.5"
41 | },
42 | "devDependencies": {
43 | "@eslint/js": "^9.39.2",
44 | "@next/eslint-plugin-next": "^16.1.1",
45 | "@tailwindcss/postcss": "^4.1.18",
46 | "@trivago/prettier-plugin-sort-imports": "^6.0.0",
47 | "@types/node": "^25.0.3",
48 | "@types/react": "^19.2.7",
49 | "@types/react-dom": "^19.2.3",
50 | "autoprefixer": "^10.4.23",
51 | "eslint": "^9.39.2",
52 | "eslint-config-next": "^16.1.1",
53 | "eslint-plugin-react": "^7.37.5",
54 | "eslint-plugin-react-hooks": "^7.0.1",
55 | "globals": "^16.5.0",
56 | "next-seo": "^7.0.1",
57 | "postcss": "^8.5.6",
58 | "prettier": "^3.7.4",
59 | "prettier-plugin-tailwindcss": "^0.7.2",
60 | "tailwindcss": "^4.1.18",
61 | "typescript": "^5.9.3",
62 | "typescript-eslint": "^8.50.1"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.computeCreateAddress_2Args.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {CreateX} from "../../../src/CreateX.sol";
6 |
7 | contract CreateX_ComputeCreateAddress_2Args_Public_Test is BaseTest {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* HELPER VARIABLES */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | uint256 internal cachedNonce;
13 |
14 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
15 | /* TESTS */
16 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
17 |
18 | modifier whenTheNonceValueDoesNotExceed18446744073709551614(address deployer, uint64 nonce) {
19 | vm.assume(nonce < type(uint64).max);
20 | if (deployer.code.length != 0) {
21 | vm.assume(nonce != 0);
22 | }
23 | vm.setNonceUnsafe(deployer, nonce);
24 | _;
25 | }
26 |
27 | function testFuzz_WhenTheNonceValueDoesNotExceed18446744073709551614(
28 | address deployer,
29 | uint64 nonce
30 | ) external whenTheNonceValueDoesNotExceed18446744073709551614(deployer, nonce) {
31 | vm.startPrank(deployer);
32 | address createAddressComputedOnChain = address(new CreateX());
33 | vm.stopPrank();
34 | // It returns the 20-byte address where a contract will be stored.
35 | assertEq(createX.computeCreateAddress(deployer, nonce), createAddressComputedOnChain, "100");
36 | }
37 |
38 | modifier whenTheNonceValueExceeds18446744073709551614(uint256 nonce) {
39 | cachedNonce = bound(nonce, type(uint64).max, type(uint256).max);
40 | _;
41 | }
42 |
43 | function testFuzz_WhenTheNonceValueExceeds18446744073709551614(
44 | address deployer,
45 | uint256 nonce
46 | ) external whenTheNonceValueExceeds18446744073709551614(nonce) {
47 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.InvalidNonceValue.selector, createXAddr);
48 | vm.expectRevert(expectedErr);
49 | // It should revert.
50 | createX.computeCreateAddress(deployer, cachedNonce);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | # Defaults for all profiles.
2 | [profile.default]
3 | src = "src" # Set the source directory.
4 | test = "test" # Set the test directory.
5 | out = "out" # Set the output directory for the artifacts.
6 | libs = ["lib"] # Configure an array of library directories.
7 | cache = true # Enable caching.
8 | cache_path = "cache" # Set the path to the cache.
9 | force = false # Do not ignore the cache.
10 | solc_version = "0.8.23" # Set the Solidity compiler version.
11 | evm_version = "paris" # Set the EVM target version (prevent using the `PUSH0` and `cancun` opcodes).
12 | optimizer = true # Enable the Solidity compiler optimiser.
13 | optimizer_runs = 10_000_000 # Configure the number of optimiser runs.
14 | via_ir = false # Prevent the compilation pipeline from running through the Yul intermediate representation.
15 | bytecode_hash = "none" # Remove the metadata hash from the bytecode.
16 | verbosity = 3 # Set the verbosity level for the tests.
17 | fs_permissions = [{ access = "read-write", path = "./" }] # Configure read-write access to the project root.
18 | fuzz = { runs = 256 } # Configure the number of fuzz runs for the tests.
19 | invariant = { runs = 256, depth = 15 } # Configure the number of runs and calls (executed in one run) for each invariant test group.
20 |
21 | # Default overrides for the CI runs.
22 | [profile.ci]
23 | force = true # Perform always a clean build.
24 | verbosity = 4 # Increase the verbosity level for the tests.
25 | fuzz = { runs = 10_000 } # Increase the number of fuzz runs.
26 | invariant = { runs = 500, depth = 500 } # Increase the number of runs (while preserving the default depth) for each invariant test group.
27 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: 🚀 UI deployment
2 |
3 | on:
4 | push:
5 | branches: ["main"]
6 | workflow_dispatch:
7 |
8 | # Allows only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
9 | # However, we do not cancel in-progress runs as we want to allow these production deployments to complete.
10 | concurrency:
11 | group: "pages"
12 | cancel-in-progress: false
13 |
14 | jobs:
15 | build:
16 | runs-on: ${{ matrix.os }}
17 | strategy:
18 | matrix:
19 | os:
20 | - ubuntu-latest
21 | node_version:
22 | - 24
23 |
24 | steps:
25 | - name: Checkout
26 | uses: actions/checkout@v6
27 |
28 | - name: Install pnpm
29 | uses: pnpm/action-setup@v4
30 | with:
31 | run_install: false
32 |
33 | - name: Get pnpm cache directory path
34 | id: pnpm-cache-dir-path
35 | run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
36 |
37 | - name: Restore pnpm cache
38 | uses: actions/cache@v5
39 | id: pnpm-cache
40 | with:
41 | path: |
42 | ${{ steps.pnpm-cache-dir-path.outputs.dir }}
43 | ${{ github.workspace }}/interface/.next/cache
44 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('interface/**/*.js', 'interface/**/*.jsx', 'interface/**/*.ts', 'interface/**/*.tsx') }}
45 | restore-keys: |
46 | ${{ runner.os }}-pnpm-store-
47 |
48 | - name: Use Node.js ${{ matrix.node_version }}
49 | uses: actions/setup-node@v6
50 | with:
51 | node-version: ${{ matrix.node_version }}
52 |
53 | - name: Install pnpm project with a clean slate
54 | run: pnpm install --prefer-offline --frozen-lockfile
55 |
56 | - name: Build with Next.js
57 | run: pnpm build:interface
58 |
59 | - name: Upload artifacts
60 | uses: actions/upload-pages-artifact@v4
61 | with:
62 | path: "interface/dist"
63 |
64 | deploy:
65 | runs-on: ${{ matrix.os }}
66 | needs: build
67 | environment:
68 | name: github-pages
69 | url: ${{ steps.deployment.outputs.page_url }}
70 | permissions:
71 | pages: write
72 | id-token: write
73 | strategy:
74 | matrix:
75 | os:
76 | - ubuntu-latest
77 |
78 | steps:
79 | - name: Deploy to GitHub Pages
80 | id: deployment
81 | uses: actions/deploy-pages@v4
82 |
--------------------------------------------------------------------------------
/abis/src/CreateX.sol/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 |
--------------------------------------------------------------------------------
/abis/src/ICreateX.sol/ICreateX.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 |
--------------------------------------------------------------------------------
/interface/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { Head } from "@/components/layout/Head";
3 | import { SITE_DESCRIPTION } from "@/lib/constants";
4 |
5 | const Home = () => {
6 | console.log(`
7 | ░██████╗██████╗░███████╗░█████╗░████████╗███████╗██╗░░██╗
8 | ██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝╚██╗██╔╝
9 | ██║░░░░░██████╔╝█████╗░░███████║░░░██║░░░█████╗░░░╚███╔╝░
10 | ██║░░░░░██╔══██╗██╔══╝░░██╔══██║░░░██║░░░██╔══╝░░░██╔██╗░
11 | ╚██████╗██║░░██║███████╗██║░░██║░░░██║░░░███████╗██╔╝░██╗
12 | ░╚═════╝╚═╝░░╚═╝╚══════╝╚═╝░░╚═╝░░░╚═╝░░░╚══════╝╚═╝░░╚═╝
13 | `);
14 | const cards = [
15 | {
16 | id: 1,
17 | href: "/deployments",
18 | title: "Deployments",
19 | subtitle: "Deployed on 170+ chains",
20 | },
21 | { id: 2, href: "/abi", title: "ABI", subtitle: "In any format" },
22 | {
23 | id: 3,
24 | href: "https://github.com/pcaversaccio/createx",
25 | title: "Docs",
26 | subtitle: "Learn more",
27 | },
28 | ];
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 | {SITE_DESCRIPTION}
36 |
37 |
38 |
39 | {cards.map((card) => (
40 |
51 | -
52 | {card.title}
53 |
54 | -
55 | {card.subtitle}
56 |
57 |
58 | ))}
59 |
60 |
61 |
62 | >
63 | );
64 | };
65 |
66 | export default Home;
67 |
--------------------------------------------------------------------------------
/interface/public/prism-dark.css:
--------------------------------------------------------------------------------
1 | /**
2 | * This file was copied from node_modules/prismjs/themes/prism-tomorrow.css and is required for clean
3 | * applying and removing of the prism.js themes when switching between light and dark mode.
4 | */
5 |
6 | /**
7 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
8 | * Based on https://github.com/chriskempson/tomorrow-theme
9 | * @author Rose Pritchard
10 | */
11 |
12 | code[class*="language-"],
13 | pre[class*="language-"] {
14 | color: #ccc;
15 | background: none;
16 | font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
17 | font-size: 1em;
18 | text-align: left;
19 | white-space: pre;
20 | word-spacing: normal;
21 | word-break: normal;
22 | word-wrap: normal;
23 | line-height: 1.5;
24 |
25 | -moz-tab-size: 4;
26 | -o-tab-size: 4;
27 | tab-size: 4;
28 |
29 | -webkit-hyphens: none;
30 | -moz-hyphens: none;
31 | -ms-hyphens: none;
32 | hyphens: none;
33 | }
34 |
35 | /* Code blocks */
36 | pre[class*="language-"] {
37 | padding: 1em;
38 | margin: 0.5em 0;
39 | overflow: auto;
40 | }
41 |
42 | :not(pre) > code[class*="language-"],
43 | pre[class*="language-"] {
44 | background: #2d2d2d;
45 | }
46 |
47 | /* Inline code */
48 | :not(pre) > code[class*="language-"] {
49 | padding: 0.1em;
50 | border-radius: 0.3em;
51 | white-space: normal;
52 | }
53 |
54 | .token.comment,
55 | .token.block-comment,
56 | .token.prolog,
57 | .token.doctype,
58 | .token.cdata {
59 | color: #999;
60 | }
61 |
62 | .token.punctuation {
63 | color: #ccc;
64 | }
65 |
66 | .token.tag,
67 | .token.attr-name,
68 | .token.namespace,
69 | .token.deleted {
70 | color: #e2777a;
71 | }
72 |
73 | .token.function-name {
74 | color: #6196cc;
75 | }
76 |
77 | .token.boolean,
78 | .token.number,
79 | .token.function {
80 | color: #f08d49;
81 | }
82 |
83 | .token.property,
84 | .token.class-name,
85 | .token.constant,
86 | .token.symbol {
87 | color: #f8c555;
88 | }
89 |
90 | .token.selector,
91 | .token.important,
92 | .token.atrule,
93 | .token.keyword,
94 | .token.builtin {
95 | color: #cc99cd;
96 | }
97 |
98 | .token.string,
99 | .token.char,
100 | .token.attr-value,
101 | .token.regex,
102 | .token.variable {
103 | color: #7ec699;
104 | }
105 |
106 | .token.operator,
107 | .token.entity,
108 | .token.url {
109 | color: #67cdcc;
110 | }
111 |
112 | .token.important,
113 | .token.bold {
114 | font-weight: bold;
115 | }
116 | .token.italic {
117 | font-style: italic;
118 | }
119 |
120 | .token.entity {
121 | cursor: help;
122 | }
123 |
124 | .token.inserted {
125 | color: green;
126 | }
127 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # 🛡️ Security Policy
2 |
3 | ## 📬 Reporting a Vulnerability
4 |
5 | Please report any security issues you find to [pascal.caversaccio@hotmail.ch](mailto:pascal.caversaccio@hotmail.ch). My public PGP key is:
6 |
7 | ```sh
8 | -----BEGIN PGP PUBLIC KEY BLOCK-----
9 | Comment: Type: 3,072-bit RSA
10 | Comment: Fingerprint: 063E966C93AB4356492FE0327C3B4B4B7725111F
11 |
12 |
13 | mQGNBGBUv08BDADTYA0GjLhbKbCezlVAubakXh0jcIbkqZPF1wueSbSgDjlS6+d8
14 | 67V6ft4hNXJhpNxqr07LrcbUEDdB7WK8EUA9qsLtVRznR/B8y2HwrFs7jbYAUzl6
15 | lZ6UgzXl2QCeKI3B3foa7aGDeBkm1um3zXlR4+b8d4krO8pZTJepC5T+UF3C81Kb
16 | lV+6s+bSsHPtLHwBh+tJtSFF7hQoU1lhVW0hKVGUUwGfoFuYjWh47fLtiEvvtM2e
17 | EUZ/0v9nMTKg+tuk4nrR7J+ARdDxaqDWLNTwzGvuTAgkjw6I+zrzFmgsAbdFLFKE
18 | e4+wzE/fb3BAWVRDruhNJmSLqTpkLYyoLkf5GAf9X1gAdeCbiwBgeRnF4m8gLUul
19 | m7mixTW45TOAm9upCZaYWhYQpHosix5NLaBAGMCaTgzJS4Ij8BGh6Td+vv2RYIW+
20 | j8tfg6uwGXYsclk609bXk6Z2S+DZalNtbSffwCKXqAU4jO194JYL471tB+OvkF6g
21 | KciJZOs+OB0fZc8AEQEAAbQ4UGFzY2FsIE1hcmNvIENhdmVyc2FjY2lvIDxwYXNj
22 | YWwuY2F2ZXJzYWNjaW9AaG90bWFpbC5jaD6JAc4EEwEIADgCGwMFCwkIBwIGFQoJ
23 | CAsCBBYCAwECHgECF4AWIQQGPpZsk6tDVkkv4DJ8O0tLdyURHwUCYvqMQAAKCRB8
24 | O0tLdyURH85gC/oCX3nn+TK/t/tkiOzVnPlCECbrayQlIEgenE7s+oHP6XouhtWJ
25 | UOq7vyssZ+QPqMVNXsXzQrzlf52KVUTHtaSysus4l5lMX1Uuj+Mzy/vAEnPi2+Qo
26 | XZE3xHv+H/kFnqnM6U4MB9OCvOC5fz/cx28141w7dbFTqquBJPIb0DUSTtWpIcqV
27 | zr0sHx59+BMyB3gNiaT6KXmcBCrLlszySuEtw9MMfrfBYuNUXQv2efl3ZjVaN5Ah
28 | LErIZwlJG1OJ+l5WPbPEceh5k7BIqksNBB6T5kkgNbsju22Bc8t2NkxKRHzUmwMz
29 | p1Z30kNNtLmkv3M+ZQZYyKtMGhFf9cll6x/Z8NATEo/rfzbUdlmmodl0Zt07Yla4
30 | jJ23oRSzgzsqiwyN3PA+iOtkNeR/UfrWFKss5RY+B5QTH/GSHvd5yKB7AWjTo2mM
31 | oo1uydwlboN6EfiK10+4sveVG/DdQIVkiu2wAYsS3UL7V0q5Yava0wQmhqNeyWcM
32 | 8xRq/K0pbJk8rwy5AY0EYFS/TwEMALgHOcvmfAc12vcYA+RjtLZPoqIBzPsyvEJi
33 | ryq45AdpSDKa+xLWXIyGCKdlaEZ0Ts9rGtXhW4Sg/YWZhBPbOftu5fsp8d9NWKTD
34 | Pbomob7UeAj9jxS8GCArkG0L6IagH4LopbKtOmh5JjavCjIWe3rRNe1hA1jVa2ES
35 | QSPfotHPm8728eO1lyOdGgukThvS3El5cUcwY0isAFKq6Z8JL2gawtR1J4ceQQGu
36 | b9PFUeSrWe05nQlgXTyZUyN6RPBTKAQwO0zEAcYhLMEizzfuiTOKQTUw5pDwFgEw
37 | nQ2tI+SYui+GTBlmd0XzBlPlgQMSCvSLZG4Vg8nK0hJdqxHadtKmnew9+vrcj4bP
38 | UG0qFesg9hbdj6UzKHUZL0Y76jNrgKwXFGmQJP0L+y+XqJGIX39ugOcccjntH7Ct
39 | xU1/Eqmcv3K0CXXAYu3OirVF51X72ynTTI5xcxHgSBGSPjPS57cNmqm6YslZ9NCE
40 | 4RDw9wtjzWmnUGQDK0nxY0X/t3pt9QARAQABiQG2BBgBCAAgAhsMFiEEBj6WbJOr
41 | Q1ZJL+AyfDtLS3clER8FAmX5RC0ACgkQfDtLS3clER+UAQv9Gzcb4Tu7ILduVOeZ
42 | /X0HQ0WTSngMV3kEtUGIriMJK/JvAqARrw+b/NMYqiGeIAxhGi6OGRorcn69oQHU
43 | 6DuEh5PMEH1rjp3yqoP12LNXJIFgBGSrvnp7adguxHqBAAfYk2PKId0pRoAiDQdh
44 | X2P0di47SnDyZ/I7dUVpHsrDLZ1OPZx+l5l+IblhGkDlzdMITqtdrYzZpmhJRVJG
45 | rSlRPB1qHyVQ6KLpYwKFcQOjbY+HrDTSjQxclS18I5TDSEz7/SOTyup4riAFxOc8
46 | qJSLrgBmKqeEuGn1MKyBMTSx+yUQYRzIzc9/gpKAjv7LMFYp6zHHZQx17QOAlOi/
47 | tFyq1dUnUayvwx0USoztbNM1Etv1nn9v2+QsTT5omtDc/YsgmfG3hmUIz5MgPof7
48 | Ty5HlJbxax7AZEG6StNNGr77+w541SYxiyE8fVxrxtb70OUcmgVF/okaT5m4xFBK
49 | 2NMhuVpgP0fQXk98ALYofKHraidGA1NQQKzFb5AjwQqrzgo0
50 | =DwWq
51 | -----END PGP PUBLIC KEY BLOCK-----
52 | ```
53 |
--------------------------------------------------------------------------------
/test/internal/CreateX._requireSuccessfulContractCreation_1Arg.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../utils/BaseTest.sol";
5 | import {CreateX} from "../../src/CreateX.sol";
6 |
7 | contract CreateX_RequireSuccessfulContractCreation_1Arg_Internal_Test is BaseTest {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* TESTS */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | modifier whenTheNewContractAddressIsTheZeroAddress() {
13 | _;
14 | }
15 |
16 | function test_WhenTheNewContractAddressIsTheZeroAddress() external whenTheNewContractAddressIsTheZeroAddress {
17 | // It should revert.
18 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXHarnessAddr);
19 | vm.expectRevert(expectedErr);
20 | createXHarness.exposed_requireSuccessfulContractCreation(zeroAddress);
21 | }
22 |
23 | modifier whenTheNewContractAddressIsNotTheZeroAddress(address newContract) {
24 | vm.assume(newContract != zeroAddress);
25 | _;
26 | }
27 |
28 | modifier whenTheNewContractAddressHasNoCode(address newContract) {
29 | vm.assume(newContract != createXHarnessAddr); // Avoid removing the code of the contract under test.
30 | assumeAddressIsNot(newContract, AddressType.ForgeAddress);
31 | // If the new contract address has code, remove the code. This is faster than `vm.assume`.
32 | if (newContract.code.length != 0) {
33 | vm.etch(newContract, "");
34 | }
35 | _;
36 | }
37 |
38 | function testFuzz_WhenTheNewContractAddressHasNoCode(
39 | address newContract
40 | )
41 | external
42 | whenTheNewContractAddressIsNotTheZeroAddress(newContract)
43 | whenTheNewContractAddressHasNoCode(newContract)
44 | {
45 | // It should revert.
46 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXHarnessAddr);
47 | vm.expectRevert(expectedErr);
48 | createXHarness.exposed_requireSuccessfulContractCreation(newContract);
49 | }
50 |
51 | modifier whenTheNewContractAddressHasCode(address newContract) {
52 | assumeAddressIsNot(newContract, AddressType.ForgeAddress, AddressType.Precompile);
53 | // If the new contract address has no code, etch some. This is faster than `vm.assume`.
54 | if (newContract.code.length == 0) {
55 | vm.etch(newContract, "01");
56 | }
57 | _;
58 | }
59 |
60 | function testFuzz_WhenTheNewContractAddressHasCode(
61 | address newContract
62 | ) external whenTheNewContractAddressIsNotTheZeroAddress(newContract) whenTheNewContractAddressHasCode(newContract) {
63 | // It should never revert. We do not need any assertions to test this.
64 | createXHarness.exposed_requireSuccessfulContractCreation(newContract);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/.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 | - 24
18 |
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v6
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@v5
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@v6
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: |
51 | pnpm lint:check
52 | pnpm lint:check:interface
53 |
54 | codespell:
55 | runs-on: ${{ matrix.os }}
56 | strategy:
57 | matrix:
58 | os:
59 | - ubuntu-latest
60 |
61 | steps:
62 | - name: Checkout
63 | uses: actions/checkout@v6
64 |
65 | - name: Run codespell
66 | uses: codespell-project/actions-codespell@v2
67 | with:
68 | check_filenames: true
69 | skip: ./.git,pnpm-lock.yaml
70 | ignore_words_list: superseed
71 |
72 | validate-links:
73 | runs-on: ${{ matrix.os }}
74 | strategy:
75 | matrix:
76 | os:
77 | - ubuntu-latest
78 | ruby_version:
79 | - 3.4
80 |
81 | steps:
82 | - name: Checkout
83 | uses: actions/checkout@v6
84 |
85 | - name: Set up Ruby
86 | uses: ruby/setup-ruby@v1
87 | with:
88 | ruby-version: ${{ matrix.ruby_version }}
89 | bundler-cache: true
90 |
91 | - name: Install awesome_bot
92 | run: gem install awesome_bot
93 |
94 | - name: Validate URLs
95 | run: |
96 | awesome_bot ./*.md src/*.sol test/**/*.sol test/public/**/*.sol interface/*.md interface/public/*.css interface/src/**/*.css interface/src/**/*.tsx interface/src/**/*.ts interface/src/components/**/*.tsx deployments/*.json \
97 | --allow-dupe --request-delay 0.4 \
98 | --white-list "https://github.com/pcaversaccio/createx/issues/new?assignees=pcaversaccio&labels=new+deployment+%E2%9E%95&projects=&template=deployment_request.yml&title=%5BNew-Deployment-Request%5D%3A+",https://foundry.paradigm.xyz,https://github.com/pcaversaccio/createx.git,https://x.com,https://dune.com
99 |
--------------------------------------------------------------------------------
/test/internal/CreateX._requireSuccessfulContractInitialisation.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../utils/BaseTest.sol";
5 | import {CreateX} from "../../src/CreateX.sol";
6 |
7 | contract CreateX_RequireSuccessfulContractInitialisation_Internal_Test is BaseTest {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* TESTS */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | modifier whenTheSuccessBooleanIsFalse() {
13 | _;
14 | }
15 |
16 | function testFuzz_WhenTheSuccessBooleanIsFalse(
17 | bytes calldata returnData,
18 | address implementation
19 | ) external whenTheSuccessBooleanIsFalse {
20 | // It should revert.
21 | bytes memory expectedErr = abi.encodeWithSelector(
22 | CreateX.FailedContractInitialisation.selector,
23 | createXHarnessAddr,
24 | returnData
25 | );
26 | vm.expectRevert(expectedErr);
27 | createXHarness.exposed_requireSuccessfulContractInitialisation(false, returnData, implementation);
28 | }
29 |
30 | modifier whenTheSuccessBooleanIsTrue() {
31 | _;
32 | }
33 |
34 | modifier whenTheImplementationAddressHasNoCode(address implementation) {
35 | vm.assume(implementation != createXHarnessAddr); // Avoid removing the code of the contract under test.
36 | assumeAddressIsNot(implementation, AddressType.ForgeAddress);
37 |
38 | // If the new contract address has code, remove the code. This is faster than `vm.assume`.
39 | if (implementation.code.length != 0) {
40 | vm.etch(implementation, "");
41 | }
42 | _;
43 | }
44 |
45 | function testFuzz_WhenTheImplementationAddressHasNoCode(
46 | bytes calldata returnData,
47 | address implementation
48 | ) external whenTheSuccessBooleanIsTrue whenTheImplementationAddressHasNoCode(implementation) {
49 | // It should revert.
50 | bytes memory expectedErr = abi.encodeWithSelector(
51 | CreateX.FailedContractInitialisation.selector,
52 | createXHarnessAddr,
53 | returnData
54 | );
55 | vm.expectRevert(expectedErr);
56 | createXHarness.exposed_requireSuccessfulContractInitialisation(true, returnData, implementation);
57 | }
58 |
59 | modifier whenTheImplementationAddressHasCode(address implementation) {
60 | assumeAddressIsNot(implementation, AddressType.ForgeAddress, AddressType.Precompile);
61 |
62 | // If the new contract address has no code, etch some. This is faster than `vm.assume`.
63 | if (implementation.code.length == 0) {
64 | vm.etch(implementation, "01");
65 | }
66 | _;
67 | }
68 |
69 | function testFuzz_WhenTheImplementationAddressHasCode(
70 | bytes calldata returnData,
71 | address implementation
72 | ) external whenTheSuccessBooleanIsTrue whenTheImplementationAddressHasCode(implementation) {
73 | // It should never revert.
74 | createXHarness.exposed_requireSuccessfulContractInitialisation(true, returnData, implementation);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/interface/public/prism-light.css:
--------------------------------------------------------------------------------
1 | /**
2 | * This file was copied from node_modules/prismjs/themes/prism.css and is required for clean
3 | * applying and removing of the prism.js themes when switching between light and dark mode.
4 | */
5 |
6 | /**
7 | * prism.js default theme for JavaScript, CSS and HTML
8 | * Based on dabblet (https://dabblet.com/)
9 | * @author Lea Verou
10 | */
11 |
12 | code[class*="language-"],
13 | pre[class*="language-"] {
14 | color: black;
15 | background: none;
16 | text-shadow: 0 1px white;
17 | font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
18 | font-size: 1em;
19 | text-align: left;
20 | white-space: pre;
21 | word-spacing: normal;
22 | word-break: normal;
23 | word-wrap: normal;
24 | line-height: 1.5;
25 |
26 | -moz-tab-size: 4;
27 | -o-tab-size: 4;
28 | tab-size: 4;
29 |
30 | -webkit-hyphens: none;
31 | -moz-hyphens: none;
32 | -ms-hyphens: none;
33 | hyphens: none;
34 | }
35 |
36 | pre[class*="language-"]::-moz-selection,
37 | pre[class*="language-"] ::-moz-selection,
38 | code[class*="language-"]::-moz-selection,
39 | code[class*="language-"] ::-moz-selection {
40 | text-shadow: none;
41 | background: #b3d4fc;
42 | }
43 |
44 | pre[class*="language-"]::selection,
45 | pre[class*="language-"] ::selection,
46 | code[class*="language-"]::selection,
47 | code[class*="language-"] ::selection {
48 | text-shadow: none;
49 | background: #b3d4fc;
50 | }
51 |
52 | @media print {
53 | code[class*="language-"],
54 | pre[class*="language-"] {
55 | text-shadow: none;
56 | }
57 | }
58 |
59 | /* Code blocks */
60 | pre[class*="language-"] {
61 | padding: 1em;
62 | margin: 0.5em 0;
63 | overflow: auto;
64 | }
65 |
66 | :not(pre) > code[class*="language-"],
67 | pre[class*="language-"] {
68 | background: #f5f2f0;
69 | }
70 |
71 | /* Inline code */
72 | :not(pre) > code[class*="language-"] {
73 | padding: 0.1em;
74 | border-radius: 0.3em;
75 | white-space: normal;
76 | }
77 |
78 | .token.comment,
79 | .token.prolog,
80 | .token.doctype,
81 | .token.cdata {
82 | color: slategray;
83 | }
84 |
85 | .token.punctuation {
86 | color: #999;
87 | }
88 |
89 | .token.namespace {
90 | opacity: 0.7;
91 | }
92 |
93 | .token.property,
94 | .token.tag,
95 | .token.boolean,
96 | .token.number,
97 | .token.constant,
98 | .token.symbol,
99 | .token.deleted {
100 | color: #905;
101 | }
102 |
103 | .token.selector,
104 | .token.attr-name,
105 | .token.string,
106 | .token.char,
107 | .token.builtin,
108 | .token.inserted {
109 | color: #690;
110 | }
111 |
112 | .token.operator,
113 | .token.entity,
114 | .token.url,
115 | .language-css .token.string,
116 | .style .token.string {
117 | color: #9a6e3a;
118 | /* This background color was intended by the author of this theme. */
119 | background: hsla(0, 0%, 100%, 0.5);
120 | }
121 |
122 | .token.atrule,
123 | .token.attr-value,
124 | .token.keyword {
125 | color: #07a;
126 | }
127 |
128 | .token.function,
129 | .token.class-name {
130 | color: #dd4a68;
131 | }
132 |
133 | .token.regex,
134 | .token.important,
135 | .token.variable {
136 | color: #e90;
137 | }
138 |
139 | .token.important,
140 | .token.bold {
141 | font-weight: bold;
142 | }
143 | .token.italic {
144 | font-style: italic;
145 | }
146 |
147 | .token.entity {
148 | cursor: help;
149 | }
150 |
--------------------------------------------------------------------------------
/interface/src/components/layout/Header.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import Link from "next/link";
3 | import { useRouter } from "next/router";
4 | import { ClipboardDocumentIcon } from "@heroicons/react/24/solid";
5 | import { Notification } from "@/components/ui/Notification";
6 | import { COMPANY_NAME, CREATEX_ADDRESS, SITE_NAME } from "@/lib/constants";
7 | import { copyToClipboard } from "@/lib/utils";
8 |
9 | const NavLink = ({
10 | path,
11 | label,
12 | currentPath,
13 | className,
14 | }: {
15 | path: string;
16 | label: string;
17 | currentPath: string;
18 | className?: string;
19 | }) => {
20 | const activeClass = path === currentPath ? "font-bold" : "";
21 | return (
22 |
27 | {label}
28 |
29 | );
30 | };
31 |
32 | export const Header = () => {
33 | const currentPath = useRouter().pathname;
34 |
35 | // -------- Copy Address to Clipboard --------
36 | const [showNotification, setShowNotification] = useState(false);
37 |
38 | const onCopy = (text: string) => {
39 | copyToClipboard(text);
40 | setShowNotification(true);
41 | setTimeout(() => setShowNotification(false), 3000);
42 | };
43 |
44 | // -------- Render --------
45 | return (
46 |
47 |
53 |
54 |
55 |
56 |
57 |
58 | {COMPANY_NAME}
59 |
60 | {SITE_NAME}
61 |
62 |
63 |
64 |
65 |
71 |
77 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | Deployment Address
90 |
91 |
92 |
93 | {CREATEX_ADDRESS}
94 |
95 |
onCopy(CREATEX_ADDRESS)}
98 | />
99 |
100 |
101 |
102 |
103 | );
104 | };
105 |
--------------------------------------------------------------------------------
/interface/src/components/layout/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { ExternalLink } from "@/components/layout/ExternalLink";
2 | import { ThemeSwitcher } from "@/components/layout/ThemeSwitcher";
3 | import {
4 | COMPANY_NAME,
5 | COMPANY_URL,
6 | GITHUB_URL,
7 | LICENSE_NAME,
8 | LICENSE_URL,
9 | X_URL,
10 | } from "@/lib/constants";
11 |
12 | const navigation = [
13 | {
14 | name: "X",
15 | href: X_URL,
16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
17 | icon: (props: any) => (
18 |
39 | ),
40 | },
41 | {
42 | name: "GitHub",
43 | href: GITHUB_URL,
44 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
45 | icon: (props: any) => (
46 |
53 | ),
54 | },
55 | ];
56 |
57 | export const Footer = () => {
58 | const currentYear = new Date().getFullYear();
59 | return (
60 |
89 | );
90 | };
91 |
--------------------------------------------------------------------------------
/test/internal/CreateX._requireSuccessfulContractCreation_2Args.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../utils/BaseTest.sol";
5 | import {CreateX} from "../../src/CreateX.sol";
6 |
7 | contract CreateX_RequireSuccessfulContractCreation_2Args_Internal_Test is BaseTest {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* TESTS */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | modifier whenTheSuccessBooleanIsFalse() {
13 | _;
14 | }
15 |
16 | function testFuzz_WhenTheSuccessBooleanIsFalse(address newContract) external whenTheSuccessBooleanIsFalse {
17 | // It should revert.
18 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXHarnessAddr);
19 | vm.expectRevert(expectedErr);
20 | createXHarness.exposed_requireSuccessfulContractCreation(false, newContract);
21 | }
22 |
23 | modifier whenTheSuccessBooleanIsTrue() {
24 | _;
25 | }
26 |
27 | modifier whenTheNewContractAddressIsTheZeroAddress() {
28 | _;
29 | }
30 |
31 | function test_WhenTheNewContractAddressIsTheZeroAddress()
32 | external
33 | whenTheSuccessBooleanIsTrue
34 | whenTheNewContractAddressIsTheZeroAddress
35 | {
36 | // It should revert.
37 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXHarnessAddr);
38 | vm.expectRevert(expectedErr);
39 | createXHarness.exposed_requireSuccessfulContractCreation(true, zeroAddress);
40 | }
41 |
42 | modifier whenTheNewContractAddressIsNotTheZeroAddress(address newContract) {
43 | vm.assume(newContract != zeroAddress);
44 | _;
45 | }
46 |
47 | modifier whenTheNewContractAddressHasNoCode(address newContract) {
48 | vm.assume(newContract != createXHarnessAddr); // Avoid removing the code of the contract under test.
49 | assumeAddressIsNot(newContract, AddressType.ForgeAddress);
50 |
51 | // If the new contract address has code, remove the code. This is faster than `vm.assume`.
52 | if (newContract.code.length != 0) {
53 | vm.etch(newContract, "");
54 | }
55 | _;
56 | }
57 |
58 | function testFuzz_WhenTheNewContractAddressHasNoCode(
59 | bool success,
60 | address newContract
61 | )
62 | external
63 | whenTheSuccessBooleanIsTrue
64 | whenTheNewContractAddressIsNotTheZeroAddress(newContract)
65 | whenTheNewContractAddressHasNoCode(newContract)
66 | {
67 | // It should revert.
68 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXHarnessAddr);
69 | vm.expectRevert(expectedErr);
70 | createXHarness.exposed_requireSuccessfulContractCreation(success, newContract);
71 | }
72 |
73 | modifier whenTheNewContractAddressHasCode(address newContract) {
74 | assumeAddressIsNot(newContract, AddressType.ForgeAddress, AddressType.Precompile);
75 |
76 | // If the new contract address has no code, etch some. This is faster than `vm.assume`.
77 | if (newContract.code.length == 0) {
78 | vm.etch(newContract, "01");
79 | }
80 | _;
81 | }
82 |
83 | function testFuzz_WhenTheNewContractAddressHasCode(
84 | address newContract
85 | )
86 | external
87 | whenTheSuccessBooleanIsTrue
88 | whenTheNewContractAddressIsNotTheZeroAddress(newContract)
89 | whenTheNewContractAddressHasCode(newContract)
90 | {
91 | // It should never revert.
92 | createXHarness.exposed_requireSuccessfulContractCreation(true, newContract);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/interface/src/components/ui/Notification.tsx:
--------------------------------------------------------------------------------
1 | import { Transition } from "@headlessui/react";
2 | import {
3 | CheckCircleIcon,
4 | ExclamationCircleIcon,
5 | InformationCircleIcon,
6 | XCircleIcon,
7 | } from "@heroicons/react/24/outline";
8 | import { XMarkIcon } from "@heroicons/react/24/solid";
9 |
10 | function getKindInfo(kind: "success" | "warning" | "error" | "info") {
11 | if (kind === "success")
12 | return {
13 | icon: CheckCircleIcon,
14 | iconColor: "text-green-500 dark:text-green-400",
15 | };
16 | if (kind === "warning")
17 | return {
18 | icon: ExclamationCircleIcon,
19 | iconColor: "text-yellow-500 dark:text-yellow-400",
20 | };
21 | if (kind === "error")
22 | return { icon: XCircleIcon, iconColor: "text-red-600 dark:text-red-500" };
23 | return {
24 | icon: InformationCircleIcon,
25 | iconColor: "text-blue-500 dark:text-blue-400",
26 | };
27 | }
28 |
29 | interface Props {
30 | show: boolean;
31 | setShow: (show: boolean) => void;
32 | kind: "success" | "warning" | "error" | "info";
33 | title: string;
34 | description?: string;
35 | }
36 |
37 | export const Notification = ({
38 | show,
39 | setShow,
40 | kind = "info",
41 | title,
42 | description,
43 | }: Props) => {
44 | const { icon: Icon, iconColor } = getKindInfo(kind);
45 |
46 | const bgColor = "bg-gray-50 dark:bg-gray-800";
47 | const titleTextColor = "text-primary";
48 | const descriptionTextColor = "text-secondary";
49 |
50 | return (
51 | <>
52 | {/* Global notification live region, render this permanently at the end of the document */}
53 |
57 |
58 | {/* Notification panel, dynamically insert this into the live region when it needs to be displayed */}
59 |
68 |
71 |
72 |
73 |
74 |
78 |
79 |
80 |
81 | {title}
82 |
83 | {description && (
84 |
85 | {description}
86 |
87 | )}
88 |
89 |
90 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | >
108 | );
109 | };
110 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.deployCreate.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
6 | import {ERC20MockPayable} from "../../mocks/ERC20MockPayable.sol";
7 | import {CreateX} from "../../../src/CreateX.sol";
8 |
9 | contract CreateX_DeployCreate_Public_Test is BaseTest {
10 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
11 | /* TESTS */
12 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
13 |
14 | function setUp() public override {
15 | BaseTest.setUp();
16 | arg1 = "MyToken";
17 | arg2 = "MTKN";
18 | arg3 = makeAddr("initialAccount");
19 | arg4 = 100;
20 | args = abi.encode(arg1, arg2, arg3, arg4);
21 | cachedInitCode = abi.encodePacked(type(ERC20MockPayable).creationCode, args);
22 | }
23 |
24 | modifier whenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithANonZeroLength() {
25 | if (cachedInitCode.length == 0) {
26 | revert ZeroByteInitCode(SELF);
27 | }
28 | _;
29 | }
30 |
31 | function testFuzz_WhenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithANonZeroLength(
32 | uint64 nonce,
33 | uint256 msgValue
34 | ) external whenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithANonZeroLength {
35 | vm.assume(nonce != 0 && nonce < type(uint64).max);
36 | vm.setNonceUnsafe(createXAddr, nonce);
37 | msgValue = bound(msgValue, 0, type(uint64).max);
38 | // We calculate the address beforehand where the contract is to be deployed.
39 | address computedAddress = createX.computeCreateAddress(createXAddr, nonce);
40 |
41 | // We also check for the ERC-20 standard `Transfer` event.
42 | vm.expectEmit(true, true, true, true, computedAddress);
43 | emit IERC20.Transfer(zeroAddress, arg3, arg4);
44 | // It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
45 | // It emits the event `ContractCreation` with the contract address as indexed argument.
46 | vm.expectEmit(true, true, true, true, createXAddr);
47 | emit CreateX.ContractCreation(computedAddress);
48 | address newContract = createX.deployCreate{value: msgValue}(cachedInitCode);
49 |
50 | assertEq(newContract, computedAddress, "100");
51 | assertNotEq(newContract, zeroAddress, "200");
52 | assertNotEq(newContract.code.length, 0, "300");
53 | assertEq(newContract.balance, msgValue, "400");
54 | assertEq(createXAddr.balance, 0, "500");
55 | assertEq(ERC20MockPayable(computedAddress).name(), arg1, "600");
56 | assertEq(ERC20MockPayable(computedAddress).symbol(), arg2, "700");
57 | assertEq(ERC20MockPayable(computedAddress).balanceOf(arg3), arg4, "800");
58 | }
59 |
60 | modifier whenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithAZeroLength() {
61 | _;
62 | }
63 |
64 | function testFuzz_WhenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithAZeroLength(
65 | uint64 nonce,
66 | uint256 msgValue
67 | ) external whenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithAZeroLength {
68 | vm.assume(nonce != 0 && nonce < type(uint64).max);
69 | vm.setNonceUnsafe(createXAddr, nonce);
70 | msgValue = bound(msgValue, 0, type(uint64).max);
71 | // It should revert.
72 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXAddr);
73 | vm.expectRevert(expectedErr);
74 | createX.deployCreate{value: msgValue}(new bytes(0));
75 | }
76 |
77 | modifier whenTheInitCodeFailsToDeployARuntimeBytecode() {
78 | _;
79 | }
80 |
81 | function testFuzz_WhenTheInitCodeFailsToDeployARuntimeBytecode(
82 | uint64 nonce,
83 | uint256 msgValue
84 | ) external whenTheInitCodeFailsToDeployARuntimeBytecode {
85 | vm.assume(nonce != 0 && nonce < type(uint64).max);
86 | vm.setNonceUnsafe(createXAddr, nonce);
87 | msgValue = bound(msgValue, 0, type(uint64).max);
88 | // The following contract creation code contains the invalid opcode `PUSH0` (`0x5F`) and `CREATE` must therefore
89 | // return the zero address (technically zero bytes `0x`), as the deployment fails. This test also ensures that if
90 | // we ever accidentally change the EVM version in Foundry and Hardhat, we will always have a corresponding failed test.
91 | bytes memory invalidInitCode = hex"5f_80_60_09_3d_39_3d_f3";
92 | // It should revert.
93 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXAddr);
94 | vm.expectRevert(expectedErr);
95 | createX.deployCreate{value: msgValue}(invalidInitCode);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/test/internal/CreateX._generateSalt.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../utils/BaseTest.sol";
5 |
6 | contract CreateX_GenerateSalt_Internal_Test is BaseTest {
7 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
8 | /* HELPER FUNCTIONS */
9 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
10 |
11 | /**
12 | * @dev Generates a 32-byte entropy value using the `keccak256` hashing algorithm.
13 | * @param seed The 32-byte seed value to generate the entropy value.
14 | * @param i The 32-byte increment value to further increase the randomness.
15 | * @return randomness The generated 32-byte entropy value.
16 | */
17 | function entropy(uint256 seed, uint256 i) internal pure returns (uint256 randomness) {
18 | randomness = uint256(keccak256(abi.encodePacked(seed, i)));
19 | }
20 |
21 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
22 | /* TESTS */
23 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
24 |
25 | function testFuzz_ShouldBeAFunctionOfMultipleBlockPropertiesAndTheCaller(
26 | uint256 increment,
27 | address coinbase,
28 | string calldata prevrandao,
29 | uint64 chainId,
30 | address msgSender
31 | ) external {
32 | // It should be a function of multiple block properties and the caller.
33 | // The full set of dependencies is:
34 | // - blockhash(block.number - 32),
35 | // - block.coinbase,
36 | // - block.number,
37 | // - block.timestamp,
38 | // - block.prevrandao,
39 | // - block.chainid,
40 | // - msg.sender.
41 | // We test their dependencies by determining the current salt, changing any of those
42 | // values, and verifying that the salt changes.
43 | increment = bound(increment, 1, type(uint64).max - 100);
44 | vm.assume(coinbase != zeroAddress && chainId != block.chainid && chainId != 0 && msgSender != SELF);
45 | uint256 snapshotId = vm.snapshotState();
46 | bytes32 originalSalt = createXHarness.exposed_generateSalt();
47 |
48 | // Change block. Block number and hash are coupled, so we can't isolate this.
49 | vm.roll(vm.getBlockNumber() + increment);
50 | assertNotEq(originalSalt, createXHarness.exposed_generateSalt(), "100");
51 |
52 | // Change coinbase.
53 | vm.revertToState(snapshotId);
54 | assertEq(createXHarness.exposed_generateSalt(), originalSalt, "200");
55 |
56 | vm.coinbase(coinbase);
57 | assertNotEq(originalSalt, createXHarness.exposed_generateSalt(), "300");
58 |
59 | // Change timestamp.
60 | vm.revertToState(snapshotId);
61 | assertEq(createXHarness.exposed_generateSalt(), originalSalt, "400");
62 |
63 | vm.warp(vm.getBlockTimestamp() + increment);
64 | assertNotEq(originalSalt, createXHarness.exposed_generateSalt(), "500");
65 |
66 | // Change prevrandao.
67 | vm.revertToState(snapshotId);
68 | assertEq(createXHarness.exposed_generateSalt(), originalSalt, "600");
69 |
70 | vm.prevrandao(keccak256(abi.encode(prevrandao)));
71 | assertNotEq(originalSalt, createXHarness.exposed_generateSalt(), "700");
72 |
73 | // Change chain ID.
74 | vm.revertToState(snapshotId);
75 | assertEq(createXHarness.exposed_generateSalt(), originalSalt, "800");
76 |
77 | vm.chainId(chainId);
78 | assertNotEq(originalSalt, createXHarness.exposed_generateSalt(), "900");
79 |
80 | // Change sender.
81 | vm.revertToState(snapshotId);
82 | assertEq(createXHarness.exposed_generateSalt(), originalSalt, "1000");
83 |
84 | vm.startPrank(msgSender);
85 | assertNotEq(originalSalt, createXHarness.exposed_generateSalt(), "1100");
86 | vm.stopPrank();
87 | }
88 |
89 | function testFuzz_NeverReverts(uint256 seed) external {
90 | // It should never revert.
91 | // We derive all our salt properties from the seed and ensure that it never reverts.
92 | // First, we generate all the entropy.
93 | uint256 entropy1 = entropy(seed, 1);
94 | uint256 entropy2 = entropy(seed, 2);
95 | uint256 entropy3 = entropy(seed, 3);
96 | uint256 entropy4 = entropy(seed, 4);
97 | uint256 entropy5 = entropy(seed, 5);
98 | uint256 entropy6 = entropy(seed, 6);
99 |
100 | // Second, we set the block properties.
101 | vm.roll(bound(entropy1, vm.getBlockNumber(), 1e18));
102 | vm.coinbase(address(uint160(entropy2)));
103 | vm.warp(bound(entropy3, vm.getBlockTimestamp(), 52e4 weeks));
104 | vm.prevrandao(bytes32(entropy4));
105 | vm.chainId(bound(entropy5, 0, type(uint64).max));
106 |
107 | // Third, we verify that it doesn't revert by calling it.
108 | vm.startPrank(address(uint160(entropy6)));
109 | createXHarness.exposed_generateSalt();
110 | vm.stopPrank();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/scripts/presign.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import path from "path";
3 | import hre from "hardhat";
4 |
5 | import { ethMainnetUrl, accounts } from "../hardhat.config";
6 |
7 | const RESET = "\x1b[0m";
8 | const GREEN = "\x1b[32m";
9 | const RED = "\x1b[31m";
10 |
11 | import initCode from "./contract_creation_bytecode_createx.json";
12 | const dir = path.join(__dirname, "presigned-createx-deployment-transactions");
13 |
14 | export async function presign() {
15 | // Ensure the correct contract creation bytecode is used
16 | if (
17 | hre.ethers.keccak256(initCode) !=
18 | // This following hash can be retrieved via Solidity and using the compiler settings in `./verification/CreateX.json`:
19 | // ```sol
20 | // // SPDX-License-Identifier: AGPL-3.0-only
21 | // pragma solidity 0.8.23;
22 | //
23 | // import {CreateX} from "./src/CreateX.sol";
24 | //
25 | // contract CreationCodeHashCreateX {
26 | // function creationCodeHashCreateX() external pure returns (bytes32) {
27 | // return keccak256(type(CreateX).creationCode);
28 | // }
29 | // }
30 | // ```
31 | "0x12ec861579b63a3ab9db3b5a23c57d56402ad3061475b088f17054e2f2daf22f"
32 | ) {
33 | throw new Error("Incorrect contract creation bytecode.");
34 | }
35 |
36 | try {
37 | if (!Array.isArray(accounts)) {
38 | throw new Error("No private key configured.");
39 | }
40 | const privateKey = accounts[0];
41 | if (typeof privateKey !== "string") {
42 | throw new Error("Invalid private key configured.");
43 | }
44 |
45 | if (ethMainnetUrl == null) {
46 | throw new Error("No RPC URL configured.");
47 | }
48 | if (typeof ethMainnetUrl !== "string") {
49 | throw new Error("Invalid RPC URL configured.");
50 | }
51 |
52 | const provider = new hre.ethers.JsonRpcProvider(ethMainnetUrl);
53 | const wallet = new hre.ethers.Wallet(privateKey, provider);
54 |
55 | console.log(
56 | "Using wallet address: " + `${GREEN}${wallet.address}${RESET}\n`,
57 | );
58 |
59 | ////////////////////////////////////////////////
60 | // Prepare the replayable transaction payload //
61 | ////////////////////////////////////////////////
62 | const tx = new hre.ethers.Transaction();
63 | tx.to = null; // A contract creation transaction has a `to` address of `null`
64 | tx.gasLimit = 3_000_000; // A normal deployment currently costs 2,580,902 gas
65 | tx.gasPrice = hre.ethers.parseUnits("100", "gwei"); // A gas price of 100 gwei
66 | tx.data = initCode; // Contract creation bytecode of `CreateX`
67 | tx.chainId = 0; // Disable EIP-155 functionality (https://github.com/ethers-io/ethers.js/blob/bbcfb5f6b88800b8ef068e4a2923675503320e33/src.ts/transaction/transaction.ts#L168)
68 | tx.nonce = 0; // It must be the first transaction of the deployer account
69 | tx.type = 0; // Set to legacy transaction type 0
70 |
71 | // Sign the transaction
72 | const signedTx = hre.ethers.Transaction.from(
73 | await wallet.signTransaction(tx),
74 | );
75 |
76 | // Save the serialised signed transaction in a JSON file
77 | if (!fs.existsSync(dir)) {
78 | fs.mkdirSync(dir);
79 | }
80 | const saveDir = path.normalize(
81 | path.join(
82 | dir,
83 | `signed_serialised_transaction_gaslimit_${tx.gasLimit}_.json`,
84 | ),
85 | );
86 | fs.writeFileSync(saveDir, JSON.stringify(signedTx.serialized));
87 |
88 | console.log(`${GREEN}Signing attempt has been successful!${RESET}\n`);
89 | console.log(
90 | `Serialised signed transaction written to: ${GREEN}${saveDir}${RESET}\n`,
91 | );
92 |
93 | // Print the raw transaction details
94 | console.log("Raw Transaction Details", "\n");
95 | console.log("----------------------------------", "\n");
96 | console.log("- from: " + `${GREEN}${signedTx.from}${RESET}`);
97 | console.log("- publicKey: " + `${GREEN}${signedTx.fromPublicKey}${RESET}`);
98 | console.log("- gasLimit: " + `${GREEN}${signedTx.gasLimit}${RESET}`);
99 | console.log("- gasPrice: " + `${GREEN}${signedTx.gasPrice}${RESET}`);
100 | console.log("- data: " + `${GREEN}${signedTx.data}${RESET}`);
101 | console.log("- nonce: " + `${GREEN}${signedTx.nonce}${RESET}`);
102 | console.log("- to: " + `${GREEN}${signedTx.to}${RESET}`);
103 | console.log("- type: " + `${GREEN}${signedTx.type}${RESET}`);
104 | console.log("- typeName: " + `${GREEN}${signedTx.typeName}${RESET}`);
105 | console.log("- chainId: " + `${GREEN}${signedTx.chainId}${RESET}`);
106 | console.log("- serialised: " + `${GREEN}${signedTx.serialized}${RESET}`); // We use this output to broadcast the contract creation transaction
107 | } catch (err) {
108 | // Save the signing attempt error in a JSON file
109 | if (!fs.existsSync(dir)) {
110 | fs.mkdirSync(dir);
111 | }
112 | const saveDir = path.normalize(
113 | path.join(dir, "signing_attempt_error.json"),
114 | );
115 | fs.writeFileSync(saveDir, JSON.stringify(err));
116 |
117 | console.log(`${RED}Signing attempt failed!${RESET}\n`);
118 | console.log(`Error details written to: ${RED}${saveDir}${RESET}\n`);
119 | }
120 | }
121 |
122 | presign().catch((error) => {
123 | console.error(error);
124 | process.exitCode = 1;
125 | });
126 |
--------------------------------------------------------------------------------
/test/public/CREATE/CreateX.deployCreateClone.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {ImplementationContract} from "../../mocks/ImplementationContract.sol";
6 | import {CreateX} from "../../../src/CreateX.sol";
7 |
8 | contract CreateX_DeployCreateClone_Public_Test is BaseTest {
9 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
10 | /* HELPER VARIABLES */
11 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
12 |
13 | ImplementationContract internal implementationContract = new ImplementationContract();
14 | address internal implementation = address(implementationContract);
15 |
16 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
17 | /* TESTS */
18 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
19 |
20 | modifier whenTheEIP1167MinimalProxyContractIsSuccessfullyCreated() {
21 | _;
22 | }
23 |
24 | modifier whenTheEIP1167MinimalProxyInitialisationCallIsSuccessful() {
25 | _;
26 | }
27 |
28 | function testFuzz_WhenTheEIP1167MinimalProxyContractIsSuccessfullyCreatedAndWhenTheEIP1167MinimalProxyInitialisationCallIsSuccessful(
29 | uint64 nonce,
30 | uint256 msgValue
31 | )
32 | external
33 | whenTheEIP1167MinimalProxyContractIsSuccessfullyCreated
34 | whenTheEIP1167MinimalProxyInitialisationCallIsSuccessful
35 | {
36 | vm.assume(nonce != 0 && nonce < type(uint64).max);
37 | vm.setNonceUnsafe(createXAddr, nonce);
38 | msgValue = bound(msgValue, 0, type(uint64).max);
39 | // We calculate the address beforehand where the contract is to be deployed.
40 | address computedAddress = createX.computeCreateAddress(createXAddr, nonce);
41 | // It emits the event `ContractCreation` with the EIP-1167 minimal proxy address as indexed argument.
42 | // It returns the EIP-1167 minimal proxy address.
43 | vm.expectEmit(true, true, true, true, createXAddr);
44 | emit CreateX.ContractCreation(computedAddress);
45 | address proxy = createX.deployCreateClone{value: msgValue}(
46 | implementation,
47 | abi.encodeCall(implementationContract.initialiser, ())
48 | );
49 | assertEq(proxy, computedAddress, "100");
50 | assertEq(
51 | proxy.codehash,
52 | keccak256(
53 | abi.encodePacked(
54 | hex"36_3d_3d_37_3d_3d_3d_36_3d_73",
55 | implementation,
56 | hex"5a_f4_3d_82_80_3e_90_3d_91_60_2b_57_fd_5b_f3"
57 | )
58 | ),
59 | "200"
60 | );
61 | assertTrue(!implementationContract.isInitialised(), "300");
62 | assertTrue(ImplementationContract(proxy).isInitialised(), "400");
63 | assertEq(proxy.balance, msgValue, "500");
64 | assertEq(implementation.balance, 0, "600");
65 | }
66 |
67 | modifier whenTheEIP1167MinimalProxyInitialisationCallIsUnsuccessful() {
68 | _;
69 | }
70 |
71 | function testFuzz_WhenTheEIP1167MinimalProxyInitialisationCallIsUnsuccessful(
72 | uint64 nonce,
73 | uint256 msgValue
74 | )
75 | external
76 | whenTheEIP1167MinimalProxyContractIsSuccessfullyCreated
77 | whenTheEIP1167MinimalProxyInitialisationCallIsUnsuccessful
78 | {
79 | vm.assume(nonce != 0 && nonce < type(uint64).max);
80 | vm.setNonceUnsafe(createXAddr, nonce);
81 | msgValue = bound(msgValue, 0, type(uint64).max);
82 | // It should revert.
83 | bytes memory expectedErr = abi.encodeWithSelector(
84 | CreateX.FailedContractInitialisation.selector,
85 | createXAddr,
86 | new bytes(0)
87 | );
88 | vm.expectRevert(expectedErr);
89 | createX.deployCreateClone{value: msgValue}(
90 | makeAddr("initialAccount"),
91 | abi.encodeCall(implementationContract.initialiser, ())
92 | );
93 | }
94 |
95 | modifier whenTheEIP1167MinimalProxyContractCreationFails() {
96 | _;
97 | }
98 |
99 | function testFuzz_WhenTheEIP1167MinimalProxyContractCreationFails(
100 | uint64 nonce,
101 | uint256 msgValue
102 | ) external whenTheEIP1167MinimalProxyContractCreationFails {
103 | vm.assume(nonce != 0 && nonce < type(uint64).max);
104 | vm.setNonceUnsafe(createXAddr, nonce);
105 | msgValue = bound(msgValue, 0, type(uint64).max);
106 | // We calculate the address beforehand where the contract is to be deployed.
107 | address computedAddress = createX.computeCreateAddress(createXAddr, nonce);
108 | // To enforce a deployment failure, we add code to the destination address `proxy`.
109 | vm.etch(computedAddress, hex"01");
110 | // It should revert.
111 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXAddr);
112 | vm.expectRevert(expectedErr);
113 | createX.deployCreateClone{value: msgValue}(
114 | implementation,
115 | abi.encodeCall(implementationContract.initialiser, ())
116 | );
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/scripts/deploy.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import path from "path";
3 | import hre from "hardhat";
4 |
5 | import initCode from "./contract_creation_bytecode_createx.json";
6 | import signedTx from "./presigned-createx-deployment-transactions/signed_serialised_transaction_gaslimit_3000000_.json";
7 | import abi from "../abis/src/CreateX.sol/CreateX.json";
8 |
9 | // Colour codes for terminal prints
10 | const RESET = "\x1b[0m";
11 | const GREEN = "\x1b[32m";
12 | const RED = "\x1b[31m";
13 |
14 | // The `keccak256` hashes of the pre-signed transactions:
15 | // - 0xb6274b80bc7cda162df89894c7748a5cb7ba2eaa6004183c41a1837c3b072f1e (3m gasLimit),
16 | // - 0xc8354e4112f3c78ecfb985f7d1935cb4a8a625cb0b000a4cf0aff327c0708d4c (25m gasLimit),
17 | // - 0x891a2cf734349124752970c4b5666b5b71e9db38c40cb8aab493a11e5c85d6fd (45m gasLimit).
18 | const signedTxHashes = [
19 | "0xb6274b80bc7cda162df89894c7748a5cb7ba2eaa6004183c41a1837c3b072f1e",
20 | "0xc8354e4112f3c78ecfb985f7d1935cb4a8a625cb0b000a4cf0aff327c0708d4c",
21 | "0x891a2cf734349124752970c4b5666b5b71e9db38c40cb8aab493a11e5c85d6fd",
22 | ];
23 |
24 | const dir = path.join(__dirname, "broadcasted-createx-deployment-transactions");
25 |
26 | function delay(ms: number) {
27 | return new Promise((resolve) => setTimeout(resolve, ms));
28 | }
29 |
30 | // This function deploys the `CreateX` contract using the private key of the deployer
31 | // account: `0xeD456e05CaAb11d66C4c797dD6c1D6f9A7F352b5` (or any other preconfigured
32 | // private key), and the hardcoded contract creation bytecode.
33 | // IMPORTANT: This function must be enabled in the main entry point of the script by
34 | // replacing the current logic at the end of this file with:
35 | // ```ts
36 | // deployUsingPrivateKey().catch((error) => {
37 | // console.error(error);
38 | // process.exitCode = 1;
39 | // });
40 | // ```
41 | // Subsequently, every `npx hardhat run --no-compile --network scripts/deploy.ts`
42 | // invocation will use the `deployUsingPrivateKey` function.
43 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
44 | async function deployUsingPrivateKey() {
45 | // Ensure the correct contract creation bytecode is used
46 | if (
47 | hre.ethers.keccak256(initCode) !=
48 | // This following hash can be retrieved via Solidity and using the compiler settings in `./verification/CreateX.json`:
49 | // ```sol
50 | // // SPDX-License-Identifier: AGPL-3.0-only
51 | // pragma solidity 0.8.23;
52 | //
53 | // import {CreateX} from "./src/CreateX.sol";
54 | //
55 | // contract CreationCodeHashCreateX {
56 | // function creationCodeHashCreateX() external pure returns (bytes32) {
57 | // return keccak256(type(CreateX).creationCode);
58 | // }
59 | // }
60 | // ```
61 | "0x12ec861579b63a3ab9db3b5a23c57d56402ad3061475b088f17054e2f2daf22f"
62 | ) {
63 | throw new Error("Incorrect contract creation bytecode.");
64 | }
65 |
66 | const createxFactory = await hre.ethers.getContractFactory(abi, initCode);
67 | const createx = await createxFactory.deploy();
68 |
69 | await createx.waitForDeployment();
70 | const createxAddress = await createx.getAddress();
71 |
72 | console.log("CreateX deployed to: " + `${GREEN}${createxAddress}${RESET}\n`);
73 |
74 | if (createxAddress != "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed") {
75 | console.log(
76 | `${RED}The CreateX address does not correspond to the expected address 0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed!${RESET}\n`,
77 | );
78 | }
79 |
80 | console.log(
81 | "Waiting 30 seconds before beginning the contract verification to allow the block explorer to index the contract...\n",
82 | );
83 | await delay(30000); // Wait for 30 seconds before verifying `CreateX`
84 |
85 | await hre.run("verify:verify", {
86 | address: createxAddress,
87 | });
88 | }
89 |
90 | // This function deploys the `CreateX` contract using a pre-signed transaction.
91 | // The preconfigured pre-signed transaction is the default version using 3 million gas.
92 | // IMPORTANT: This function is enabled in the main entry point of the script! Thus, every
93 | // `npx hardhat run --no-compile --network scripts/deploy.ts` invocation
94 | // will use the `deployUsingPresignedTransaction` function.
95 | async function deployUsingPresignedTransaction() {
96 | // Ensure a correct pre-signed transaction is used
97 | if (!signedTxHashes.includes(hre.ethers.keccak256(signedTx))) {
98 | throw new Error("Incorrect pre-signed transaction.");
99 | }
100 |
101 | try {
102 | // Send the transaction
103 | const tx = await hre.ethers.provider.broadcastTransaction(signedTx);
104 | console.log("Transaction hash: " + `${GREEN}${tx.hash}${RESET}\n`);
105 | const transactionReceipt = await tx.wait();
106 | const createxAddress = transactionReceipt?.contractAddress;
107 | console.log(
108 | "CreateX deployed to: " + `${GREEN}${createxAddress}${RESET}\n`,
109 | );
110 |
111 | // Save the transaction receipt in a JSON file
112 | if (!fs.existsSync(dir)) {
113 | fs.mkdirSync(dir);
114 | }
115 | const saveDir = path.normalize(
116 | path.join(dir, `transaction_receipt_date_${Date.now().toString()}.json`),
117 | );
118 | fs.writeFileSync(saveDir, JSON.stringify(transactionReceipt));
119 |
120 | console.log(
121 | `${GREEN}Transaction has been successfully broadcasted!${RESET}\n`,
122 | );
123 | console.log(`Transaction details written to: ${GREEN}${saveDir}${RESET}\n`);
124 |
125 | console.log(
126 | "Waiting 30 seconds before beginning the contract verification to allow the block explorer to index the contract...\n",
127 | );
128 | await delay(30000); // Wait for 30 seconds before verifying `CreateX`
129 |
130 | await hre.run("verify:verify", {
131 | address: createxAddress,
132 | });
133 | } catch (err) {
134 | // Save the transaction error in a JSON file
135 | if (!fs.existsSync(dir)) {
136 | fs.mkdirSync(dir);
137 | }
138 | const saveDir = path.normalize(path.join(dir, "transaction_error.json"));
139 | fs.writeFileSync(saveDir, JSON.stringify(err));
140 |
141 | console.log(`${RED}Transaction broadcasting failed!${RESET}\n`);
142 | console.log(`Error details written to: ${RED}${saveDir}${RESET}\n`);
143 | }
144 | }
145 |
146 | deployUsingPresignedTransaction().catch((error) => {
147 | console.error(error);
148 | process.exitCode = 1;
149 | });
150 |
--------------------------------------------------------------------------------
/src/ICreateX.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-only
2 | pragma solidity ^0.8.4;
3 |
4 | /**
5 | * @title CreateX Factory Interface Definition
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 | */
9 | interface ICreateX {
10 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
11 | /* TYPES */
12 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
13 |
14 | struct Values {
15 | uint256 constructorAmount;
16 | uint256 initCallAmount;
17 | }
18 |
19 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
20 | /* EVENTS */
21 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
22 |
23 | event ContractCreation(address indexed newContract, bytes32 indexed salt);
24 | event ContractCreation(address indexed newContract);
25 | event Create3ProxyContractCreation(address indexed newContract, bytes32 indexed salt);
26 |
27 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
28 | /* CUSTOM ERRORS */
29 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
30 |
31 | error FailedContractCreation(address emitter);
32 | error FailedContractInitialisation(address emitter, bytes revertData);
33 | error InvalidSalt(address emitter);
34 | error InvalidNonceValue(address emitter);
35 | error FailedEtherTransfer(address emitter, bytes revertData);
36 |
37 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
38 | /* CREATE */
39 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
40 |
41 | function deployCreate(bytes memory initCode) external payable returns (address newContract);
42 |
43 | function deployCreateAndInit(
44 | bytes memory initCode,
45 | bytes memory data,
46 | Values memory values,
47 | address refundAddress
48 | ) external payable returns (address newContract);
49 |
50 | function deployCreateAndInit(
51 | bytes memory initCode,
52 | bytes memory data,
53 | Values memory values
54 | ) external payable returns (address newContract);
55 |
56 | function deployCreateClone(address implementation, bytes memory data) external payable returns (address proxy);
57 |
58 | function computeCreateAddress(address deployer, uint256 nonce) external view returns (address computedAddress);
59 |
60 | function computeCreateAddress(uint256 nonce) external view returns (address computedAddress);
61 |
62 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
63 | /* CREATE2 */
64 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
65 |
66 | function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address newContract);
67 |
68 | function deployCreate2(bytes memory initCode) external payable returns (address newContract);
69 |
70 | function deployCreate2AndInit(
71 | bytes32 salt,
72 | bytes memory initCode,
73 | bytes memory data,
74 | Values memory values,
75 | address refundAddress
76 | ) external payable returns (address newContract);
77 |
78 | function deployCreate2AndInit(
79 | bytes32 salt,
80 | bytes memory initCode,
81 | bytes memory data,
82 | Values memory values
83 | ) external payable returns (address newContract);
84 |
85 | function deployCreate2AndInit(
86 | bytes memory initCode,
87 | bytes memory data,
88 | Values memory values,
89 | address refundAddress
90 | ) external payable returns (address newContract);
91 |
92 | function deployCreate2AndInit(
93 | bytes memory initCode,
94 | bytes memory data,
95 | Values memory values
96 | ) external payable returns (address newContract);
97 |
98 | function deployCreate2Clone(
99 | bytes32 salt,
100 | address implementation,
101 | bytes memory data
102 | ) external payable returns (address proxy);
103 |
104 | function deployCreate2Clone(address implementation, bytes memory data) external payable returns (address proxy);
105 |
106 | function computeCreate2Address(
107 | bytes32 salt,
108 | bytes32 initCodeHash,
109 | address deployer
110 | ) external pure returns (address computedAddress);
111 |
112 | function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external view returns (address computedAddress);
113 |
114 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
115 | /* CREATE3 */
116 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
117 |
118 | function deployCreate3(bytes32 salt, bytes memory initCode) external payable returns (address newContract);
119 |
120 | function deployCreate3(bytes memory initCode) external payable returns (address newContract);
121 |
122 | function deployCreate3AndInit(
123 | bytes32 salt,
124 | bytes memory initCode,
125 | bytes memory data,
126 | Values memory values,
127 | address refundAddress
128 | ) external payable returns (address newContract);
129 |
130 | function deployCreate3AndInit(
131 | bytes32 salt,
132 | bytes memory initCode,
133 | bytes memory data,
134 | Values memory values
135 | ) external payable returns (address newContract);
136 |
137 | function deployCreate3AndInit(
138 | bytes memory initCode,
139 | bytes memory data,
140 | Values memory values,
141 | address refundAddress
142 | ) external payable returns (address newContract);
143 |
144 | function deployCreate3AndInit(
145 | bytes memory initCode,
146 | bytes memory data,
147 | Values memory values
148 | ) external payable returns (address newContract);
149 |
150 | function computeCreate3Address(bytes32 salt, address deployer) external pure returns (address computedAddress);
151 |
152 | function computeCreate3Address(bytes32 salt) external view returns (address computedAddress);
153 | }
154 |
--------------------------------------------------------------------------------
/.github/workflows/test-createx.yml:
--------------------------------------------------------------------------------
1 | name: 🕵️♂️ Test CreateX
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 | tests:
11 | runs-on: ${{ matrix.os }}
12 | permissions:
13 | contents: read
14 | security-events: write
15 | strategy:
16 | matrix:
17 | os:
18 | - ubuntu-latest
19 | node_version:
20 | - 24
21 | go_version:
22 | - 1.25
23 |
24 | steps:
25 | - name: Checkout
26 | uses: actions/checkout@v6
27 | with:
28 | submodules: recursive
29 |
30 | - name: Install pnpm
31 | uses: pnpm/action-setup@v4
32 | with:
33 | run_install: false
34 |
35 | - name: Get pnpm cache directory path
36 | id: pnpm-cache-dir-path
37 | run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
38 |
39 | - name: Restore pnpm cache
40 | uses: actions/cache@v5
41 | id: pnpm-cache
42 | with:
43 | path: ${{ steps.pnpm-cache-dir-path.outputs.dir }}
44 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
45 | restore-keys: |
46 | ${{ runner.os }}-pnpm-store-
47 |
48 | - name: Use Node.js ${{ matrix.node_version }}
49 | uses: actions/setup-node@v6
50 | with:
51 | node-version: ${{ matrix.node_version }}
52 |
53 | - name: Install pnpm project with a clean slate
54 | run: pnpm install --prefer-offline --frozen-lockfile
55 |
56 | - name: Install Foundry
57 | uses: foundry-rs/foundry-toolchain@v1
58 | with:
59 | version: nightly
60 |
61 | - name: Show the Foundry CI config
62 | run: forge config
63 | env:
64 | FOUNDRY_PROFILE: ci
65 | FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
66 |
67 | - name: Ensure Solidity version consistency
68 | run: |
69 | version_foundry=$(forge config --json | jq -r ".solc")
70 | version_hh=$(npx hardhat solc)
71 | if [[ $version_foundry != "0.8.23" ]] || [[ $version_foundry != $version_hh ]]; then exit 1; fi
72 |
73 | - name: Ensure `paris` as EVM version
74 | run: |
75 | version_foundry=$(forge config --json | jq -r ".evm_version")
76 | version_hh=$(npx hardhat evm)
77 | if [[ $version_foundry != "paris" ]] || [[ $version_foundry != $version_hh ]]; then exit 1; fi
78 |
79 | - name: Set up Go
80 | uses: actions/setup-go@v6
81 | with:
82 | go-version: ${{ matrix.go_version }}
83 | cache: False
84 |
85 | - name: Install `jd` CLI
86 | run: go install github.com/josephburnett/jd@latest
87 |
88 | - name: Ensure correctness of the `ICreateX` interface
89 | run: |
90 | pnpm abi
91 | diff=$(jd -set abis/src/CreateX.sol/CreateX.json abis/src/ICreateX.sol/ICreateX.json)
92 | if [[ -n $diff ]]; then exit 1; fi
93 |
94 | - name: Foundry tests
95 | run: pnpm test
96 | env:
97 | FOUNDRY_PROFILE: ci
98 | FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
99 |
100 | - name: Show the Foundry default config
101 | run: forge config
102 | env:
103 | FOUNDRY_PROFILE: default
104 | FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
105 |
106 | - name: Run snapshot
107 | run: NO_COLOR=1 forge snapshot >> $GITHUB_STEP_SUMMARY
108 | env:
109 | FOUNDRY_PROFILE: default
110 | FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
111 |
112 | - name: Slither static analyser
113 | uses: crytic/slither-action@v0.4.1
114 | id: slither
115 | with:
116 | fail-on: config
117 | sarif: results.sarif
118 |
119 | - name: Upload SARIF file
120 | uses: github/codeql-action/upload-sarif@v4
121 | with:
122 | sarif_file: ${{ steps.slither.outputs.sarif }}
123 |
124 | coverage:
125 | runs-on: ${{ matrix.os }}
126 | permissions:
127 | pull-requests: write
128 | strategy:
129 | matrix:
130 | os:
131 | - ubuntu-latest
132 |
133 | steps:
134 | - name: Checkout
135 | uses: actions/checkout@v6
136 | with:
137 | submodules: recursive
138 |
139 | - name: Install Foundry
140 | uses: foundry-rs/foundry-toolchain@v1
141 | with:
142 | version: nightly
143 |
144 | - name: Show the Foundry default config
145 | run: forge config
146 | env:
147 | FOUNDRY_PROFILE: default
148 | FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
149 |
150 | - name: Set up LCOV
151 | uses: hrishikesh-kadam/setup-lcov@v1
152 | with:
153 | ref: v2.4
154 |
155 | - name: Run coverage
156 | run: |
157 | echo '```' >> $GITHUB_STEP_SUMMARY
158 | NO_COLOR=1 forge coverage --report summary --report lcov --lcov-version 2.4 >> $GITHUB_STEP_SUMMARY
159 | echo '```' >> $GITHUB_STEP_SUMMARY
160 | env:
161 | FOUNDRY_PROFILE: default
162 | FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
163 |
164 | # See https://github.com/ScopeLift/foundry-template/blob/fd3875d2e99a65dec19431723d6516b4ed76746e/.github/workflows/ci.yml#L49-L78.
165 | - name: Remove unnecessary `test` directory
166 | run: lcov --branch-coverage --remove lcov.info 'test/*' --output-file lcov.info --ignore-errors inconsistent,inconsistent
167 |
168 | - name: Post coverage report
169 | # See https://github.com/orgs/community/discussions/26829#discussioncomment-3253575.
170 | if: ${{ (github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request') }}
171 | uses: romeovs/lcov-reporter-action@v0.4.0
172 | with:
173 | title: "`CreateX` Test Coverage Report"
174 | delete-old-comments: true
175 | lcov-file: ./lcov.info
176 | github-token: ${{ secrets.GITHUB_TOKEN }}
177 |
178 | # The following steps act as a temporary workaround, as LCOV `2.4` is not yet supported
179 | # in `zgosalvez/github-actions-report-lcov@v5`: https://github.com/zgosalvez/github-actions-report-lcov/issues/168.
180 | - name: Set up LCOV `1.16`
181 | run: |
182 | wget https://github.com/linux-test-project/lcov/releases/download/v1.16/lcov-1.16.tar.gz
183 | tar -xzf lcov-1.16.tar.gz
184 | cd lcov-1.16
185 | sudo make install
186 | lcov --version
187 | sudo rm -rf lcov-1.16.tar.gz lcov-1.16
188 |
189 | - name: Run coverage using LCOV `1.16`
190 | run: forge coverage --report lcov --lcov-version 1.16
191 | env:
192 | FOUNDRY_PROFILE: default
193 | FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
194 |
195 | # See https://github.com/ScopeLift/foundry-template/blob/fd3875d2e99a65dec19431723d6516b4ed76746e/.github/workflows/ci.yml#L49-L78.
196 | - name: Remove unnecessary `test` directory
197 | run: lcov --remove lcov.info 'test/*' --output-file lcov.info --rc lcov_branch_coverage=1
198 |
199 | - name: Verify minimum coverage
200 | uses: zgosalvez/github-actions-report-lcov@v5
201 | with:
202 | coverage-files: ./lcov.info
203 | minimum-coverage: 100
204 |
--------------------------------------------------------------------------------
/test/invariants/CreateX_Invariants.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {CreateX} from "../../src/CreateX.sol";
6 |
7 | contract CreateX_Invariants is Test {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* HELPER VARIABLES */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | CreateX internal createX;
13 | CreateXHandler internal createXHandler;
14 |
15 | address internal createXAddr;
16 |
17 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
18 | /* SETUP */
19 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
20 |
21 | function setUp() public {
22 | uint256 initialBalance = 1 ether;
23 | createX = new CreateX();
24 | createXAddr = address(createX);
25 | createXHandler = new CreateXHandler(createX, initialBalance);
26 | // We prefund the `createX` contract with an initial amount.
27 | deal(createXAddr, initialBalance);
28 | targetContract(address(createXHandler));
29 | excludeSender(createXAddr);
30 | }
31 |
32 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
33 | /* TESTS */
34 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
35 |
36 | function statefulFuzz_EtherBalance() external view {
37 | assertEq(createXAddr.balance, createXHandler.updatedBalance(), "100");
38 | }
39 | }
40 |
41 | contract CreateXHandler {
42 | uint256 public updatedBalance;
43 | CreateX internal createX;
44 |
45 | constructor(CreateX createX_, uint256 initialBalance_) {
46 | createX = createX_;
47 | updatedBalance = initialBalance_;
48 | }
49 |
50 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
51 | /* CREATE */
52 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
53 |
54 | function deployCreate(bytes memory initCode) public payable returns (address newContract) {
55 | newContract = createX.deployCreate(initCode);
56 | }
57 |
58 | function deployCreateAndInit(
59 | bytes memory initCode,
60 | bytes memory data,
61 | CreateX.Values memory values,
62 | address refundAddress
63 | ) public payable returns (address newContract) {
64 | newContract = createX.deployCreateAndInit(initCode, data, values, refundAddress);
65 | updatedBalance = 0;
66 | }
67 |
68 | function deployCreateAndInit(
69 | bytes memory initCode,
70 | bytes memory data,
71 | CreateX.Values memory values
72 | ) public payable returns (address newContract) {
73 | newContract = createX.deployCreateAndInit(initCode, data, values);
74 | updatedBalance = 0;
75 | }
76 |
77 | function deployCreateClone(address implementation, bytes memory data) public payable returns (address proxy) {
78 | proxy = createX.deployCreateClone(implementation, data);
79 | }
80 |
81 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
82 | /* CREATE2 */
83 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
84 |
85 | function deployCreate2(bytes32 salt, bytes memory initCode) public payable returns (address newContract) {
86 | newContract = createX.deployCreate2(salt, initCode);
87 | }
88 |
89 | function deployCreate2(bytes memory initCode) public payable returns (address newContract) {
90 | newContract = createX.deployCreate2(initCode);
91 | }
92 |
93 | function deployCreate2AndInit(
94 | bytes32 salt,
95 | bytes memory initCode,
96 | bytes memory data,
97 | CreateX.Values memory values,
98 | address refundAddress
99 | ) public payable returns (address newContract) {
100 | newContract = createX.deployCreate2AndInit(salt, initCode, data, values, refundAddress);
101 | updatedBalance = 0;
102 | }
103 |
104 | function deployCreate2AndInit(
105 | bytes32 salt,
106 | bytes memory initCode,
107 | bytes memory data,
108 | CreateX.Values memory values
109 | ) public payable returns (address newContract) {
110 | newContract = createX.deployCreate2AndInit(salt, initCode, data, values);
111 | updatedBalance = 0;
112 | }
113 |
114 | function deployCreate2AndInit(
115 | bytes memory initCode,
116 | bytes memory data,
117 | CreateX.Values memory values,
118 | address refundAddress
119 | ) public payable returns (address newContract) {
120 | newContract = createX.deployCreate2AndInit(initCode, data, values, refundAddress);
121 | updatedBalance = 0;
122 | }
123 |
124 | function deployCreate2AndInit(
125 | bytes memory initCode,
126 | bytes memory data,
127 | CreateX.Values memory values
128 | ) public payable returns (address newContract) {
129 | newContract = createX.deployCreate2AndInit(initCode, data, values);
130 | updatedBalance = 0;
131 | }
132 |
133 | function deployCreate2Clone(
134 | bytes32 salt,
135 | address implementation,
136 | bytes memory data
137 | ) public payable returns (address proxy) {
138 | proxy = createX.deployCreate2Clone(salt, implementation, data);
139 | }
140 |
141 | function deployCreate2Clone(address implementation, bytes memory data) public payable returns (address proxy) {
142 | proxy = createX.deployCreate2Clone(implementation, data);
143 | }
144 |
145 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
146 | /* CREATE3 */
147 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
148 |
149 | function deployCreate3(bytes32 salt, bytes memory initCode) public payable returns (address newContract) {
150 | newContract = createX.deployCreate3(salt, initCode);
151 | }
152 |
153 | function deployCreate3(bytes memory initCode) public payable returns (address newContract) {
154 | newContract = createX.deployCreate3(initCode);
155 | }
156 |
157 | function deployCreate3AndInit(
158 | bytes32 salt,
159 | bytes memory initCode,
160 | bytes memory data,
161 | CreateX.Values memory values,
162 | address refundAddress
163 | ) public payable returns (address newContract) {
164 | newContract = createX.deployCreate3AndInit(salt, initCode, data, values, refundAddress);
165 | updatedBalance = 0;
166 | }
167 |
168 | function deployCreate3AndInit(
169 | bytes32 salt,
170 | bytes memory initCode,
171 | bytes memory data,
172 | CreateX.Values memory values
173 | ) public payable returns (address newContract) {
174 | newContract = createX.deployCreate3AndInit(salt, initCode, data, values);
175 | updatedBalance = 0;
176 | }
177 |
178 | function deployCreate3AndInit(
179 | bytes memory initCode,
180 | bytes memory data,
181 | CreateX.Values memory values,
182 | address refundAddress
183 | ) public payable returns (address newContract) {
184 | newContract = createX.deployCreate3AndInit(initCode, data, values, refundAddress);
185 | updatedBalance = 0;
186 | }
187 |
188 | function deployCreate3AndInit(
189 | bytes memory initCode,
190 | bytes memory data,
191 | CreateX.Values memory values
192 | ) public payable returns (address newContract) {
193 | newContract = createX.deployCreate3AndInit(initCode, data, values);
194 | updatedBalance = 0;
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/test/utils/BaseTest.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {CreateX} from "../../src/CreateX.sol";
6 |
7 | /**
8 | * @dev Harness contract that exposes `internal` functions for testing.
9 | */
10 | contract CreateXHarness is CreateX {
11 | function exposed_guard(bytes32 salt) external view returns (bytes32 guardedSalt) {
12 | guardedSalt = _guard(salt);
13 | }
14 |
15 | function exposed_parseSalt(
16 | bytes32 salt
17 | ) external view returns (SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag) {
18 | (senderBytes, redeployProtectionFlag) = _parseSalt(salt);
19 | }
20 |
21 | function exposed_efficientHash(bytes32 a, bytes32 b) external pure returns (bytes32 hash) {
22 | hash = _efficientHash(a, b);
23 | }
24 |
25 | function exposed_generateSalt() external view returns (bytes32 salt) {
26 | salt = _generateSalt();
27 | }
28 |
29 | function exposed_requireSuccessfulContractCreation(bool success, address newContract) external view {
30 | _requireSuccessfulContractCreation(success, newContract);
31 | }
32 |
33 | function exposed_requireSuccessfulContractCreation(address newContract) external view {
34 | _requireSuccessfulContractCreation(newContract);
35 | }
36 |
37 | function exposed_requireSuccessfulContractInitialisation(
38 | bool success,
39 | bytes calldata returnData,
40 | address implementation
41 | ) external view {
42 | _requireSuccessfulContractInitialisation(success, returnData, implementation);
43 | }
44 | }
45 |
46 | /**
47 | * @dev Base test contract that deploys `CreateX` and `CreateXHarness`, and defines
48 | * custom errors, and helper variables and methods.
49 | */
50 | contract BaseTest is Test {
51 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
52 | /* HELPER VARIABLES */
53 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
54 |
55 | CreateX internal createX;
56 | address internal createXAddr;
57 |
58 | CreateXHarness internal createXHarness;
59 | address internal createXHarnessAddr;
60 |
61 | // solhint-disable-next-line const-name-snakecase
62 | address internal constant zeroAddress = address(0);
63 | address internal immutable SELF = address(this);
64 |
65 | // Constructor arguments for the `ERC20MockPayable` contract.
66 | string internal arg1;
67 | string internal arg2;
68 | address internal arg3;
69 | uint256 internal arg4;
70 | bytes internal args;
71 |
72 | // Caching and helper variables used in numerous tests.
73 | bytes internal cachedInitCode;
74 | uint256 internal cachedBalance;
75 | bytes32 internal initCodeHash;
76 |
77 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
78 | /* CUSTOM ERRORS */
79 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
80 |
81 | /**
82 | * @dev Error that occurs when the contract creation code has zero-byte length.
83 | * @param emitter The contract that emits the error.
84 | */
85 | error ZeroByteInitCode(address emitter);
86 |
87 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
88 | /* SETUP */
89 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
90 |
91 | function setUp() public virtual {
92 | // Note that the main contract `CreateX` does `block.number - 32` when generating
93 | // it's own salt, so we start at block 100 here to prevent a (negative) overflow.
94 | vm.roll(100);
95 |
96 | // Deploy contracts.
97 | createX = new CreateX();
98 | createXAddr = address(createX);
99 |
100 | createXHarness = new CreateXHarness();
101 | createXHarnessAddr = address(createXHarness);
102 | }
103 |
104 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
105 | /* HELPER FUNCTIONS */
106 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
107 |
108 | /**
109 | * @dev Indicates whether a permissioned deploy protection and/or a cross-chain redeploy protection
110 | * has been configured via `salt` or whether it must revert.
111 | * @param originalDeployer The 20-byte original deployer address.
112 | * @param salt The 32-byte random value used to create the contract address.
113 | * @return permissionedDeployProtection The Boolean variable that specifies whether a permissioned redeploy
114 | * protection has been configured.
115 | * @return xChainRedeployProtection The Boolean variable that specifies whether a cross-chain deploy
116 | * protection has been configured.
117 | * @return mustRevert The Boolean variable that specifies whether it must revert.
118 | * @return guardedSalt The guarded 32-byte random value used to create the contract address.
119 | */
120 | function parseFuzzerSalt(
121 | address originalDeployer,
122 | bytes32 salt
123 | )
124 | internal
125 | returns (bool permissionedDeployProtection, bool xChainRedeployProtection, bool mustRevert, bytes32 guardedSalt)
126 | {
127 | vm.startPrank(originalDeployer);
128 | (CreateX.SenderBytes senderBytes, CreateX.RedeployProtectionFlag redeployProtectionFlag) = createXHarness
129 | .exposed_parseSalt(salt);
130 | vm.stopPrank();
131 |
132 | if (
133 | senderBytes == CreateX.SenderBytes.MsgSender &&
134 | redeployProtectionFlag == CreateX.RedeployProtectionFlag.True
135 | ) {
136 | vm.startPrank(originalDeployer);
137 | // Configures a permissioned deploy protection as well as a cross-chain redeploy protection.
138 | guardedSalt = createXHarness.exposed_guard(salt);
139 | vm.stopPrank();
140 | permissionedDeployProtection = true;
141 | xChainRedeployProtection = true;
142 | } else if (
143 | senderBytes == CreateX.SenderBytes.MsgSender &&
144 | redeployProtectionFlag == CreateX.RedeployProtectionFlag.False
145 | ) {
146 | vm.startPrank(originalDeployer);
147 | // Configures solely a permissioned deploy protection.
148 | guardedSalt = createXHarness.exposed_guard(salt);
149 | vm.stopPrank();
150 | permissionedDeployProtection = true;
151 | } else if (senderBytes == CreateX.SenderBytes.MsgSender) {
152 | // Reverts if the 21st byte is greater than `0x01` in order to enforce developer explicitness.
153 | mustRevert = true;
154 | } else if (
155 | senderBytes == CreateX.SenderBytes.ZeroAddress &&
156 | redeployProtectionFlag == CreateX.RedeployProtectionFlag.True
157 | ) {
158 | vm.startPrank(originalDeployer);
159 | // Configures solely a cross-chain redeploy protection.
160 | guardedSalt = createXHarness.exposed_guard(salt);
161 | vm.stopPrank();
162 | xChainRedeployProtection = true;
163 | } else if (
164 | senderBytes == CreateX.SenderBytes.ZeroAddress &&
165 | redeployProtectionFlag == CreateX.RedeployProtectionFlag.Unspecified
166 | ) {
167 | // Reverts if the 21st byte is greater than `0x01` in order to enforce developer explicitness.
168 | mustRevert = true;
169 | } else {
170 | vm.startPrank(originalDeployer);
171 | // For the non-pseudo-random cases, the salt value `salt` is hashed to prevent the safeguard mechanisms
172 | // from being bypassed. Otherwise, the salt value `salt` is not modified.
173 | guardedSalt = (salt != createXHarness.exposed_generateSalt()) ? keccak256(abi.encode(salt)) : salt;
174 | vm.stopPrank();
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/interface/src/pages/abi.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import Image from "next/image";
3 | import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
4 | import {
5 | ArrowDownTrayIcon,
6 | ClipboardDocumentIcon,
7 | } from "@heroicons/react/24/solid";
8 | import { useTheme } from "next-themes";
9 | import Prism from "prismjs";
10 | import "prismjs/components/prism-json";
11 | import "prismjs/components/prism-solidity";
12 | import "prismjs/components/prism-typescript";
13 | import { Head } from "@/components/layout/Head";
14 | import { Notification } from "@/components/ui/Notification";
15 | import {
16 | CREATEX_ABI,
17 | CREATEX_ABI_ETHERS,
18 | CREATEX_ABI_VIEM,
19 | CREATEX_SOLIDITY_INTERFACE,
20 | } from "@/lib/constants";
21 | import { classNames } from "@/lib/utils";
22 | import { copyToClipboard } from "@/lib/utils";
23 |
24 | const tabs = [
25 | {
26 | name: "Solidity",
27 | href: "#solidity",
28 | imgUri: "/solidity.png",
29 | imgSize: "sm",
30 | language: "solidity",
31 | abi: CREATEX_SOLIDITY_INTERFACE,
32 | filename: "ICreateX.sol",
33 | mimeType: "text/plain",
34 | },
35 | {
36 | name: "ethers.js",
37 | href: "#ethers-js",
38 | imgUri: "/ethersjs.png",
39 | language: "json",
40 | abi: CREATEX_ABI_ETHERS,
41 | filename: "ICreateX.json",
42 | mimeType: "text/plain",
43 | },
44 | {
45 | name: "viem",
46 | href: "#viem",
47 | imgUri: "/viem.png",
48 | language: "typescript",
49 | abi: CREATEX_ABI_VIEM,
50 | filename: "ICreateX.ts",
51 | mimeType: "text/plain",
52 | },
53 | {
54 | name: "JSON",
55 | href: "#json",
56 | imgUri: "/json.svg",
57 | imgSize: "sm",
58 | language: "json",
59 | abi: JSON.stringify(CREATEX_ABI, null, 2),
60 | filename: "ICreateX.json",
61 | mimeType: "application/json",
62 | },
63 | ];
64 |
65 | const hashToIndex = () => {
66 | const anchor = window.location.hash || "#solidity";
67 | const index = tabs.findIndex((tab) => tab.href === anchor);
68 | return index === -1 ? 0 : index;
69 | };
70 |
71 | const indexToHash = (index: number) => {
72 | return tabs[index].href || "#solidity";
73 | };
74 |
75 | const Abi = () => {
76 | // -------- Syntax Highlighting --------
77 | const { resolvedTheme: theme } = useTheme();
78 | const [selectedTab, setSelectedTab] = useState(hashToIndex());
79 | const [showNotification, setShowNotification] = useState(false);
80 | const [isLoading, setIsLoading] = useState(true);
81 |
82 | const onTabChange = (index: number) => {
83 | // We set `isLoading` to true to fade out the content while the tab is changing, to avoid
84 | // briefly showing un-highlighted code. This is set to false again in the `useEffect` hook.
85 | setIsLoading(true);
86 | setSelectedTab(index);
87 | window.location.hash = indexToHash(index);
88 | };
89 |
90 | // This is required to re-highlight the code when the tab changes, and we use `setTimeout` with
91 | // a delay of 0 to ensure that the code is highlighted after the tab has changed. Otherwise the
92 | // `highlightAll` function runs before the code has been updated, so the code is not highlighted.
93 | useEffect(() => {
94 | setTimeout(() => {
95 | Prism.highlightAll();
96 | setIsLoading(false);
97 | }, 0);
98 | }, [selectedTab]);
99 |
100 | // We conditionally import the Prism theme based on the current theme, to adjust the syntax
101 | // highlighting to the theme. The logic in the `importTheme` function combined with the presence
102 | // of the `prism-light.css` and `prism-dark.css` files in the `public` folder is what allows us
103 | // to ensure all styles from the light theme are removed when the user toggles to the dark theme,
104 | // and vice versa.
105 | useEffect(() => {
106 | const importTheme = async () => {
107 | // Define the new stylesheet href based on the theme and get it's element.
108 | const newStylesheetHref =
109 | theme === "dark" ? "/prism-dark.css" : "/prism-light.css";
110 | const existingStylesheet = document.getElementById("dynamic-stylesheet");
111 |
112 | // If there's an existing stylesheet, remove it.
113 | existingStylesheet?.parentNode?.removeChild(existingStylesheet);
114 |
115 | // Create a new element for the new stylesheet, and append the stylesheet to the head.
116 | const newStylesheet = document.createElement("link");
117 | newStylesheet.rel = "stylesheet";
118 | newStylesheet.type = "text/css";
119 | newStylesheet.href = newStylesheetHref;
120 | newStylesheet.id = "dynamic-stylesheet";
121 | document.head.appendChild(newStylesheet);
122 | };
123 | importTheme();
124 | }, [theme]);
125 |
126 | // -------- Download and Copy ABI --------
127 | const onDownload = (content: string, filename: string, mimeType: string) => {
128 | const blob = new Blob([content], { type: mimeType });
129 | const url = URL.createObjectURL(blob);
130 | const link = document.createElement("a");
131 |
132 | link.href = url;
133 | link.download = filename;
134 | link.style.display = "none";
135 |
136 | document.body.appendChild(link);
137 | link.click();
138 |
139 | setTimeout(() => {
140 | document.body.removeChild(link);
141 | URL.revokeObjectURL(url);
142 | }, 100);
143 | };
144 |
145 | const onCopy = (text: string) => {
146 | copyToClipboard(text);
147 | setShowNotification(true);
148 | setTimeout(() => setShowNotification(false), 3000);
149 | };
150 |
151 | // -------- Render --------
152 | return (
153 | <>
154 |
155 |
156 |
162 |
163 |
164 |
165 | {tabs.map((tab) => {
166 | return (
167 |
168 | {({ selected }) => (
169 |
177 |
190 | {tab.name}
191 |
192 | )}
193 |
194 | );
195 | })}
196 |
197 |
201 | {tabs.map((tab) => {
202 | return (
203 |
207 |
220 |
231 |
232 | {tab.abi}
233 |
234 |
235 | );
236 | })}
237 |
238 |
239 | >
240 | );
241 | };
242 |
243 | export default Abi;
244 |
--------------------------------------------------------------------------------
/test/internal/CreateX._guard.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../utils/BaseTest.sol";
5 | import {CreateX} from "../../src/CreateX.sol";
6 |
7 | contract CreateX_Guard_Internal_Test is BaseTest {
8 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9 | /* HELPER VARIABLES */
10 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11 |
12 | bytes32 internal cachedSalt;
13 |
14 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
15 | /* TESTS */
16 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
17 |
18 | modifier whenTheFirst20BytesOfTheSaltEqualsTheCaller(address caller, bytes32 salt) {
19 | // Set the first 20 bytes of the `salt` equal to `caller` (a.k.a. `msg.sender`).
20 | cachedSalt = bytes32(abi.encodePacked(caller, bytes12(uint96(uint256(salt)))));
21 | _;
22 | }
23 |
24 | /**
25 | * @custom:security To ensure a proper test coverage, please use this modifier only subsequent
26 | * to a modifier that updates the value of `cachedSalt`. Otherwise, it might be possible that
27 | * `cachedSalt` has never changed its default value.
28 | */
29 | modifier whenThe21stByteOfTheSaltEquals0x01() {
30 | // Set the 21st byte of the `salt` equal to `0x01`.
31 | cachedSalt = bytes32(abi.encodePacked(bytes20(cachedSalt), hex"01", bytes11(uint88(uint256(cachedSalt)))));
32 | _;
33 | }
34 |
35 | function testFuzz_WhenTheFirst20BytesOfTheSaltEqualsTheCallerAndWhenThe21stByteOfTheSaltEquals0x01(
36 | address caller,
37 | bytes32 salt
38 | ) external whenTheFirst20BytesOfTheSaltEqualsTheCaller(caller, salt) whenThe21stByteOfTheSaltEquals0x01 {
39 | vm.startPrank(caller);
40 | // It should return the `keccak256` hash of the ABI-encoded values `msg.sender`, `block.chainid`, and the `salt`.
41 | bytes32 guardedSalt = createXHarness.exposed_guard(cachedSalt);
42 | vm.stopPrank();
43 | assertEq(guardedSalt, keccak256(abi.encode(caller, block.chainid, cachedSalt)), "100");
44 | }
45 |
46 | /**
47 | * @custom:security To ensure a proper test coverage, please use this modifier only subsequent
48 | * to a modifier that updates the value of `cachedSalt`. Otherwise, it might be possible that
49 | * `cachedSalt` has never changed its default value.
50 | */
51 | modifier whenThe21stByteOfTheSaltEquals0x00() {
52 | // Set the 21st byte of the `salt` equal to `0x00`.
53 | cachedSalt = bytes32(abi.encodePacked(bytes20(cachedSalt), hex"00", bytes11(uint88(uint256(cachedSalt)))));
54 | _;
55 | }
56 |
57 | function testFuzz_WhenTheFirst20BytesOfTheSaltEqualsTheCallerAndWhenThe21stByteOfTheSaltEquals0x00(
58 | address caller,
59 | bytes32 salt
60 | ) external whenTheFirst20BytesOfTheSaltEqualsTheCaller(caller, salt) whenThe21stByteOfTheSaltEquals0x00 {
61 | vm.startPrank(caller);
62 | // It should return the `keccak256` hash of the ABI-encoded values `msg.sender` and the `salt`.
63 | bytes32 guardedSalt = createXHarness.exposed_guard(cachedSalt);
64 | vm.stopPrank();
65 | assertEq(guardedSalt, keccak256(abi.encode(caller, cachedSalt)), "100");
66 | }
67 |
68 | /**
69 | * @custom:security To ensure a proper test coverage, please use this modifier only subsequent
70 | * to a modifier that updates the value of `cachedSalt`. Otherwise, it might be possible that
71 | * `cachedSalt` has never changed its default value.
72 | */
73 | modifier whenThe21stByteOfTheSaltIsGreaterThan0x01() {
74 | // Set the 21st byte of the `salt` to a value greater than `0x01`.
75 | if (uint8(cachedSalt[20]) <= uint8(1)) {
76 | bytes1 newByte = bytes1(keccak256(abi.encode(cachedSalt[20])));
77 | while (uint8(newByte) <= uint8(1)) {
78 | newByte = bytes1(keccak256(abi.encode(newByte)));
79 | }
80 | cachedSalt = bytes32(abi.encodePacked(bytes20(cachedSalt), newByte, bytes11(uint88(uint256(cachedSalt)))));
81 | }
82 | _;
83 | }
84 |
85 | function testFuzz_WhenTheFirst20BytesOfTheSaltEqualsTheCallerAndWhenThe21stByteOfTheSaltIsGreaterThan0x01(
86 | address caller,
87 | bytes32 salt
88 | ) external whenTheFirst20BytesOfTheSaltEqualsTheCaller(caller, salt) whenThe21stByteOfTheSaltIsGreaterThan0x01 {
89 | vm.startPrank(caller);
90 | // It should revert.
91 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.InvalidSalt.selector, createXHarnessAddr);
92 | vm.expectRevert(expectedErr);
93 | createXHarness.exposed_guard(cachedSalt);
94 | vm.stopPrank();
95 | }
96 |
97 | modifier whenTheFirst20BytesOfTheSaltEqualsTheZeroAddress(bytes32 salt) {
98 | // Set the first 20 bytes of the `salt` equal to `0x0000000000000000000000000000000000000000`.
99 | cachedSalt = bytes32(abi.encodePacked(bytes20(0), bytes12(uint96(uint256(salt)))));
100 | _;
101 | }
102 |
103 | function testFuzz_WhenTheFirst20BytesOfTheSaltEqualsTheZeroAddressAndWhenThe21stByteOfTheSaltEquals0x01(
104 | address caller,
105 | bytes32 salt
106 | ) external whenTheFirst20BytesOfTheSaltEqualsTheZeroAddress(salt) whenThe21stByteOfTheSaltEquals0x01 {
107 | vm.assume(caller != zeroAddress);
108 | vm.startPrank(caller);
109 | // It should return the `keccak256` hash of the ABI-encoded values `block.chainid` and the `salt`.
110 | bytes32 guardedSalt = createXHarness.exposed_guard(cachedSalt);
111 | vm.stopPrank();
112 | assertEq(guardedSalt, keccak256(abi.encode(block.chainid, cachedSalt)), "100");
113 | }
114 |
115 | function testFuzz_WhenTheFirst20BytesOfTheSaltEqualsTheZeroAddressAndWhenThe21stByteOfTheSaltEquals0x00(
116 | address caller,
117 | bytes32 salt
118 | ) external whenTheFirst20BytesOfTheSaltEqualsTheZeroAddress(salt) whenThe21stByteOfTheSaltEquals0x00 {
119 | vm.assume(caller != zeroAddress);
120 | vm.startPrank(caller);
121 | // It should return the `keccak256` hash of the ABI-encoded value `salt`.
122 | bytes32 guardedSalt = createXHarness.exposed_guard(cachedSalt);
123 | vm.stopPrank();
124 | assertEq(guardedSalt, keccak256(abi.encode(cachedSalt)), "100");
125 | }
126 |
127 | function testFuzz_WhenTheFirst20BytesOfTheSaltEqualsTheZeroAddressAndWhenThe21stByteOfTheSaltIsGreaterThan0x01(
128 | address caller,
129 | bytes32 salt
130 | ) external whenTheFirst20BytesOfTheSaltEqualsTheZeroAddress(salt) whenThe21stByteOfTheSaltIsGreaterThan0x01 {
131 | vm.startPrank(caller);
132 | // It should revert.
133 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.InvalidSalt.selector, createXHarnessAddr);
134 | vm.expectRevert(expectedErr);
135 | createXHarness.exposed_guard(cachedSalt);
136 | vm.stopPrank();
137 | }
138 |
139 | modifier whenTheFirst20BytesOfTheSaltDoNotEqualTheCallerOrTheZeroAddress(address caller, bytes32 salt) {
140 | vm.assume(address(bytes20(salt)) != caller && address(bytes20(salt)) != zeroAddress);
141 | cachedSalt = salt;
142 | _;
143 | }
144 |
145 | function testFuzz_WhenTheFirst20BytesOfTheSaltDoNotEqualTheCallerOrTheZeroAddress(
146 | address caller,
147 | bytes32 salt
148 | ) external whenTheFirst20BytesOfTheSaltDoNotEqualTheCallerOrTheZeroAddress(caller, salt) {
149 | vm.startPrank(caller);
150 | // It should return the `keccak256` hash of the ABI-encoded value `salt`.
151 | bytes32 guardedSalt = createXHarness.exposed_guard(cachedSalt);
152 | vm.stopPrank();
153 | assertEq(guardedSalt, keccak256(abi.encode(cachedSalt)), "100");
154 | }
155 |
156 | modifier whenTheFirst20BytesOfTheSaltDoNotEqualTheCallerOrTheZeroAddressAndWhenTheSaltValueIsGeneratedPseudorandomly(
157 | address caller,
158 | uint256 increment,
159 | address coinbase,
160 | string calldata prevrandao,
161 | uint64 chainId
162 | ) {
163 | increment = bound(increment, 1, type(uint64).max - 100);
164 | vm.assume(coinbase != zeroAddress && chainId != block.chainid && chainId != 0);
165 | vm.roll(vm.getBlockNumber() + increment);
166 | vm.coinbase(coinbase);
167 | vm.warp(vm.getBlockTimestamp() + increment);
168 | vm.prevrandao(keccak256(abi.encode(prevrandao)));
169 | vm.chainId(chainId);
170 |
171 | vm.startPrank(caller);
172 | cachedSalt = createXHarness.exposed_generateSalt();
173 | vm.stopPrank();
174 | _;
175 | }
176 |
177 | function testFuzz_WhenTheFirst20BytesOfTheSaltDoNotEqualTheCallerOrTheZeroAddressAndWhenTheSaltValueIsGeneratedPseudorandomly(
178 | address caller,
179 | uint256 increment,
180 | address coinbase,
181 | string calldata prevrandao,
182 | uint64 chainId
183 | )
184 | external
185 | whenTheFirst20BytesOfTheSaltDoNotEqualTheCallerOrTheZeroAddressAndWhenTheSaltValueIsGeneratedPseudorandomly(
186 | caller,
187 | increment,
188 | coinbase,
189 | prevrandao,
190 | chainId
191 | )
192 | {
193 | vm.startPrank(caller);
194 | // It should return the unmodified salt value.
195 | bytes32 guardedSalt = createXHarness.exposed_guard(cachedSalt);
196 | vm.stopPrank();
197 | assertEq(guardedSalt, cachedSalt, "100");
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/test/public/CREATE2/CreateX.deployCreate2_1Arg.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity 0.8.23;
3 |
4 | import {BaseTest} from "../../utils/BaseTest.sol";
5 | import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
6 | import {ERC20MockPayable} from "../../mocks/ERC20MockPayable.sol";
7 | import {CreateX} from "../../../src/CreateX.sol";
8 |
9 | contract CreateX_DeployCreate2_1Arg_Public_Test is BaseTest {
10 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
11 | /* TESTS */
12 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
13 |
14 | function setUp() public override {
15 | BaseTest.setUp();
16 | arg1 = "MyToken";
17 | arg2 = "MTKN";
18 | arg3 = makeAddr("initialAccount");
19 | arg4 = 100;
20 | args = abi.encode(arg1, arg2, arg3, arg4);
21 | cachedInitCode = abi.encodePacked(type(ERC20MockPayable).creationCode, args);
22 | initCodeHash = keccak256(cachedInitCode);
23 | }
24 |
25 | modifier whenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithANonZeroLength() {
26 | if (cachedInitCode.length == 0) {
27 | revert ZeroByteInitCode(SELF);
28 | }
29 | _;
30 | }
31 |
32 | function testFuzz_WhenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithANonZeroLength(
33 | address originalDeployer,
34 | uint256 msgValue,
35 | uint64 chainId,
36 | address msgSender
37 | ) external whenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithANonZeroLength {
38 | msgValue = bound(msgValue, 0, type(uint64).max);
39 | vm.deal(originalDeployer, 2 * msgValue);
40 | vm.assume(
41 | chainId != block.chainid &&
42 | chainId != 0 &&
43 | originalDeployer != msgSender &&
44 | originalDeployer != createXAddr &&
45 | originalDeployer != zeroAddress &&
46 | msgSender != createXAddr &&
47 | msgSender != zeroAddress
48 | );
49 |
50 | vm.startPrank(originalDeployer);
51 | bytes32 salt = createXHarness.exposed_generateSalt();
52 | vm.stopPrank();
53 | (
54 | bool permissionedDeployProtection,
55 | bool xChainRedeployProtection,
56 | bool mustRevert,
57 | bytes32 guardedSalt
58 | ) = parseFuzzerSalt(originalDeployer, salt);
59 | // When we pseudo-randomly calculate the salt value `salt`, we must never have configured a permissioned
60 | // deploy protection or a cross-chain redeploy protection, and it must never revert.
61 | assertTrue(!permissionedDeployProtection && !xChainRedeployProtection && !mustRevert, "100");
62 |
63 | // We calculate the address beforehand where the contract is to be deployed.
64 | address computedAddress = createX.computeCreate2Address(guardedSalt, initCodeHash, createXAddr);
65 | vm.assume(originalDeployer != computedAddress);
66 |
67 | // We also check for the ERC-20 standard `Transfer` event.
68 | vm.expectEmit(true, true, true, true, computedAddress);
69 | emit IERC20.Transfer(zeroAddress, arg3, arg4);
70 | // It returns a contract address with a non-zero bytecode length and a potential non-zero ether balance.
71 | // It emits the event `ContractCreation` with the contract address and the salt as indexed arguments.
72 | vm.expectEmit(true, true, true, true, createXAddr);
73 | emit CreateX.ContractCreation(computedAddress, guardedSalt);
74 | vm.startPrank(originalDeployer);
75 | address newContract = createX.deployCreate2{value: msgValue}(cachedInitCode);
76 | vm.stopPrank();
77 |
78 | assertEq(newContract, computedAddress, "200");
79 | assertNotEq(newContract, zeroAddress, "300");
80 | assertNotEq(newContract.code.length, 0, "400");
81 | assertEq(newContract.balance, msgValue, "500");
82 | assertEq(createXAddr.balance, 0, "600");
83 | assertEq(ERC20MockPayable(computedAddress).name(), arg1, "700");
84 | assertEq(ERC20MockPayable(computedAddress).symbol(), arg2, "800");
85 | assertEq(ERC20MockPayable(computedAddress).balanceOf(arg3), arg4, "900");
86 |
87 | vm.chainId(chainId);
88 | // We mock a potential frontrunner address.
89 | vm.deal(msgSender, msgValue);
90 | vm.startPrank(msgSender);
91 | address newContractMsgSender = createX.deployCreate2{value: msgValue}(cachedInitCode);
92 | vm.stopPrank();
93 | vm.assume(msgSender != newContractMsgSender);
94 |
95 | // The newly created contract on chain `chainId` must not be the same as the previously created
96 | // contract at the `computedAddress` address.
97 | assertNotEq(newContractMsgSender, computedAddress, "1000");
98 | assertNotEq(newContractMsgSender, zeroAddress, "1100");
99 | assertNotEq(newContractMsgSender.code.length, 0, "1200");
100 | assertEq(newContractMsgSender.balance, msgValue, "1300");
101 | assertEq(createXAddr.balance, 0, "1400");
102 | assertEq(ERC20MockPayable(newContractMsgSender).name(), arg1, "1500");
103 | assertEq(ERC20MockPayable(newContractMsgSender).symbol(), arg2, "1600");
104 | assertEq(ERC20MockPayable(newContractMsgSender).balanceOf(arg3), arg4, "1700");
105 |
106 | // We mock the original caller.
107 | vm.startPrank(originalDeployer);
108 | address newContractOriginalDeployer = createX.deployCreate2{value: msgValue}(cachedInitCode);
109 | vm.stopPrank();
110 | vm.assume(originalDeployer != newContractOriginalDeployer);
111 |
112 | // The newly created contract on chain `chainId` must not be the same as the previously created
113 | // contract at the `computedAddress` address as well as at the `newContractMsgSender` address.
114 | assertNotEq(newContractOriginalDeployer, computedAddress, "1800");
115 | assertNotEq(newContractOriginalDeployer, newContractMsgSender, "1900");
116 | assertNotEq(newContractOriginalDeployer, zeroAddress, "2000");
117 | assertNotEq(newContractOriginalDeployer.code.length, 0, "2100");
118 | assertEq(newContractOriginalDeployer.balance, msgValue, "2200");
119 | assertEq(createXAddr.balance, 0, "2300");
120 | assertEq(ERC20MockPayable(newContractOriginalDeployer).name(), arg1, "2400");
121 | assertEq(ERC20MockPayable(newContractOriginalDeployer).symbol(), arg2, "2500");
122 | assertEq(ERC20MockPayable(newContractOriginalDeployer).balanceOf(arg3), arg4, "2600");
123 | }
124 |
125 | modifier whenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithAZeroLength() {
126 | _;
127 | }
128 |
129 | function testFuzz_WhenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithAZeroLength(
130 | address originalDeployer,
131 | uint256 msgValue
132 | ) external whenTheInitCodeSuccessfullyCreatesARuntimeBytecodeWithAZeroLength {
133 | msgValue = bound(msgValue, 0, type(uint64).max);
134 | vm.deal(originalDeployer, msgValue);
135 | vm.assume(originalDeployer != createXAddr && originalDeployer != zeroAddress);
136 | vm.startPrank(originalDeployer);
137 | bytes32 salt = createXHarness.exposed_generateSalt();
138 | vm.stopPrank();
139 | (bool permissionedDeployProtection, bool xChainRedeployProtection, bool mustRevert, ) = parseFuzzerSalt(
140 | originalDeployer,
141 | salt
142 | );
143 | // When we pseudo-randomly calculate the salt value `salt`, we must never have configured a permissioned
144 | // deploy protection or a cross-chain redeploy protection, and it must never revert.
145 | assertTrue(!permissionedDeployProtection && !xChainRedeployProtection && !mustRevert, "100");
146 | vm.startPrank(originalDeployer);
147 | // It should revert.
148 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXAddr);
149 | vm.expectRevert(expectedErr);
150 | createX.deployCreate2{value: msgValue}(new bytes(0));
151 | vm.stopPrank();
152 | }
153 |
154 | modifier whenTheInitCodeFailsToDeployARuntimeBytecode() {
155 | _;
156 | }
157 |
158 | function testFuzz_WhenTheInitCodeFailsToDeployARuntimeBytecode(
159 | address originalDeployer,
160 | uint256 msgValue
161 | ) external whenTheInitCodeFailsToDeployARuntimeBytecode {
162 | msgValue = bound(msgValue, 0, type(uint64).max);
163 | vm.deal(originalDeployer, msgValue);
164 | vm.assume(originalDeployer != createXAddr && originalDeployer != zeroAddress);
165 | vm.startPrank(originalDeployer);
166 | bytes32 salt = createXHarness.exposed_generateSalt();
167 | vm.stopPrank();
168 | (bool permissionedDeployProtection, bool xChainRedeployProtection, bool mustRevert, ) = parseFuzzerSalt(
169 | originalDeployer,
170 | salt
171 | );
172 | // When we pseudo-randomly calculate the salt value `salt`, we must never have configured a permissioned
173 | // deploy protection or a cross-chain redeploy protection, and it must never revert.
174 | assertTrue(!permissionedDeployProtection && !xChainRedeployProtection && !mustRevert, "100");
175 | // The following contract creation code contains the invalid opcode `PUSH0` (`0x5F`) and `CREATE` must therefore
176 | // return the zero address (technically zero bytes `0x`), as the deployment fails. This test also ensures that if
177 | // we ever accidentally change the EVM version in Foundry and Hardhat, we will always have a corresponding failed test.
178 | bytes memory invalidInitCode = hex"5f_80_60_09_3d_39_3d_f3";
179 | vm.startPrank(originalDeployer);
180 | // It should revert.
181 | bytes memory expectedErr = abi.encodeWithSelector(CreateX.FailedContractCreation.selector, createXAddr);
182 | vm.expectRevert(expectedErr);
183 | createX.deployCreate2{value: msgValue}(invalidInitCode);
184 | vm.stopPrank();
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/interface/src/pages/deployments.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import {
3 | ArrowTopRightOnSquareIcon,
4 | ChevronDownIcon,
5 | } from "@heroicons/react/20/solid";
6 | import { ExternalLink } from "@/components/layout/ExternalLink";
7 | import { Head } from "@/components/layout/Head";
8 | import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
9 |
10 | interface Deployment {
11 | name: string;
12 | chainId: number;
13 | urls: string[];
14 | address?: `0x${string}`;
15 | }
16 |
17 | const Deployments = () => {
18 | // -------- Fetch deployments --------
19 | const [deployments, setDeployments] = useState([] as Deployment[]);
20 | const [isLoading, setIsLoading] = useState(true);
21 | const deploymentsUrls =
22 | "https://github.com/pcaversaccio/createx/blob/main/deployments/deployments.json";
23 | const deploymentsUrlsRaw =
24 | "https://raw.githubusercontent.com/pcaversaccio/createx/main/deployments/deployments.json";
25 |
26 | useEffect(() => {
27 | fetch(deploymentsUrlsRaw)
28 | .then((response) => response.json())
29 | .then((data: Deployment[]) =>
30 | setDeployments(data.sort((a, b) => a.chainId - b.chainId)),
31 | )
32 | .catch((error) => console.error("Error:", error))
33 | .finally(() => setIsLoading(false));
34 | }, []);
35 |
36 | // -------- Focus search input when user presses Cmd/Ctrl + K --------
37 | const searchInputRef = useRef(null);
38 | const modifierKey = navigator.userAgent.includes("Mac") ? "⌘ " : "Ctrl + ";
39 |
40 | useEffect(() => {
41 | const handleKeyDown = (e: KeyboardEvent) => {
42 | if ((e.metaKey || e.ctrlKey) && e.key === "k") {
43 | e.preventDefault();
44 | searchInputRef.current?.focus();
45 | }
46 | };
47 |
48 | window.addEventListener("keydown", handleKeyDown);
49 | return () => window.removeEventListener("keydown", handleKeyDown);
50 | }, []);
51 |
52 | // -------- Sorting and filtering --------
53 | const [sortField, setSortField] = useState(null as "name" | "chainId" | null);
54 | const [sortDirection, setSortDirection] = useState("ascending");
55 | const [search, setSearch] = useState("");
56 |
57 | const onHeaderClick = (field: "name" | "chainId") => {
58 | if (sortField === field) {
59 | setSortDirection(
60 | sortDirection === "ascending" ? "descending" : "ascending",
61 | );
62 | } else {
63 | setSortField(field);
64 | setSortDirection("ascending");
65 | }
66 | };
67 |
68 | const sortedDeployments = deployments.sort((a, b) => {
69 | // Don't change default sort order if sort field is null.
70 | if (sortField === null) return 0;
71 |
72 | const aValue = a[sortField];
73 | const bValue = b[sortField];
74 | if (sortDirection === "ascending") {
75 | return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
76 | } else {
77 | return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
78 | }
79 | });
80 |
81 | const filteredDeployments = sortedDeployments.filter(
82 | (deployment) =>
83 | deployment.name.toLowerCase().includes(search.toLowerCase()) ||
84 | deployment.chainId.toString().includes(search),
85 | );
86 |
87 | const loadingDiv = () => (
88 |
89 |
90 |
Fetching deployments...
91 |
92 | );
93 |
94 | const errorDiv = () => (
95 |
96 |
97 |
98 | 🥴Oops!
99 |
100 |
101 | Something went wrong fetching the list of deployments.
102 |
103 |
104 |
110 |
114 | <>
115 | View as JSON{" "}
116 |
120 | >
121 |
122 |
123 |
124 |
125 | );
126 |
127 | const noDeploymentsDiv = () => (
128 |
129 |
130 |
131 | No deployments found.
132 |
133 |
134 | If you need CreateX deployed on a new chain,
135 |
136 | please{" "}
137 | {" "}
141 | on GitHub.
142 |
143 |
144 |
145 | );
146 |
147 | const showDeploymentsDiv = () => (
148 |
149 |
150 |
151 |
152 | |
156 | onHeaderClick("name")}
159 | >
160 | Name
161 |
162 |
163 |
164 |
165 | |
166 |
170 | onHeaderClick("chainId")}
173 | >
174 | Chain ID
175 |
176 |
177 |
178 |
179 | |
180 |
181 | Edit
182 | |
183 |
184 |
185 |
186 | {filteredDeployments.map((deployment) => (
187 |
191 | window.open(deployment.urls[0], "_blank", "noopener,noreferrer")
192 | }
193 | >
194 | |
195 | {deployment.name}
196 | {deployment.address && (
197 |
198 | {`${deployment.address?.slice(
199 | 0,
200 | 6,
201 | )}...${deployment.address?.slice(-4)}`}
202 |
203 | )}
204 | |
205 |
206 | {deployment.chainId}
207 | |
208 |
209 |
210 |
214 |
215 | Open contract in block explorer
216 |
217 |
218 | |
219 |
220 | ))}
221 |
222 |
223 |
224 |
225 | Showing {filteredDeployments.length} of {deployments.length}{" "}
226 | deployments.
227 |
228 |
229 |
230 | );
231 |
232 | const deploymentsTableDiv = () => (
233 | <>
234 |
235 |
236 | {modifierKey}K
237 |
238 | setSearch(e.target.value)}
244 | ref={searchInputRef}
245 | />
246 |
247 | {filteredDeployments.length === 0 && noDeploymentsDiv()}
248 | {filteredDeployments.length > 0 && showDeploymentsDiv()}
249 | >
250 | );
251 |
252 | // -------- Render --------
253 | return (
254 | <>
255 |
256 |
257 |
258 |
259 | {isLoading && loadingDiv()}
260 | {!isLoading && deployments.length === 0 && errorDiv()}
261 | {!isLoading && deployments.length > 0 && deploymentsTableDiv()}
262 |
263 |
264 |
265 | >
266 | );
267 | };
268 |
269 | export default Deployments;
270 |
--------------------------------------------------------------------------------