Skip to content

Commit

Permalink
Improve docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Lohann committed Sep 24, 2024
1 parent 1adc304 commit f99775a
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 92 deletions.
107 changes: 63 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,83 +10,102 @@
### 1. Initialize immutables without constructor parameters.

You can initialize to set immutables without having to set any parameter, once constructor parameters changes the final `create2` address.
In this example, only the account `0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef` can create this contract, so as long the `creationCode` and `salt` doesn't change, the final address doesn't change either.
In this example, only the contract owner can create this contract, so as long the `owner`, `creationCode` and `salt` doesn't change, the final address doesn't change either.
```solidity
import {Context, IUniversalFactory} from "universal-factory/src/UniversalFactory.sol";
import {Context, IUniversalFactory} from "@universal-factory/UniversalFactory.sol";
contract CrossChainOwned {
IUniversalFactory internal constant FACTORY = IUniversalFactory(0x000000000000e27221183e5c85058c31df6a7d01);
address internal constant OWNER = IUniversalFactory(0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef);
uint256 immutable public IMMUTABLE_VALUE;
contract Owned {
IUniversalFactory internal constant FACTORY = IUniversalFactory(0x000000000000E27221183E5C85058c31DF6a7D01);
uint256 private immutable IMMUTABLE_VALUE;
address public owner;
constructor() {
require(msg.sender == address(FACTORY), "can only be created using Universal Factory");
Context memory ctx = factory.context();
// Any value defined in the constructor influences the CREATE2 address.
constructor(address _owner) {
Context memory ctx = FACTORY.context();
require(ctx.contractAddress == address(this), "can only be created using Universal Factory");
// Actual sender, who called the UniversalFactory
require(ctx.sender == address(OWNER), "unauthorized");
// Only the `_owner` can create this contract.
require(ctx.sender == address(_owner), "unauthorized");
// Can initialize immutable, without influecing the create2 address.
owner = _owner;
IMMUTABLE_VALUE = abi.decode(ctx.data, (uint256));
}
function value() external view returns (uint256) {
return IMMUTABLE_VALUE;
}
}
```

