-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: support create3 * fix: resolve issues per comments * chore: resolve issues from comments
- Loading branch information
1 parent
9e648d1
commit fe0da95
Showing
9 changed files
with
597 additions
and
1 deletion.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
.forge-snapshots/Create2FactoryTest#test_Deploy_ContractWithArgs.snap
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 +1 @@ | ||
106498 | ||
108872 |
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,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); | ||
} | ||
} |
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,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(); | ||
} | ||
} | ||
} | ||
} |
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,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; | ||
} |
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,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"))))); | ||
} | ||
} |
Oops, something went wrong.