Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ docs/

# Dotenv file
.env

.DS_Store
17 changes: 7 additions & 10 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
{
"printWidth": 120,
"tabWidth": 4,
"useTabs": true,
"singleQuote": false,
"trailingComma": "all",
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 120,
"tabWidth": 4,
"useTabs": true,
"singleQuote": false,
"bracketSpacing": false
}
},
{
"files": ["*.json"],
"options": {
"tabWidth": 4,
"useTabs": true,
"singleQuote": false
}
"files": ["*.ts", "*.js", "*.json"],
"options": {}
}
]
}
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@

## Features

- **CREATE**: Traditional contract deployment
- **CREATE2**: Deterministic contract deployment
- **CREATE3**: Chain-agnostic deterministic deployment
- **EIP-1167 Clone**: Minimal proxy pattern deployment
- **EIP-1167 CloneDeterministic**: Deterministic minimal proxy deployment
- **Address Prediction**: Compute contract addresses before deployment
- **Dual Approach**: Use as a library in your contracts or interact with the deployed factory
- **CREATE**: Traditional contract deployment
- **CREATE2**: Deterministic contract deployment
- **CREATE3**: Chain-agnostic deterministic deployment
- **EIP-1167 Clone**: Minimal proxy pattern deployment
- **EIP-1167 CloneDeterministic**: Deterministic minimal proxy deployment
- **Address Prediction**: Compute contract addresses before deployment
- **Dual Approach**: Use as a library in your contracts or interact with the deployed factory

## Directory

