foundry
foundry copied to clipboard
feat: migrate to `CreateX` for `CREATE2` factory (or let user define create2 factory)
Component
Forge
Describe the feature you would like
Define CREATE2 Factory
I do not want to use your factory, I want to use my own factory.
Additional context
https://etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c no https://goerli.etherscan.io/address/0x179c98f5ccdb3bc7d7aefffa06fb7f608a301877#code yes
This is a good config option to add, e.g. create2_deployer = 0x123.... Alternative name ideas: create2_deployer_contract or create2_factory
Especially considering how etherscan represents the current factory, teams may prefer to have their own canonical factory (nothing preventing other people from using it ofc) etc etc
The main reasons to use another create2 deployer are (1) pass init data to be called after deploy, (2) more leading zeros for cheaper deploy, and I think that's it? Given that, an alternative is to mine a cheap address, agree on the deploy contract's code, deploy it to all the chains, and replace the current default. One downside here is this is a breaking change for anyone relying on the current deployer for vanity addresses.
@sambacha would that work as an alternative / are there other reasons people might need a custom deployer?
The main reasons to use another create2 deployer are (1) pass init data to be called after deploy, (2) more leading zeros for cheaper deploy, and I think that's it? Given that, an alternative is to mine a cheap address, agree on the deploy contract's code, deploy it to all the chains, and replace the current default. One downside here is this is a breaking change for anyone relying on the current deployer for vanity addresses.
@sambacha would that work as an alternative / are there other reasons people might need a custom deployer?
The factory contract I linked has additional benefits such as:
-
There is also a view function that computes the address of the contract that will be created when submitting a given salt or nonce along with a given block of initialization code.
-
Determine if a contract has already been deployed by the factory to a given address.
-
Modifier to ensure that the first 20 bytes of a submitted salt match those of the calling account. This provides protection against the salt being stolen by frontrunners or other attackers.
Yes for leading zeros, I am using this: https://github.com/0age/create2crunch
wdyt @mds1
I think all of those are good ideas to include in a canonical create2 deployer contract 👌
Perhaps next week I can scaffold out a proposed interface
The way we verify contracts or create the transaction depends on the factory we use. So just letting anyone use their own factory implementation is not that straightforward imo. So... if we can agree to a certain interface, it should be fine.
One downside here is this is a breaking change for anyone relying on the current deployer for vanity addresses
True...
The way we verify contracts or create the transaction depends on the factory we use. So just letting anyone use their own factory implementation is not that straightforward imo. So... if we can agree to a certain interface, it should be fine.
One downside here is this is a breaking change for anyone relying on the current deployer for vanity addresses
True...
if your gonna break it, break it before v1.0
This is a good config option to add, e.g.
create2_deployer = 0x123.... Alternative name ideas:create2_deployer_contractorcreate2_factory
FoundryCreate2FactoryV8
Proposed create2 deployer below, feedback welcome. Interfaces (and the code comments) are largely based on @0age's create2 deployer, with a few modifications based on the above.
contract Create2Deployer {
// Mapping to track which addresses have already been deployed.
mapping(address => bool) public isDeployed;
/// @dev Create a contract using CREATE2 by submitting a given salt or nonce
/// along with the initialization code for the contract.
/// @return Address of the contract that will be created, or the null address
/// if a contract already exists at that address.
function create2(bytes32 salt, bytes calldata initCode)
external
payable
returns (address deploymentAddr);
/// @dev Create a contract using CREATE2 by submitting a given salt or nonce
/// along with the initialization code for the contract, and call the
/// contract after deployment with the provided data.
/// @return Address of the contract that will be created, or the null address
/// if a contract already exists at that address.
function create2(bytes32 salt, bytes calldata initCode, bytes calldata data)
external
payable
returns (address deploymentAddr);
/// @dev Create a contract using CREATE2 by submitting a given salt or nonce
/// along with the initialization code for the contract.
/// @dev The first 20 bytes of the salt must match those of the calling
/// address, which prevents contract creation events from being submitted by
/// unintended parties.
/// @return Address of the contract that will be created, or the null address
/// if a contract already exists at that address.
function safeCreate2(bytes32 salt, bytes calldata initCode)
external
payable
containsCaller(salt)
returns (address deploymentAddr);
/// @dev Create a contract using CREATE2 by submitting a given salt or nonce
/// along with the initialization code for the contract, and call the
/// contract after deployment with the provided data.
/// @dev The first 20 bytes of the salt must match those of the calling
/// address, which prevents contract creation events from being submitted by
/// unintended parties.
/// @return Address of the contract that will be created, or the null address
/// if a contract already exists at that address.
function safeCreate2(bytes32 salt, bytes calldata initCode, bytes calldata data)
external
payable
containsCaller(salt)
returns (address deploymentAddr);
/// @dev Compute the address of the contract that will be created when
/// submitting a given salt or nonce to the contract along with the contract's
/// initialization code.
/// @return Address of the contract that will be created, or the null address
/// if a contract has already been deployed to that address.
function computeCreate2Address(bytes32 salt, bytes calldata initCode)
external
view
returns (address deploymentAddr);
/// @dev Compute the address of the contract that will be created when
/// submitting a given salt or nonce to the contract along with the keccak256
/// hash of the contract's initialization code.
/// @return Address of the contract that will be created, or the null address
/// if a contract has already been deployed to that address.
function computeCreate2Address(bytes32 salt, bytes32 initCodeHash)
external
view
returns (address deploymentAddress);
/// @dev Modifier to ensure that the first 20 bytes of a submitted salt match
/// those of the calling account. This provides protection against the salt
/// being stolen by frontrunners or other attackers. The protection can also be
/// bypassed if desired by setting each of the first 20 bytes to zero.
/// @param salt bytes32 The salt value to check against the calling address.
modifier containsCaller(bytes32 salt) {
require(
(address(bytes20(salt)) == msg.sender) || (bytes20(salt) == bytes20(0)),
"Invalid salt: first 20 bytes of the salt must match calling address."
);
_;
}
}
We can arguably remove either (1) the containsCaller bypass of bytes20(salt) == bytes20(0) OR (2) the dedicated create2 methods, since it's a bit redundant, but I think it's worth keeping both because:
- Existing tooling for mining vanity addresses fits better with
create2(i.e. they don't often let you add limitations on the salt) - Removing the bypass doesn't save much gas anyway so can't hurt to keep it to add compatibility for anyone who relies on that
cc'ing a few people for feedback who I think are interested: @sambacha @gakonst @joshieDo @pcaversaccio @storming0x
Proposed create2 deployer below, feedback welcome. Interfaces (and the code comments) are largely based on @0age's create2 deployer, with a few modifications based on the above.
contract Create2Deployer { // Mapping to track which addresses have already been deployed. mapping(address => bool) public isDeployed; /// @dev Create a contract using CREATE2 by submitting a given salt or nonce /// along with the initialization code for the contract. /// @return Address of the contract that will be created, or the null address /// if a contract already exists at that address. function create2(bytes32 salt, bytes calldata initCode) external payable returns (address deploymentAddr); /// @dev Create a contract using CREATE2 by submitting a given salt or nonce /// along with the initialization code for the contract, and call the /// contract after deployment with the provided data. /// @return Address of the contract that will be created, or the null address /// if a contract already exists at that address. function create2(bytes32 salt, bytes calldata initCode, bytes calldata data) external payable returns (address deploymentAddr); /// @dev Create a contract using CREATE2 by submitting a given salt or nonce /// along with the initialization code for the contract. /// @dev The first 20 bytes of the salt must match those of the calling /// address, which prevents contract creation events from being submitted by /// unintended parties. /// @return Address of the contract that will be created, or the null address /// if a contract already exists at that address. function safeCreate2(bytes32 salt, bytes calldata initCode) external payable containsCaller(salt) returns (address deploymentAddr); /// @dev Create a contract using CREATE2 by submitting a given salt or nonce /// along with the initialization code for the contract, and call the /// contract after deployment with the provided data. /// @dev The first 20 bytes of the salt must match those of the calling /// address, which prevents contract creation events from being submitted by /// unintended parties. /// @return Address of the contract that will be created, or the null address /// if a contract already exists at that address. function safeCreate2(bytes32 salt, bytes calldata initCode, bytes calldata data) external payable containsCaller(salt) returns (address deploymentAddr); /// @dev Compute the address of the contract that will be created when /// submitting a given salt or nonce to the contract along with the contract's /// initialization code. /// @return Address of the contract that will be created, or the null address /// if a contract has already been deployed to that address. function computeCreate2Address(bytes32 salt, bytes calldata initCode) external view returns (address deploymentAddr); /// @dev Compute the address of the contract that will be created when /// submitting a given salt or nonce to the contract along with the keccak256 /// hash of the contract's initialization code. /// @return Address of the contract that will be created, or the null address /// if a contract has already been deployed to that address. function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external view returns (address deploymentAddress); /// @dev Modifier to ensure that the first 20 bytes of a submitted salt match /// those of the calling account. This provides protection against the salt /// being stolen by frontrunners or other attackers. The protection can also be /// bypassed if desired by setting each of the first 20 bytes to zero. /// @param salt bytes32 The salt value to check against the calling address. modifier containsCaller(bytes32 salt) { require( (address(bytes20(salt)) == msg.sender) || (bytes20(salt) == bytes20(0)), "Invalid salt: first 20 bytes of the salt must match calling address." ); _; } }We can arguably remove either (1) the
containsCallerbypass ofbytes20(salt) == bytes20(0)OR (2) the dedicatedcreate2methods, since it's a bit redundant, but I think it's worth keeping both because:
- Existing tooling for mining vanity addresses fits better with
create2(i.e. they don't often let you add limitations on the salt)- Removing the bypass doesn't save much gas anyway so can't hurt to keep it to add compatibility for anyone who relies on that
cc'ing a few people for feedback who I think are interested: @sambacha @gakonst @joshieDo @pcaversaccio @storming0x
Should there be any chain id information included ?
What chain ID information did you have in mind?
Another method that hashes your salt with chain ID to prevent redeploying to another chain at the same address might be a good idea
What chain ID information did you have in mind?
Another method that hashes your salt with chain ID to prevent redeploying to another chain at the same address might be a good idea
This is what I had in mind, but then again I am not sure if its even useful.
It would be useful in the edge case of contentious forks, so def YMMV
/// constructor
deploymentChainId = block.chainid;
_DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);
/// ... Return the DOMAIN_SEPARATOR.
return block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid);
/// ...abi.encode
block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid),
Thanks for tagging, yeah i think this covers our use case mostly, being able to deploy init type smart contracts with create2
@mds1 thanks for tagging me - the suggested interface would not be compatible with my Create2Deployer.sol code. I use the following semantics:
/**
* @dev Deploys a contract using `CREATE2`. The address where the
* contract will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `value`.
* - if `value` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 value, bytes32 salt, bytes memory code) external returns (address addr);
/**
* @dev Deployment of the {ERC1820Implementer}.
* Further information: https://eips.ethereum.org/EIPS/eip-1820
*/
function deployERC1820Implementer(uint256 value, bytes32 salt) external returns (address addr);
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}.
* Any change in the `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 codeHash) external view returns (address addr);
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a
* contract located at `deployer`. If `deployer` is this contract's address, returns the
* same value as {computeAddress}.
*/
function computeAddressWithDeployer(bytes32 salt, bytes32 codeHash, address deployer) external pure returns (address addr);
The payable feature is implemented via a receive function in the contract itself. So the question is whether the interface should be compatible with my deployed factory or not - generally, I like the definition of the interface except for the keyword safe. Can we get rid of this, please ;-)?
@mds1 thanks for tagging me - the suggested interface would not be compatible with my
Create2Deployer.solcode. I use the following semantics:/** * @dev Deploys a contract using `CREATE2`. The address where the * contract will be deployed can be known in advance via {computeAddress}. * * The bytecode for a contract can be obtained from Solidity with * `type(contractName).creationCode`. * * Requirements: * - `bytecode` must not be empty. * - `salt` must have not been used for `bytecode` already. * - the factory must have a balance of at least `value`. * - if `value` is non-zero, `bytecode` must have a `payable` constructor. */ function deploy(uint256 value, bytes32 salt, bytes memory code) external returns (address addr); /** * @dev Deployment of the {ERC1820Implementer}. * Further information: https://eips.ethereum.org/EIPS/eip-1820 */ function deployERC1820Implementer(uint256 value, bytes32 salt) external returns (address addr); /** * @dev Returns the address where a contract will be stored if deployed via {deploy}. * Any change in the `bytecodeHash` or `salt` will result in a new destination address. */ function computeAddress(bytes32 salt, bytes32 codeHash) external view returns (address addr); /** * @dev Returns the address where a contract will be stored if deployed via {deploy} from a * contract located at `deployer`. If `deployer` is this contract's address, returns the * same value as {computeAddress}. */ function computeAddressWithDeployer(bytes32 salt, bytes32 codeHash, address deployer) external pure returns (address addr);The
payablefeature is implemented via areceivefunction in the contract itself. So the question is whether the interface should be compatible with my deployed factory or not - generally, I like the definition of the interface except for the keywordsafe. Can we get rid of this, please ;-)?
🫂✨⭐️💫
any progress on this? is there a way to define the address for a custom create2 factory?
would love to work on it and open a PR if it makes sense to do so
Nobody's on it, feel free to go for it!
sounds good @gakonst, Ill start working on it.
As per my understanding the main goal here is to add a parameter like create2_deployer which lets a user use a custom create2 factory instead of the default 0x4e59b44847b379578588920ca78fbf26c0b4956c. This is useful for the following reasons:
- Enables usage of alternative improved create2 custom contracts as described above in the thread.
- There is no way to deploy the create2 0x4e59b44847b379578588920ca78fbf26c0b4956c on chains not supporting pre EIP-155 transactions. (The transaction mentioned here can not be replayed https://github.com/Arachnid/deterministic-deployment-proxy#latest-outputs). Eg. CANTO (http://chainlist.org/chain/7700)
Does this make sense? @mds1 @joshieDo
The way we verify contracts or create the transaction depends on the factory we use. So just letting anyone use their own factory implementation is not that straightforward imo. So... if we can agree to a certain interface, it should be fine.
I think the core issue is not just about a custom factory, but agreeing on a specific interface for a new factory.
Sure, having a parameter for a custom factory is nice, but for the reasons given above, it has to share the same interface as the default factory.
This sounds good. I'll add this feature while making sure the create2 interface is shared by the default one
Any movement on this for v1.0
Hello, I didn't get time to work on it earlier, but will now try to open a PR asap
Hello, I didn't get time to work on it earlier, but will now try to open a PR asap
Hell yea lmk if you need any help
Hello @sambacha @joshieDo @mds1
Had a quick question regarding the interface.
Currently for example,
bytes32 constant SALT = bytes32(uint256(0x00...))
vm.startBroadcast();
// deploy with create
permit2 = new Permit2();
// deploy with create2
permit2 = new Permit2{salt: SALT}();
vm.stopBroadcast();
This common create scheme comes directly from the revm library:
pub enum CreateScheme {
/// Legacy create scheme of `CREATE`.
Create,
/// Create scheme of `CREATE2`.
Create2 {
/// Salt.
salt: U256,
},
}
So should we change this schema to accommodate something similar to permit2 = new Permit2{salt: SALT, deployerCreate2: CREATE2_ADDRESS}(); OR instead add a forge-std helper method such as deployCreate2(string contractName, bytes32 salt, bytes memory constructorData, bytes memory initData, address deployer)
Hello @sambacha @joshieDo @mds1
Had a quick question regarding the interface.
Currently for example,
bytes32 constant SALT = bytes32(uint256(0x00...)) vm.startBroadcast(); // deploy with create permit2 = new Permit2(); // deploy with create2 permit2 = new Permit2{salt: SALT}(); vm.stopBroadcast();This common create scheme comes directly from the revm library:
pub enum CreateScheme { /// Legacy create scheme of `CREATE`. Create, /// Create scheme of `CREATE2`. Create2 { /// Salt. salt: U256, }, }So should we change this schema to accommodate something like
permit2 = new Permit2{salt: SALT, deployerCreate2: CREATE2_ADDRESS}();OR instead add a forge-std helper method such asdeployCreate2(string contractName, bytes32 salt, bytes memory constructorData, bytes memory initData, address deployer)
Probably forge std usage but i would wait to hear from Matt first before settling on usage
So should we change this schema to accommodate something like
permit2 = new Permit2{salt: SALT, deployerCreate2: CREATE2_ADDRESS}();
This is not valid solidity and there is currently no plans to preprocess solidity to support custom syntax, so this option isn't currently doable.
OR instead add a forge-std helper method such as
deployCreate2(string contractName, bytes32 salt, bytes memory constructorData, bytes memory initData, address deployer)
This solution is ok, would pair nicely with the create2 helpers recently added in https://github.com/foundry-rs/forge-std/pull/276. There may be alternative function sigs we'd want, would need to think about it. But the custom create2 deployer would need to have the same signature as the default create2 deployer so it's not clear how much value this adds since you can do this on your own anyway.
Another option is to add a new config option to the foundry.toml with some name options defined in https://github.com/foundry-rs/foundry/issues/2638#issuecomment-1206855029, but again the custom create2 deployer would need to have the same signature as the default create2 deployer
There's been a lot of discussion in this issue so I think it's worth backing up a sec to discuss why we want this feature, which we discussed briefly starting here, and using that answer to determine which solution to go with.
IMO the best (but not necessarily simplest) solution is to write a new, more flexible create2 factory that has the features we need and deploy it across all chains. You can then add cheatcodes to instruct forge what specifically to do with the create2 deployer, for example a vm.BatchInitializeCall cheat might take the next create2 deploy and subsequent tx and batch them into the same transaction to the deployer. Or a vm.safeCreate2 cheat might instruct forge to instead use the safeCreate2 method, etc. AFAIK none of other solutions allow native support for this, and the ability to batch contract deployment + initialization is definitely important
Hey guys, any update on this?
Sorry not yet! @akshatcx what's your current plan here?
Hey, any update on this?
We haven't prioritized it, no. Is it a big deal for you? It's a small enough bite that I'd prefer a third party contributor to take. But if it is really important we can try to prio.