Skip to content

Commit

Permalink
feat: support create3 (#3)
Browse files Browse the repository at this point in the history
* feat: support create3

* fix: resolve issues per comments

* chore: resolve issues from comments
  • Loading branch information
chefburger authored Oct 17, 2024
1 parent 9e648d1 commit fe0da95
Show file tree
Hide file tree
Showing 9 changed files with 597 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
106498
108872
56 changes: 56 additions & 0 deletions src/Create3Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {ICreate3Factory} from "./interfaces/ICreate3Factory.sol";
import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Create3} from "./libraries/Create3.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/// @notice Deploy contracts in a deterministic way using CREATE3
/// @dev ensure this contract is deployed on multiple chain with the same address
contract Create3Factory is ICreate3Factory, Ownable2Step, ReentrancyGuard {
event SetWhitelist(address indexed user, bool isWhitelist);
event Deployed(address indexed deployed, bytes32 salt, bytes32 creationCodeHash);

// Only whitelisted user can interact with create2Factory
mapping(address user => bool isWhitelisted) public isUserWhitelisted;

modifier onlyWhitelisted() {
if (!isUserWhitelisted[msg.sender]) revert NotWhitelisted();
_;
}

constructor() Ownable(msg.sender) {
isUserWhitelisted[msg.sender] = true;
}

/// @inheritdoc ICreate3Factory
function deploy(
bytes32 salt,
bytes memory creationCode,
bytes32 creationCodeHash,
uint256 creationFund,
bytes calldata afterDeploymentExecutionPayload,
uint256 afterDeploymentExecutionFund
) external payable onlyWhitelisted nonReentrant returns (address deployed) {
if (creationCodeHash != keccak256(creationCode)) revert CreationCodeHashMismatch();

deployed = Create3.create3(
salt, creationCode, creationFund, afterDeploymentExecutionPayload, afterDeploymentExecutionFund
);

emit Deployed(deployed, salt, creationCodeHash);
}

/// @inheritdoc ICreate3Factory
function computeAddress(bytes32 salt) public view returns (address) {
return Create3.addressOf(salt);
}

/// @inheritdoc ICreate3Factory
function setWhitelistUser(address user, bool isWhiteList) external onlyOwner {
isUserWhitelisted[user] = isWhiteList;

emit SetWhitelist(user, isWhiteList);
}
}
44 changes: 44 additions & 0 deletions src/CustomizedProxyChild.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract CustomizedProxyChild {
error NotFromParent();
error BackrunExecutionFailed();

address public immutable parent;

constructor() {
parent = msg.sender;
}

function deploy(bytes memory creationCode, uint256 creationFund, bytes calldata backRunPayload, uint256 backRunFund)
external
payable
returns (address addr)
{
// make sure only Create3Factory can deploy contract in case of unauthorized deployment
if (msg.sender != parent) {
revert NotFromParent();
}

assembly {
/// @dev create with creation code, deterministic addr since create(thisAddr=fixed, nonce=0)
addr := create(creationFund, add(creationCode, 32), mload(creationCode))
}

if (backRunPayload.length != 0) {
/// @dev This could be helpful when newly deployed contract
/// needs to run some initialization logic for example owner update
(bool success, bytes memory reason) = addr.call{value: backRunFund}(backRunPayload);
if (!success) {
// bubble up the revert reason from backrun if any
if (reason.length > 0) {
assembly {
revert(add(reason, 32), mload(reason))
}
}
revert BackrunExecutionFailed();
}
}
}
}
33 changes: 33 additions & 0 deletions src/interfaces/ICreate3Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICreate3Factory {
error NotWhitelisted();
error CreationCodeHashMismatch();

/**
* @notice create3 deploy a contract
* @dev So long the same salt is used, the contract will be deployed at the same address on other chain
* @param salt Salt of the contract creation, resulting address will be derived from this value only
* @param creationCode Creation code (constructor + args) of the contract to be deployed, this value doesn't affect the resulting address
* @param creationCodeHash Hash of the creation code, it can be used to verify the creation code
* @param creationFund In WEI of ETH to be forwarded to target contract constructor
* @param afterDeploymentExecutionPayload Payload to be executed after contract creation
* @param afterDeploymentExecutionFund In WEI of ETH to be forwarded to when executing after deployment initialization
* @return deployed of the deployed contract, reverts on error
*/
function deploy(
bytes32 salt,
bytes memory creationCode,
bytes32 creationCodeHash,
uint256 creationFund,
bytes calldata afterDeploymentExecutionPayload,
uint256 afterDeploymentExecutionFund
) external payable returns (address deployed);

/// @notice compute the create3 address based on salt
function computeAddress(bytes32 salt) external view returns (address);

/// @notice set user as whitelisted
function setWhitelistUser(address user, bool isWhiteList) external;
}
84 changes: 84 additions & 0 deletions src/libraries/Create3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {CustomizedProxyChild} from "../CustomizedProxyChild.sol";

