├── 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 |
4 | 11 | 19 | 24 | 25 |
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 | 8 | JSON logo 9 | 10 | 11 | 12 | 13 | 14 | 24 | 34 | 35 | 36 | 41 | 46 | 47 | 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 | 27 | 28 | 34 | 38 | 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 | 47 | 52 | 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 |
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 | JSON logo 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 | 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 | 166 | 180 | 183 | 184 | 185 | 186 | {filteredDeployments.map((deployment) => ( 187 | 191 | window.open(deployment.urls[0], "_blank", "noopener,noreferrer") 192 | } 193 | > 194 | 205 | 208 | 219 | 220 | ))} 221 | 222 |
156 |
onHeaderClick("name")} 159 | > 160 | Name 161 | 162 | 164 |
165 |
170 |
onHeaderClick("chainId")} 173 | > 174 | Chain ID 175 | 176 | 178 |
179 |
181 | Edit 182 |
195 | {deployment.name} 196 | {deployment.address && ( 197 |

198 | {`${deployment.address?.slice( 199 | 0, 200 | 6, 201 | )}...${deployment.address?.slice(-4)}`} 202 |

203 | )} 204 |
206 | {deployment.chainId} 207 | 209 |
210 |
218 |
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 | --------------------------------------------------------------------------------