generated from hrkrshnn/tstore-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
1,001 additions
and
90 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Submodule forge-std
updated
48 files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.