OpenZeppelin / openzeppelin-upgrades

Plugins for Hardhat and Foundry to deploy and manage upgradeable contracts on Ethereum.
MIT License
627 stars 268 forks source link

Ability to deploy proxy using create2 #358

Open Remscar opened 3 years ago

Remscar commented 3 years ago

It would be helpful if it were possible to define a salt when calling deployProxy which would indicate to use CREATE2 and deploy out a proxy at a deterministic address.

Happy to elaborate more, but for instance if I wanted to deploy the same upgradable proxy on two different chains, and make it so they have the same address, it wouldn't be possible when using this library.

Remscar commented 3 years ago

Similar to #272 but smaller in scope. The salt would be only for the proxy itself.

frangio commented 3 years ago

Can you elaborate how this would work? As far as I can tell we would need to deploy a create2 factory contract, which would also need to be on the same address on every network, so it's kind of a circular problem.

Remscar commented 3 years ago

So hopefully it would do something like:

1 Deploy a Create2Factory contract and store reference in the .oz folder (or somewhere) for future use (only do if salt is used) 2 Use the deployed Create2 Factory contract for any 'create2' deployments

  1. When the user called deployProxy with a DeployOptions field which is salt the Create2Factory is used to deploy and initialize an instance of the AdminUpgradeabilityProxy contract with the passed salt

The Create2Factory contract could just be (or based on) the ProxyFactory contract found in the now deprecated @openzeppelin/upgrades package (@openzeppelin/upgrades/contracts/upgradeability/ProxyFactory.sol)

Example of the factory:

contract Create2Factory {
  function deploy(uint256 _salt, address _logic, address _admin, bytes memory _data) public returns (address) {
    return _deployProxy(_salt, _logic, _admin, _data, msg.sender);
  }

  function _deployProxy(uint256 _salt, address _logic, address _admin, bytes memory _data, address _sender) internal returns (address) {
    InitializableAdminUpgradeabilityProxy proxy = _createProxy(_salt, _sender);
    emit ProxyCreated(address(proxy));
    proxy.initialize(_logic, _admin, _data);
    return address(proxy);
  }

  function _createProxy(uint256 _salt, address _sender) internal returns (InitializableAdminUpgradeabilityProxy) {
    address payable addr;
    bytes memory code = type(InitializableAdminUpgradeabilityProxy).creationCode;
    bytes32 salt = _getSalt(_salt, _sender);

    assembly {
      addr := create2(0, add(code, 0x20), mload(code), salt)
      if iszero(extcodesize(addr)) {
        revert(0, 0)
      }
    }

    return InitializableAdminUpgradeabilityProxy(addr);
  }

  function _getSalt(uint256 _salt, address _sender) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(_salt, _sender)); 
  }
}

While it would be nice to have just one Create2Factory contract per network (for all users) so a new one doesn't have to be deployed by an individual user, it would need to be predeployed by the OpenZeppelin team and also raises security questions. Perhaps there would exist a single instance per network that gets used, and if it's not available on the network the user is using, they deploy a new version.

frangio commented 3 years ago

Yeah the hard thing is getting the factory in the same address on every network. As far as I know this is only possible to do reliably using "Nick's method".

The ability to deploy proxies on the same address on every network sounds good, but unfortunately we don't have enough bandwidth right now to make it a priority.

habdelra commented 3 years ago

Gnosis safe factories (whose job it is to make gnosis safe proxies) already do this, they have a single contract that can exist at the same address in every network. And in fact they even have a “replay” script that they encourage everyone to use to deploy the factory to networks where it doesn’t exist yet which will deploy to the same canonical address. My understanding is that the resulting address for create2 is that it is a hash of the byte code for the contract and user provided salt. So if you use create2 to also deploy the factory as well, does it really matter who deploys the factory if the same salt is provided? Could we provide a “replay” like gnosis does for their factories to let anyone deploy the factory to the canonical address for any network? Here is the gnosis replay https://github.com/gnosis/safe-contract-deployment-replay and here is some more documentation around this https://docs.gnosis.io/safe/docs/contracts_other_evm/

Amxx commented 3 years ago

@habdelra, an EOA (wallet with a private key) cannot use create2. create2 power can only be used by a smart contract. This is why you need a factory.

You first need to use a script similar to what gnosis does to create the factory at the right address ... and then (and only then) you can use create2 to instanciate "child-contracts".

If you want a generic factory, that is available on most networks, and can be used to deploy anything (simple contract or complex factories) you can use this one.

All the details necessary to deploy it on any new chain, including hardhat/ganache chains, can be found here

Last shameless plug: my devcon talk that discuss the need for such factory

tab00 commented 2 years ago

If you want a generic factory, that is available on most networks, and can be used to deploy anything (simple contract or complex factories) you can use this one.

There is also Axelar's Constant Address Deployer that can be used to deploy a new smart contract using CREATE2. But how can we use it to deploy an upgradeable contract?

ethermachine commented 2 years ago

If you want a generic factory, that is available on most networks, and can be used to deploy anything (simple contract or complex factories) you can use this one.

I am checking how to deploy using create2 on a selfdestructed contract but I see that unless I send the exact same bytecode it deploys to a new address. It's confusing because I read that only the salt and the factory address are used to generate it. Can you please clarify? thanks

waynehoover commented 1 year ago

The ability to deploy proxies via create2 is the last remaining thing keeping me from using the upgrades plugin to deploy, is this feature planned?

frangio commented 1 year ago

Not planned at the moment.

When we implement this we will need to use a standard create2 factory that is deployed on multiple chains. If someone has a favorite factory please share the link here.

tab00 commented 1 year ago

Have you checked Axelar as mentioned above? They specialize in cross-chain. See: https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/tree/main/contracts/deploy https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/main/scripts/create3Deployer.js

This could also be worth having a look into: https://github.com/ZeframLou/create3-factory

It describes the difference between CREATE2 and CREATE3:

One could use a CREATE2 factory that deterministically deploys contracts to an address that's unrelated to the deployer's nonce, but the address is still related to the hash of the contract's creation code. This means if you wanted to use different constructor parameters on different chains, the deployed contracts will have different addresses.

A CREATE3 factory offers the best solution: the address of the deployed contract is determined by only the deployer address and the salt. This makes it far easier to deploy contracts to multiple chains at the same addresses.

Oba-One commented 1 year ago

Any updates on this functionality?