Skip to content

Commit

Permalink
feat: CREATE4!
Browse files Browse the repository at this point in the history
  • Loading branch information
dyedm1 committed Sep 24, 2023
1 parent da02759 commit 3491ed1
Show file tree
Hide file tree
Showing 10 changed files with 1,001 additions and 90 deletions.
2 changes: 0 additions & 2 deletions .gas-snapshot

This file was deleted.

698 changes: 674 additions & 24 deletions LICENSE

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# CREATE4

[![tests](https://github.com/dyedm1/create4/actions/workflows/tests.yml/badge.svg)](https://github.com/0xsequence/create4/actions/workflows/tests.yml)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

## Brief history lesson
[EIP-3171](https://github.com/ethereum/EIPs/pull/3171) was an EIP that introduced a new native opcode (`CREATE3`) that enabled deterministic contract creation similar to that of `CREATE2` but with the `initcode` removed from the address derivation formula. Unfortunately, the EIP was rejected, in part due to the emergence of [a popular application-layer approximation bearing the same name.](https://github.com/0xsequence/create3) However, this approximation leveraged a combination of the `CREATE` and `CREATE2` opcodes to achieve its functionality, and in doing so increased the compute cost of mining efficient addresses by severalfold while also breaking compatibility with [existing salt mining tooling.](https://github.com/0age/create2crunch)

## Summary
`CREATE4` is a different approach to approximating the functionality lost in the rejection of EIP-3171. Despite the name, the address derivation formula for `CREATE4` is significantly closer to what a native `CREATE3` opcode would have possessed than the original [`CREATE3` app-layer implementation](https://github.com/0xsequence/create3) was. Because `CREATE4` uses a single `CREATE2` call with a fixed `initCode` under the hood, [create2crunch](https://github.com/0age/create2crunch) can be used to mine `CREATE4` salts for efficient addresses at no additional compute cost over `CREATE2` mining.

## Create4Factory (`CREATE4` Implementation)

### How
1. Store the `deployedCode` of the desired contract somewhere on-chain
(in order of gas cost)
a. If the network supports store the code in transient storage with `TSTORE`
b. If the network does not support [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153) deploy the contract using your method of choice and use the address as a reference to the code
2. Expose the `deployedCode` via a getter function with support for returning from both transient storage and from a contract address based on previously set flags in storage. This ensures that the method of code storage does not affect the address derivation formula.
3. `CREATE2` with a user's salt and "bootstrap" `initCode` containing their address somewhere (for front-running protection) that queries the `deployedCode` from the getter function and returns it.
4. We now have a process that can be used to deploy any bytecode with a deterministic address based on the `msg.sender` and a salt.

### Initcode for address derivation

For `Create4Factory`, the `initCode` is constructed as follows:
- Code segment 1: `5f5f5f5f5f73`
- `Create4Factory` address: `ffffffffffffffffffffffffffffffffffffffff`
- Code segment 2: `5af13d5f5f3e3d5ff3`
- Deployer address (user calling `create4`): `ffffffffffffffffffffffffffffffffffffffff`
Result: `0x5f5f5f5f5f735<Create4Factory address>5af13d5f5f3e3d5ff3<deployer address>`
#### Example
If we have
- `Create4Factory` address: `0xe358511cd9bf45c8a4d4aaf96ad5f6234ad20282` (note: not the real-world address!)
- Deployer address: `0xab5801a7d398351b8be11c439e05c5b3259aec9b` (Vb)
Our `initCode` would be:
`0x5f5f5f5f5f735e358511cd9bf45c8a4d4aaf96ad5f6234ad202825af13d5f5f3e3d5ff3ab5801a7d398351b8be11c439e05c5b3259aec9b`

### Features

- Deterministic contract address based on `msg.sender` + salt
- Simple, `CREATE2`-compatible address derivation formula with ideal compute cost
- Front-running protection
- Cheaper than [CREATE3](https://github.com/0xsequence/create3) for smaller contracts on chains with EIP-1153 support
- Same contract addresses on different EVM networks (even those without support for EIP-1153)
- Supports any EVM compatible chain with support for CREATE2 (& PUSH0)

### Limitations

- ~2x deployment cost increase for chains without EIP-1153 (transient storage) support
- ~5% deployment cost increase for chains with EIP-1153 support
- No constructor support (deployed bytecode must be precomputed)

### Cross-chain Deployments

None yet :)

### Usage

Call the `create4` method on the `Create4Factory`, provide the contract `creationCode` (on EIP-1153 networks) or the address of a deployed contract containing the code you want to deploy (on non-EIP-1153 networks) and a salt. Different contract codes will result on the same address as long as the same salt is provided.

### Install (interface)
```bash
forge install dyedm1/create4
```

## Commands

### Build
```bash
forge build --use bin/solc
```

### Run tests
```bash
forge test --use bin/solc
```

### Format
```bash
forge fmt
```

### Contract example

```javascript
//SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.20;

import {Create4} from "lib/create4/src/Create4Factory.sol";


contract CreateMe {
function test() external view returns (uint256) {
return 0x1337;
}
}

contract Deployer {
function deployChild() external {
Create4Factory(0x0000000000000000000000000000000000000000).create4(type(CreateMe).deploymentCode, bytes32(0x1337));
}
}
```


# License
Code in this repository is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) unless otherwise indicated (see [LICENSE](./LICENSE) file).
24 changes: 0 additions & 24 deletions Readme.md

This file was deleted.

4 changes: 3 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[profile.default]
cancun = true
[profile.default.fuzz]
runs = 1_000_000
[profile.ci.fuzz]
runs = 10_000
runs = 100_000
2 changes: 1 addition & 1 deletion lib/forge-std
Submodule forge-std updated 48 files
+134 −0 .github/workflows/ci.yml
+29 −0 .github/workflows/sync.yml
+0 −21 .github/workflows/tests.yml
+2 −0 .gitignore
+1 −1 LICENSE-APACHE
+1 −1 LICENSE-MIT
+42 −18 README.md
+21 −0 foundry.toml
+1 −1 lib/ds-test
+16 −0 package.json
+35 −0 src/Base.sol
+27 −0 src/Script.sol
+376 −0 src/StdAssertions.sol
+236 −0 src/StdChains.sol
+817 −0 src/StdCheats.sol
+15 −0 src/StdError.sol
+107 −0 src/StdInvariant.sol
+179 −0 src/StdJson.sol
+43 −0 src/StdMath.sol
+378 −0 src/StdStorage.sol
+333 −0 src/StdStyle.sol
+198 −0 src/StdUtils.sol
+32 −370 src/Test.sol
+558 −45 src/Vm.sol
+1 −0 src/console.sol
+1,558 −0 src/console2.sol
+105 −0 src/interfaces/IERC1155.sol
+12 −0 src/interfaces/IERC165.sol
+43 −0 src/interfaces/IERC20.sol
+190 −0 src/interfaces/IERC4626.sol
+164 −0 src/interfaces/IERC721.sol
+73 −0 src/interfaces/IMulticall3.sol
+13,248 −0 src/safeconsole.sol
+0 −129 src/test/StdCheats.t.sol
+0 −276 src/test/StdStorage.t.sol
+1,015 −0 test/StdAssertions.t.sol
+220 −0 test/StdChains.t.sol
+610 −0 test/StdCheats.t.sol
+17 −22 test/StdError.t.sol
+212 −0 test/StdMath.t.sol
+315 −0 test/StdStorage.t.sol
+110 −0 test/StdStyle.t.sol
+342 −0 test/StdUtils.t.sol
+10 −0 test/compilation/CompilationScript.sol
+10 −0 test/compilation/CompilationScriptBase.sol
+10 −0 test/compilation/CompilationTest.sol
+10 −0 test/compilation/CompilationTestBase.sol
+187 −0 test/fixtures/broadcast.log.json
89 changes: 89 additions & 0 deletions src/Create4Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.20;

contract Create4Factory {
// hex("EIP-1153")
// We set currentDeployment to this address to signal that the code can be read from transient storage
address internal constant EIP_1153_MAGIC = address(0x4549502d31313533);

/*
[26 BYTES]
PUSH0 // retSize: 0
PUSH0 // retOffset: 0
PUSH0 // argsSize: 0
PUSH0 // argsOffset: 0
PUSH0 // value: 0
PUSH20 (0x0000000000000000000000000000000000000000) // FACTORY ADDRESS(THIS), FILLED DYNAMICALLY
Right-padded with 0s to 32 bytes
*/
bytes32 internal constant BOOTSTRAP_CODE_SECTION_1 =
hex"5f5f5f5f5f73000000000000000000000000000000000000000000000000";

/*
[29 BYTES]
GAS // gas (gas remaining in frame)
CALL
RETURNDATASIZE // size (of returned code to copy)
PUSH0 // offset: 0
PUSH0 // destOffset: 0
RETURNDATACOPY
RETURNDATASIZE // size (of code to return/deploy)
PUSH0 // offset: 0
RETURN
(0x0000000000000000000000000000000000000000) // MSG.SENDER, FILLED DYNAMICALLY
Right-padded with 0s to 32 bytes
*/
bytes32 internal constant BOOTSTRAP_CODE_SECTION_2 =
hex"5af13d5f5f3e3d5ff30000000000000000000000000000000000000000000000";

address internal currentDeployment;

uint96 internal currentSize;

function create4(address deployedCode, bytes32 salt) public returns (address newContract) {
currentDeployment = deployedCode;

assembly {
mstore(0, BOOTSTRAP_CODE_SECTION_1)
mstore(6, shl(96, address()))
mstore(26, BOOTSTRAP_CODE_SECTION_2)
mstore(35, shl(96, caller()))
newContract := create2(0, 0, 55, salt)
}
}

function create4(bytes memory deployedCode, bytes32 salt) public returns (address newContract) {
currentDeployment = EIP_1153_MAGIC;
uint96 size = uint96(deployedCode.length);
currentSize = size;

assembly {
for { let i := 0 } lt(mul(i, 32), size) { i := add(i, 1) } {
tstore(i, mload(add(deployedCode, add(32, mul(i, 32)))))
}
mstore(0, BOOTSTRAP_CODE_SECTION_1)
mstore(6, shl(96, address()))
mstore(26, BOOTSTRAP_CODE_SECTION_2)
mstore(35, shl(96, caller()))
newContract := create2(0, 0, 55, salt)
}
}

receive() external payable {
address _currentDeployment = currentDeployment;
if (_currentDeployment == EIP_1153_MAGIC) {
uint96 size = currentSize;
assembly {
for { let i := 0 } lt(mul(i, 32), size) { i := add(i, 1) } { mstore(mul(i, 32), tload(i)) }
return(0, size)
}
} else {
// use assembly to bypass ABI encoding and make output easier to process from bootstrap code
assembly {
let size := extcodesize(_currentDeployment)
extcodecopy(_currentDeployment, 0, 0, size)
return(0, size)
}
}
}
}
15 changes: 0 additions & 15 deletions src/SimpleTStore.sol

This file was deleted.

127 changes: 127 additions & 0 deletions test/Create4Factory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.13;

import {Test, console2} from "forge-std/Test.sol";

import {Create4Factory} from "src/Create4Factory.sol";

contract TestCreate4Factory is Test {
bytes6 internal constant BOOTSTRAP_CODE_SECTION_1 = hex"5f5f5f5f5f73";
bytes9 internal constant BOOTSTRAP_CODE_SECTION_2 = hex"5af13d5f5f3e3d5ff3";
address internal constant EIP_1153_MAGIC = address(0x4549502d31313533);

Create4Factory internal C4F;

function expectedAddress(address deployer, bytes32 salt) internal returns (address) {
bytes32 bootstrapCodeHash = keccak256(
abi.encodePacked(
BOOTSTRAP_CODE_SECTION_1, bytes20(address(C4F)), BOOTSTRAP_CODE_SECTION_2, bytes20(address(deployer))
)
);

return
address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(C4F), salt, bootstrapCodeHash)))));
}