/**
* @dev Referenced from https://github.com/0xsequence/create3/blob/master/contracts/Create3.sol
* Updated PROXY_CHILD_BYTECODE to support customized deployment logic
* @title A library for deploying contracts EIP-3171 style.
* @author Agustin Aguilar <[email protected]>
*/
library Create3 {
error ErrorCreatingProxy();
error ErrorCreatingContract();
error TargetAlreadyExists();

bytes internal constant PROXY_CHILD_BYTECODE = type(CustomizedProxyChild).creationCode;

bytes32 internal constant KECCAK256_PROXY_CHILD_BYTECODE = keccak256(PROXY_CHILD_BYTECODE);

/**
* @notice Returns the size of the code on a given address
* @param _addr Address that may or may not contain code
* @return size of the code on the given `_addr`
*/
function codeSize(address _addr) internal view returns (uint256 size) {
assembly {
size := extcodesize(_addr)
}
}

/**
* @notice Creates a new contract with given `_creationCode` and `_salt`
* @param _salt Salt of the contract creation, resulting address will be derived from this value only
* @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
* @param _creationFund In WEI of ETH to be forwarded to target contract constructor
* @param _afterDeploymentExecutionPayload Payload to be executed after contract creation
* @param _afterDeploymentExecutionFund In WEI of ETH to be forwarded to when executing after deployment initialization
* @return addr of the deployed contract, reverts on error
*/
function create3(
bytes32 _salt,
bytes memory _creationCode,
uint256 _creationFund,
bytes calldata _afterDeploymentExecutionPayload,
uint256 _afterDeploymentExecutionFund
) internal returns (address addr) {
// Creation code
bytes memory proxyCreationCode = PROXY_CHILD_BYTECODE;

// Get target final address
address preCalculatedAddr = addressOf(_salt);
if (codeSize(preCalculatedAddr) != 0) revert TargetAlreadyExists();

// Create CREATE2 proxy
address proxy;
assembly {
proxy := create2(0, add(proxyCreationCode, 32), mload(proxyCreationCode), _salt)
}
if (proxy == address(0)) revert ErrorCreatingProxy();

// Call proxy with final init code to deploy target contract
addr = CustomizedProxyChild(proxy).deploy{value: _creationFund + _afterDeploymentExecutionFund}(
_creationCode, _creationFund, _afterDeploymentExecutionPayload, _afterDeploymentExecutionFund
);

if (preCalculatedAddr != addr) revert ErrorCreatingContract();
}

/**
* @notice Computes the resulting address of a contract deployed using address(this) and the given `_salt`
* @param _salt Salt of the contract creation, resulting address will be derived from this value only
* @return addr of the deployed contract, reverts on error
*
* @dev The address creation formula is: keccak256(rlp([keccak256(0xff ++ address(this) ++ _salt ++ keccak256(childBytecode))[12:], 0x01]))
*/
function addressOf(bytes32 _salt) internal view returns (address) {
address proxy = address(
uint160(uint256(keccak256(abi.encodePacked(hex"ff", address(this), _salt, KECCAK256_PROXY_CHILD_BYTECODE))))
);

return address(uint160(uint256(keccak256(abi.encodePacked(hex"d694", proxy, hex"01")))));
}
}
Loading

0 comments on commit fe0da95

Please sign in to comment.