```text
createx/
├── deployments/...
├── script/
│ ├── Create.s.sol
│ ├── CreateX.s.sol
│ └── Deploy.s.sol
├── src/
│ ├── CreateX.sol
Expand Down Expand Up @@ -322,19 +322,19 @@ function deployCreateX(

The factory includes built-in access control through salt validation:

- For salted deployments (CREATE2, CREATE3, CloneDeterministic), the first 20 bytes of the salt must match either:
- The caller's address (for user-specific deployments)
- Zero address (for open deployments)
- For salted deployments (CREATE2, CREATE3, CloneDeterministic), the first 20 bytes of the salt must match either:
- The caller's address (for user-specific deployments)
- Zero address (for open deployments)

---

## Acknowledgements

The following repositories served as key references during the development of this project:

- [Solady](https://github.com/Vectorized/solady)
- [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts)
- [Solady](https://github.com/Vectorized/solady)
- [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts)

## Author

- [fomoweth](https://github.com/fomoweth)
- [fomoweth](https://github.com/fomoweth)
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ ffi = true
optimizer = true
optimizer_runs = 44444444
via_ir = true

fs_permissions = [
{ access = "read-write", path = "./deployments"},
{ access = "read", path = "./out"},
{ access = "read", path = "./script" },
{ access = "read", path = "./test"}
]

gas_reports = ["CreateXFactory"]

[fuzz]
Expand Down
121 changes: 121 additions & 0 deletions script/BaseScript.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import {Script, console2 as console, stdJson} from "forge-std/Script.sol";
import {ICreateXFactory} from "src/ICreateXFactory.sol";
import {VmSafe} from "forge-std/Vm.sol";

abstract contract BaseScript is Script {
using stdJson for string;

string private constant DEFAULT_MNEMONIC = "test test test test test test test test test test test junk";

string private constant DEFAULT_CHAINS = "ethereum, optimism, polygon, base, arbitrum";

address internal broadcaster;

modifier broadcast() {
vm.startBroadcast(broadcaster);
_;
vm.stopBroadcast();
}

modifier fork(string memory chainAlias) {
vm.createSelectFork(chainAlias);
_;
}

function setUp() public virtual {
broadcaster = vm.rememberKey(configurePrivateKey());
}

function configurePrivateKey() internal view virtual returns (uint256 privateKey) {
privateKey = vm.envOr({
name: "PRIVATE_KEY",
defaultValue: vm.deriveKey({
mnemonic: vm.envOr({name: "MNEMONIC", defaultValue: DEFAULT_MNEMONIC}),
index: uint8(vm.envOr({name: "EOA_INDEX", defaultValue: uint256(0)}))
})
});
}

function generateJson(string memory path, string memory name, address instance, bytes32 salt) internal virtual {
string memory json = "json";
json.serialize("address", instance);
json.serialize("blockNumber", vm.getBlockNumber());
json.serialize("timestamp", vm.getBlockTimestamp());
json.serialize("salt", salt);
json = json.serialize("name", name);
json.write(path);
}

function promptChains() internal virtual returns (string[] memory chainAliases) {
string memory input = prompt("Chains separated by ','", defaultChains());
return vm.split(vm.replace(input, " ", ""), ",");
}

function prompt(string memory promptText) internal returns (string memory input) {
return prompt(promptText, new string(0));
}

function prompt(string memory promptText, string memory defaultValue) internal returns (string memory input) {
input = vm.prompt(string.concat(promptText, " (default: `", defaultValue, "`)"));
if (bytes(input).length == 0) input = defaultValue;
}

function promptAddress(string memory promptText, address defaultValue) internal returns (address) {
return vm.parseAddress(prompt(promptText, vm.toString(defaultValue)));
}

function promptAddress(string memory promptText) internal returns (address) {
return promptAddress(promptText, address(0));
}

function promptBool(string memory promptText, bool defaultValue) internal returns (bool) {
return vm.parseBool(prompt(promptText, vm.toString(defaultValue)));
}

function promptBool(string memory promptText) internal returns (bool) {
return promptBool(promptText, false);
}

function promptUint256(string memory promptText, uint256 defaultValue) internal returns (uint256) {
return vm.parseUint(prompt(promptText, vm.toString(defaultValue)));
}

function promptUint256(string memory promptText) internal returns (uint256) {
return promptUint256(promptText, uint256(0));
}

function promptInt256(string memory promptText, int256 defaultValue) internal returns (int256) {
return vm.parseInt(prompt(promptText, vm.toString(defaultValue)));
}

function promptInt256(string memory promptText) internal returns (int256) {
return promptInt256(promptText, int256(0));
}

function promptBytes32(string memory promptText, bytes32 defaultValue) internal returns (bytes32) {
return vm.parseBytes32(prompt(promptText, vm.toString(defaultValue)));
}

function promptBytes32(string memory promptText) internal returns (bytes32) {
return promptBytes32(promptText, bytes32(0));
}

function promptBytes(string memory promptText, bytes memory defaultValue) internal returns (bytes memory) {
return vm.parseBytes(prompt(promptText, vm.toString(defaultValue)));
}

function promptBytes(string memory promptText) internal returns (bytes memory) {
return promptBytes(promptText, new bytes(0));
}

function defaultChains() internal view virtual returns (string memory chains) {
return DEFAULT_CHAINS;
}

function defaultSalt() internal view virtual returns (bytes32) {
return bytes32(0);
}
}
145 changes: 145 additions & 0 deletions script/CreateX.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import {ICreateXFactory} from "src/ICreateXFactory.sol";
import {CreateX as CreateXLib} from "src/CreateX.sol";
import {BaseScript} from "./BaseScript.sol";
import {ERC1967_PROXY_BYTECODE, TRANSPARENT_PROXY_BYTECODE} from "./Precompiles.sol";

contract CreateX is BaseScript {
ICreateXFactory internal constant CREATEX_FACTORY = ICreateXFactory(0xfC5D1D7b066730fC403C994365205a96fE1d8Bcf);

function deployContract() internal virtual broadcast returns (address) {
uint256 creationType = promptCreationType();
bytes memory initCode;
bytes32 salt;
uint256 value;

if (creationType < uint8(ICreateXFactory.CreationType.Clone)) {
bytes memory creationCode = vm.getCode(prompt("Artifact path"));
bytes memory arguments = promptBytes("Constructor arguments");
initCode = bytes.concat(creationCode, arguments);
} else {
address implementation = vm.promptAddress("Implementation");
initCode = abi.encodePacked(implementation);
}

if (
creationType != uint8(ICreateXFactory.CreationType.CREATE) &&
creationType != uint8(ICreateXFactory.CreationType.Clone)
) {
salt = promptBytes32("Salt", defaultSalt());
}

value = promptUint256("msg.value");

return deployCreateX(creationType, initCode, salt, value);
}

function deployCreateX(uint256 creationType, bytes memory initCode, bytes32 salt) internal returns (address) {
return deployCreateX(creationType, initCode, salt, 0);
}

function deployCreateX(
uint256 creationType,
bytes memory initCode,
bytes32 salt,
uint256 value
) internal returns (address) {
return CREATEX_FACTORY.createX{value: value}(asCreationType(creationType), initCode, salt);
}

function deployCreate(bytes memory initCode) internal returns (address) {
return deployCreate(initCode, 0);
}

function deployCreate(bytes memory initCode, uint256 value) internal returns (address) {
return CREATEX_FACTORY.create{value: value}(initCode);
}

function deployCreate2(bytes memory initCode, bytes32 salt) internal returns (address) {
return deployCreate2(initCode, salt, 0);
}

function deployCreate2(bytes memory initCode, bytes32 salt, uint256 value) internal returns (address) {
return CREATEX_FACTORY.create2{value: value}(initCode, salt);
}

function deployCreate3(bytes memory initCode, bytes32 salt) internal returns (address) {
return deployCreate3(initCode, salt, 0);
}

function deployCreate3(bytes memory initCode, bytes32 salt, uint256 value) internal returns (address) {
return CREATEX_FACTORY.create3{value: value}(initCode, salt);
}

function deployClone(address implementation) internal returns (address) {
return deployClone(implementation, 0);
}

function deployClone(address implementation, uint256 value) internal returns (address) {
return CREATEX_FACTORY.clone{value: value}(implementation);
}

function deployCloneDeterministic(address implementation, bytes32 salt) internal returns (address) {
return deployCloneDeterministic(implementation, salt, 0);
}

function deployCloneDeterministic(address implementation, bytes32 salt, uint256 value) internal returns (address) {
return CREATEX_FACTORY.cloneDeterministic{value: value}(implementation, salt);
}

function deployERC1967Proxy(
address implementation,
bytes memory data,
uint256 value
) internal returns (address proxy) {
bytes memory arguments = abi.encode(implementation, data);
bytes memory initCode = bytes.concat(ERC1967_PROXY_BYTECODE, arguments);
return deployCreate(initCode, value);
}

function deployERC1967Proxy(
address implementation,
bytes memory data,
bytes32 salt,
uint256 value
) internal returns (address proxy) {
bytes memory arguments = abi.encode(implementation, data);
bytes memory initCode = bytes.concat(ERC1967_PROXY_BYTECODE, arguments);
return deployCreate2(initCode, salt, value);
}

function deployTransparentProxy(
address owner,
address implementation,
bytes memory data,
uint256 value
) internal returns (address proxy, address proxyAdmin) {
bytes memory arguments = abi.encode(owner, implementation, data);
bytes memory initCode = bytes.concat(TRANSPARENT_PROXY_BYTECODE, arguments);
proxyAdmin = CreateXLib.computeCreateAddress(proxy = deployCreate(initCode, value), 1);
}

function deployTransparentProxy(
address owner,
address implementation,
bytes memory data,
bytes32 salt,
uint256 value
) internal returns (address proxy, address proxyAdmin) {
bytes memory arguments = abi.encode(owner, implementation, data);
bytes memory initCode = bytes.concat(TRANSPARENT_PROXY_BYTECODE, arguments);
proxyAdmin = CreateXLib.computeCreateAddress(proxy = deployCreate2(initCode, salt, value), 1);
}

function promptCreationType() internal returns (uint256) {
string memory promptText = "CreationType (0: CREATE, 1: CREATE2, 2: CREATE3, 3: Clone, 4: CloneDeterministic)";
return vm.promptUint(promptText);
}

function asCreationType(uint256 creationType) internal pure returns (ICreateXFactory.CreationType) {
if (creationType > uint8(type(ICreateXFactory.CreationType).max)) revert ICreateXFactory.InvalidCreationType();
return ICreateXFactory.CreationType(creationType);
}
}
Loading