function test_Success_create4_ExternalCode(
address factoryAddress,
address deployer,
address deployedCodeAddress,
bytes memory code,
bytes32 salt
) public {
if (code.length > 0) {
// According to https://eips.ethereum.org/EIPS/eip-3541 contract creation (via create transaction, CREATE or CREATE2 instructions) results in an exceptional abort if the code’s first byte is 0xEF.
vm.assume(code[0] != bytes1(0xef));
}

// Assume all actors have unique addresses
vm.assume(factoryAddress != deployer);
vm.assume(factoryAddress != deployedCodeAddress);
vm.assume(deployer != deployedCodeAddress);

// Assume nothing is set to the cheatcode address because somehow Forge found a fuzz seed that sets something to this and breaks it
vm.assume(factoryAddress != address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D));
vm.assume(deployer != address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D));
vm.assume(deployedCodeAddress != address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D));

// We're not allowed to etch to addresses lower than 10, so assume the factory and deployed code addresses we etch to are at least address(11)
vm.assume(factoryAddress > address(10));
vm.assume(deployedCodeAddress > address(10));

// Ensure deployedCodeAddress isn't inadvertently set to the magic address, which would cause the contract to try to read the code from transient storage
vm.assume(deployedCodeAddress != EIP_1153_MAGIC);