How to deploy using foundry script.
How to deploy using [foundry script](https://book.getfoundry.sh/tutorials/solidity-scripting#writing-the-script).
```solidity
import {CrossChainOwned} from "./CrossChainOwned.sol";
address owner = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef;
uint256 salt = 1234;
bytes memory creationCode = type(CrossChainOwned).creationCode;
bytes memory arguments = abi.encode(uint256(1337));
vm.startBroadcast(owner);
CrossChainOwned myContract = CrossChainOwned(factory.create2(salt, creationCode, arguments));
vm.stopBroadcast();
import {Owned} from "../src/Owned.sol";
import {Script, console} from "forge-std/Script.sol";
contract DeplotOwned is Script {
function run() external {
// SALT=1234 VALUE=321 forge script ./script/DeployOwned.s.sol --private-key $PRIVATE_KEY --broadcast
address owner = msg.sender;
uint256 salt = vm.envUint("SALT");
uint256 value = vm.envUint("VALUE");
bytes memory creationCode = bytes.concat(type(Owned).creationCode, abi.encode(owner));
bytes memory arguments = abi.encode(value);
vm.startBroadcast(msg.sender);
Owned owned = Owned(FACTORY.create2(salt, creationCode, arguments));
vm.stopBroadcast();
}
}
```

### 2. Reserved Addresses
Reserve an address, and use it to deploy an custom bytecode later in ANY network.
### 2. Callbacks and reserved addresses
Reserve an address, and use it to deploy an custom bytecode in ANY network.
```solidity
import {Context, IUniversalFactory} from "universal-factory/src/UniversalFactory.sol";
contract Reserved {
IUniversalFactory internal constant FACTORY = IUniversalFactory(0x000000000000e27221183e5c85058c31df6a7d01);
address internal constant OWNER = IUniversalFactory(0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef);
IUniversalFactory internal constant FACTORY = IUniversalFactory(0x000000000000E27221183E5C85058c31DF6a7D01);
address internal constant OWNER = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF;
constructor() payable {
require(msg.sender == address(FACTORY), "can only be created using Universal Factory");
Context memory ctx = factory.context();
require(ctx.sender == address(OWNER), "unauthorized");
require(ctx.contractAddress == address(this), "unauthorized");
// If a callback is provided, the method `deploy` must be called.
if (ctx.hasCallback) {
require(ctx.callbackSelector == Proxy.deploy.selector, "invalid callback");
}
}
// Create an arbitrary contract at an deterministic address that only the
// `OWNER` can use.
bytes memory creationCode = ctx.data;
address newContract;
// only the `owner` can call this method, or `factory` when a callback is provided.
function deploy(bytes memory creationCode) payable returns (address addr) {
require(msg.sender == address(OWNER) || msg.sender == address(FACTORY), "unauthorized");
assembly {
newContract := create(selfbalance(), add(creationCode, 0x20), mload(creationCode))
// Prefer `address(this).balance` over `msg.value` for Frontier compatibility.
addr := create(selfbalance(), add(creationCode, 0x20), mload(creationCode))
}
require(newContract != address(0), "failed to create contract");
require(addr != address(0), "failed to create contract");
}
}
```

How to deploy using foundry script.
```solidity
import {Reserved} from "./Reserved.sol";
contract MyContract {
constructor() { ... }
import {MyContract} from "../src/MyContract.sol";
import {Script, console} from "forge-std/Script.sol";
contract DeplotMyContract is Script {
function run() external {
uint256 salt = vm.envUint("SALT");
bytes memory creationCode = type(Reserved).creationCode;
bytes memory callback = abi.encodeCall(Reserved.deploy, (type(MyContract).creationCode));
vm.startBroadcast(msg.sender);
Owned owned = Owned(FACTORY.create2(salt, creationCode, "", callback));
vm.stopBroadcast();
}
}
IUniversalFactory factory = IUniversalFactory(0x000000000000e27221183e5c85058c31df6a7d01);
uint256 salt = 1;
bytes memory reservedContract = type(Reserved).creationCode;
bytes memory creationCode = type(MyContract).creationCode;
// The final contract address is only influenced by `salt` and `reservedContract`.
vm.startBroadcast();
MyContract myContract = MyContract(factory.create2(salt, reservedContract, creationCode));
vm.stopBroadcast();
```

Information available in the **Context**:
Expand Down
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
forge-std/=lib/forge-std/src/
@universal-factory/=src/
91 changes: 91 additions & 0 deletions src/FactoryUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {IUniversalFactory} from "./UniversalFactory.sol";

library FactoryUtils {
// Create3Proxy creation code
// 0x763318602e57363d3d37363d34f080915215602e57f35bfd6017526460203d3d7360a01b33173d5260306007f3:
// 0x00 0x67 0x763318602e.. PUSH23 0x3318.. 0x3318602e57363d3d37363d34f080915215602e57f35bfd
// 0x01 0x3d 0x3d PUSH1 0x58 23 0x3318602e57363d3d37363d34f080915215602e57f35bfd
// 0x01 0x3d 0x3d MSTORE
// 0x03 0x52 0x5260203d3d.. PUSH5 0x60203.. 0x60203d3d73
// 0x04 0xf3 0x6008 PUSH1 0xa0 160 0x60203d3d73
// 0x05 0x60 0x6018 SHL 0x60203d3d730000000000000000000000000000000000000000
// 0x06 0x3d 0x3d CALLER addr 0x60203d3d730000000000000000000000000000000000000000
// 0x08 0xf3 0xf3 OR 0x60203d3d73XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// 0x09 0x60 0x6018 RETURNDATASIZE 0 0x60203d3d73XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// 0x04 0xf3 0x6008 PUSH1 0x30 48
// 0x04 0xf3 0x6008 PUSH1 0x07 7 48
// 0x14 0x3d 0x3d RETURN
bytes internal constant PROXY_INITCODE =
hex"763318602e57363d3d37363d34f080915215602e57f35bfd6017526460203d3d7360a01b33173d5260306007f3";
bytes32 internal constant PROXY_INITCODE_HASH = keccak256(PROXY_INITCODE);

/**
* @dev Compute the create2 address of an contract created by `UniversalFactory`.
*/
function computeCreate2Address(IUniversalFactory factory, uint256 salt, bytes memory initcode)
internal
pure
returns (address)
{
return computeCreate2Address(factory, salt, keccak256(initcode));
}

/**
* @dev Compute the create2 address of an contract created by `UniversalFactory`.
*/
function computeCreate2Address(IUniversalFactory factory, uint256 salt, bytes32 initcodeHash)
internal
pure
returns (address addr)
{
// The code below is equivalent to the following Solidity code:
// ```solidity
// bytes32 create2hash = keccak256(abi.encodePacked(uint8(0xff), address(factory), salt, initcodeHash));
// return address(uint160(uint256(create2hash)));
// ```
assembly ("memory-safe") {
// Cache the free memory pointer.
let free_ptr := mload(0x40)
{
mstore(0x00, factory)
mstore8(11, 0xff)
mstore(0x20, salt)
mstore(0x40, initcodeHash)
addr := shr(96, shl(96, keccak256(11, 85)))
}
// Restore the free memory pointer.
mstore(0x40, free_ptr)
}
}

/**
* @dev Compute the create3 address of an contract created by `UniversalFactory`.
*/
function computeCreate3Address(IUniversalFactory factory, uint256 salt) internal pure returns (address addr) {
// The code below is equivalent to the following Solidity code:
// ```solidity
// address create2addr = computeCreate2Address(factory, salt, PROXY_INITCODE_HASH);
// bytes32 create3hash = keccak256(abi.encodePacked(bytes2(0xd694), create2addr, uint8(0x01)));
// return address(uint160(uint256(create3hash)));
// ```
assembly ("memory-safe") {
// Cache the free memory pointer.
let free_ptr := mload(0x40)
{
mstore(0x00, factory)
mstore8(11, 0xff)
mstore(0x20, salt)
mstore(0x40, 0xda812570be8257354a14ed469885e4d206be920835861010301b25f5c180427a)
mstore(0x14, keccak256(11, 85))
mstore(0x00, 0xd694)
mstore8(0x34, 0x01)
addr := shr(96, shl(96, keccak256(30, 23)))
}
// Restore the free memory pointer.
mstore(0x40, free_ptr)
}
}
}
44 changes: 18 additions & 26 deletions src/UniversalFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
* ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
*
*/
pragma solidity ^0.8.27;
pragma solidity ^0.8.0;

/**
* @dev The type of create operation being used by the current context.
Expand Down Expand Up @@ -124,9 +124,7 @@ interface IUniversalFactory {
function create2(uint256 salt, bytes calldata creationCode) external payable returns (address);

/**
* Same as above, except it also accept a callback to call the contract after it is created, useful for initialize proxies for example.
* The contract contructor can enforce it is initialized by retrieving the `Context` and checking the `hasInitializer`,
* `initializerSlice` and `initializerLength` fields.
* Same as above, except it also accept `arguments` useful for initialize the contract constructor, args are available at `Context.data`.
*
* @param salt Salt of the contract creation, this value affect the resulting address.
* @param creationCode Creation code (constructor) of the contract to be deployed, this value affect the resulting address.
Expand All @@ -139,8 +137,6 @@ interface IUniversalFactory {

/**
* Same as above, except it also accept a callback to call the contract after it is created, useful for initialize proxies for example.
* The contract contructor can enforce it is initialized by retrieving the `Context` and checking the `hasInitializer`,
* `initializerSlice` and `initializerLength` fields.
*
* @param salt Salt of the contract creation, this value affect the resulting address.
* @param creationCode Creation code (constructor) of the contract to be deployed, this value affect the resulting address.
Expand All @@ -167,9 +163,7 @@ interface IUniversalFactory {
function create3(uint256 salt, bytes calldata creationCode) external payable returns (address);

/**
* Same as above, except it also accept a callback to call the contract after it is created, useful for initialize proxies for example.
* The contract contructor can enforce it is initialized by retrieving the `Context` and checking the `hasInitializer`,
* `initializerSlice` and `initializerLength` fields.
* Same as above, except it also accept `arguments` useful for initialize the contract constructor, args are available at `Context.data`.
*
* @param salt Salt of the contract creation, this value affect the resulting address.
* @param creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address.
Expand All @@ -182,8 +176,6 @@ interface IUniversalFactory {

/**
* Same as above, except it also accept a callback to call the contract after it is created, useful for initialize proxies for example.
* The contract contructor can enforce it is initialized by retrieving the `Context` and checking the `hasInitializer`,
* `initializerSlice` and `initializerLength` fields.
*
* @param salt Salt of the contract creation, this value affect the resulting address.
* @param creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address.
Expand Down Expand Up @@ -327,7 +319,7 @@ contract UniversalFactory {
if eq(caller(), address()) { return(0, tload(address())) }

// ------- BITFLAGS -------
// HAS_DATA = 0x01
// HAS_ARGUMENTS = 0x01
// HAS_CALLBACK = 0x02
// IS_CREATE3 = 0x04
// SUPPORT_EIP1153 = 0x08
Expand Down Expand Up @@ -447,7 +439,7 @@ contract UniversalFactory {
// Check if the method contains an `data` but not an `callback`
// - function create2(uint256 salt, bytes calldata creationCode, bytes calldata data)
// - function create3(uint256 salt, bytes calldata creationCode, bytes calldata data)
let has_data := or(has_callback, or(eq(selector, 0x579da0bf), eq(selector, 0xb5164ce9)))
let has_args := or(has_callback, or(eq(selector, 0x579da0bf), eq(selector, 0xb5164ce9)))

// Check if the method doesn't contain an `data` or `callback`
// - function create2(uint256 salt, bytes calldata creationCode)
Expand All @@ -459,17 +451,17 @@ contract UniversalFactory {
or(or(eq(selector, 0x53ca4842), eq(selector, 0xb5164ce9)), eq(selector, 0x1f7a56c0))

// Check if the selector is valid
let valid := or(is_simple, has_data)
let valid := or(is_simple, has_args)
{
// Check the minimal calldatasize when `data` or `callback` are provided
let min_calldatasize := add(0x43, shl(5, add(has_data, has_callback)))
let min_calldatasize := add(0x43, shl(5, add(has_args, has_callback)))
valid := and(valid, gt(calldatasize(), min_calldatasize))
}

// Set `deploy_proxy`, `has_callback` and `has_data` flags
// Set `deploy_proxy`, `has_callback` and `has_args` flags
bitflags := or(shl(1, bitflags), is_create3)
bitflags := or(shl(1, bitflags), has_callback)
bitflags := or(shl(1, bitflags), has_data)
bitflags := or(shl(1, bitflags), has_args)

if iszero(valid) {
// Revert if the selector is invalid
Expand Down Expand Up @@ -507,7 +499,7 @@ contract UniversalFactory {
// creationcode_len > 0
valid := and(valid, gt(creationcode_len, 0))

// store the `creationcode_ptr` and `creationcode_len` at static memory 0xa0-0xc0
// store the `creationcode_ptr` and `creationcode_len` at static memory 0xc0-0xe0
mstore(0xc0, creationcode_ptr)
mstore(0xe0, creationcode_len)
}
Expand All @@ -518,8 +510,8 @@ contract UniversalFactory {
{
// initializer_ptr <= 0xffffffffffffffff
let arguments_ptr := calldataload(0x44)
let has_data := and(bitflags, 0x01)
let valid_initializer := and(has_data, lt(arguments_ptr, 0x010000000000000000))
let has_args := and(bitflags, 0x01)
let valid_initializer := and(has_args, lt(arguments_ptr, 0x010000000000000000))
// initializer_ptr > (has_callback ? 0x7f : 0x5f)
{
let has_callback := shl(4, and(bitflags, 0x02))
Expand All @@ -540,16 +532,16 @@ contract UniversalFactory {
and(valid_initializer, iszero(gt(add(arguments_ptr, arguments_len), calldatasize())))

// Set arguments_ptr and arguments_len to zero if there's no initializer
valid_initializer := and(valid_initializer, has_data)
valid_initializer := and(valid_initializer, has_args)
arguments_ptr := mul(arguments_ptr, valid_initializer)
arguments_len := mul(arguments_len, valid_initializer)

// store the `arguments_ptr` and `arguments_len` at static memory 0x00e0-0x0100
// store the `arguments_ptr` and `arguments_len` at static memory 0x0100-0x0120
mstore(0x0100, arguments_ptr)
mstore(0x0120, arguments_len)

// If the call has no initializer, it is always valid.
valid := and(valid, or(valid_initializer, iszero(has_data)))
valid := and(valid, or(valid_initializer, iszero(has_args)))
}

/////////////////////////
Expand Down Expand Up @@ -580,7 +572,7 @@ contract UniversalFactory {
callback_ptr := mul(callback_ptr, valid_callback)
callback_len := mul(callback_len, valid_callback)

// store the `callback_ptr` and `callback_len` at static memory 0x0120-0x0140
// store the `callback_ptr` and `callback_len` at static memory 0x0140-0x0160
mstore(0x0140, callback_ptr)
mstore(0x0160, callback_len)

Expand Down Expand Up @@ -749,9 +741,9 @@ contract UniversalFactory {
let slot0
// Encode `data[96..128]` (32 bits)
{
let has_data := and(bitflags, 0x01)
let has_args := and(bitflags, 0x01)
let data := shr(224, shl(96, calldataload(arguments_ptr)))
data := mul(data, has_data)
data := mul(data, has_args)
slot0 := data
}
// Encode data_len (22 bits)
Expand Down
Loading

0 comments on commit f99775a

Please sign in to comment.