Is it possible to deploy an UUPS Proxy in conjunction with CREATE2 so I can precompute the address it will be deployed to?

I would like to know the precomputed address my UUPS proxy will be deployed to.

:1234: Code to reproduce

I came up with this code to test it but it doesn't let me call the computeAddress function (low delegatecall failed...).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

import "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract OrganizerTemplate is Initializable {
    function initialize(bytes memory _foo,
            bytes32 _salt) public payable initializer {
  }
}

contract Factory is Initializable, OrganizerTemplate {
    address public template;
    function initialize(address organizerTemplate) public payable initializer {
        template = organizerTemplate;
  }
  function getBytes(string memory value) public view returns(bytes memory){
      return bytes(value);
  }

    function deployReal(address _owner,
        uint256 _foo,
        bytes32 _salt
 ) public {
    address organizerClone = ClonesUpgradeable.clone(template);
    ERC1967Proxy organizerProxy = new ERC1967Proxy{salt: _salt}(
      organizerClone,
      abi.encodeWithSelector(
        OrganizerTemplate(address(0)).initialize.selector,
        _owner,
        _foo
      )
    );
    }
    
    // Returns the address of the newly deployed contract
    function computeAddress(
        address _owner,
        bytes memory _foo, // USED TO BE UINT
        bytes32 _salt
    ) public payable returns (address) {
        // This syntax is a newer way to invoke create2 without assembly, you just need to pass salt
        // https://docs.soliditylang.org/en/latest/control-structures.html#salted-contract-creations-create2
        return address(new ERC1967Proxy{salt: _salt}(_owner, _foo));
    }
}

:computer: Environment

Hardhat

Im now trying with the following code I adjusted from this solidity docs example (0.8.17):

function createDSalted(bytes32 salt, uint arg) public payable returns (address, address) {
        // This complicated expression just tells you how the address
        // can be pre-computed. It is just there for illustration.
        // You actually only need ``new D{salt: salt}(arg)``.
        address organizerClone = ClonesUpgradeable.cloneDeterministic(template, salt); 
        address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(
                type(ERC1967Proxy).creationCode,
                abi.encode(arg)
            ))
        )))));

        ERC1967Proxy proxy = new ERC1967Proxy{salt: salt, value: msg.value}(
            organizerClone,
            abi.encodeWithSelector(
                OrganizerTemplate(address(0)).initialize.selector,
                arg
            )
            );
        return (address(proxy),predictedAddress); // THESE TWO SHOULD BE THE SAME
    }

However, Im getting two different addresses :frowning:. Im trying to use CloneDeterministic from ClonesUpgradeable.sol but still the addresses differ...

Here is the solution. I was not putting the right args in the predictedAddress field:

function createDSalted(bytes32 salt, uint arg) public payable returns (address, address) {
        // This complicated expression just tells you how the address
        // can be pre-computed. It is just there for illustration.
        // You actually only need ``new D{salt: salt}(arg)``.
        address organizerClone = ClonesUpgradeable.cloneDeterministic(template, salt);
        address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(
                type(ERC1967Proxy).creationCode,
                abi.encode(organizerClone,
            abi.encodeWithSelector(
                OrganizerTemplate(address(0)).initialize.selector,
                arg
            ))
            ))
        )))));

        ERC1967Proxy proxy = new ERC1967Proxy{
            salt: salt, 
            value: msg.value
            }(
            organizerClone,
            abi.encodeWithSelector(
                OrganizerTemplate(address(0)).initialize.selector,
                arg
            )
            );
        return (address(proxy),predictedAddress);
        // require(address(proxy) == predictedAddress);
    }
}

However, if the only thing you want is the pre-computed address for off-chain operations or namespace handling you can simply use Create2 contract using the hash I defined in my example:

address preComputedwithCreate2 = Create2Upgradeable.computeAddress(salt, keccak256(abi.encodePacked(
                type(ERC1967Proxy).creationCode,
                abi.encode(organizerTemplate,
                abi.encodeWithSelector(
                    Organizer(address(0)).initialize.selector,
                    organizationName,
                    LomEventV3Template, 
                    MarketPlaceV1, 
                    MinterTemplate
                ))
            ))); 
2 Likes

My repository covers deploying a UUPS upgradeable contract via CREATE3: https://github.com/SKYBITDev3/SKYBIT-Keyless-Deployment

The deployment script in particular that you'd want is: https://github.com/SKYBITDev3/SKYBIT-Keyless-Deployment/blob/main/scripts/deployViaCREATE3-TESTERC20UG.js

CREATE3 is preferred over CREATE2 as the creation code no longer affects the address, making it easier e.g. if you want to deploy a contract to the same address on multiple (including future) blockchains.