vm.etch(factoryAddress, type(Create4Factory).runtimeCode);
C4F = Create4Factory(payable(factoryAddress));

vm.etch(deployedCodeAddress, code);

vm.prank(deployer);

address newContract = C4F.create4(deployedCodeAddress, salt);

assertEq(
newContract,
expectedAddress(deployer, salt),
"Create4Factory returned an address that does not match the deployer and provided salt"
);
assertEq(newContract.code, code, "The provided code was not present at the address of the new contract");
}

function test_Success_create4_DirectCode_Transient(
address factoryAddress,
address deployer,
bytes memory code,
bytes32 salt
) public {
if (code.length > 0) {
// According to https://eips.ethereum.org/EIPS/eip-3541 contract creation (via create transaction, CREATE or CREATE2 instructions) results in an exceptional abort if the code’s first byte is 0xEF.
vm.assume(code[0] != bytes1(0xef));
}

// Assume nothing is set to the cheatcode address because somehow Forge found a fuzz seed that sets something to this and breaks it
vm.assume(factoryAddress != address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D));
vm.assume(deployer != address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D));

// We're not allowed to etch to addresses lower than 10, so assume the factory address we etch to is at least least address(11)
vm.assume(factoryAddress > address(10));

vm.etch(factoryAddress, type(Create4Factory).runtimeCode);
C4F = Create4Factory(payable(factoryAddress));

vm.prank(deployer);
address newContract = C4F.create4(code, salt);

assertEq(
newContract,
expectedAddress(deployer, salt),
"Create4Factory returned an address that does not match the deployer and provided salt"
);
assertEq(newContract.code, code, "The provided code was not present at the address of the new contract");
}

/// forge-config: default.fuzz.runs = 100
/// forge-config: ci.fuzz.runs = 10
function test_Success_create4_Multiple(
address factoryAddress,
bool[1000] calldata ops,
address[1000] calldata deployers,
address[1000] calldata deployedCodeAddresses,
bytes[1000] memory codes,
bytes32[1000] calldata salts
) public {
C4F = Create4Factory(payable(factoryAddress));
for (uint256 i = 0; i < 1000; i++) {
// You can't deploy a contract to an address that already has code, but if there was previously a deployment resulting in an empty code, you can deploy to that address again!
if (expectedAddress(deployers[i], salts[i]).code.length > 0) continue;
if (ops[i]) {
test_Success_create4_ExternalCode(
factoryAddress, deployers[i], deployedCodeAddresses[i], codes[i], salts[i]
);
} else {
test_Success_create4_DirectCode_Transient(factoryAddress, deployers[i], codes[i], salts[i]);
}
}
}
}
Loading

0 comments on commit 3491ed1

Please sign in to comment.