How to compute the CREATE2 address for a minimal proxy?

Ed Zynda asked on Telegram

I haven’t been able to find this anywhere but is there a gist showing a reliable way to compute the CREATE2 address of a minimal proxy?

Hi Ed,

I created the following simple Minimal Proxy Factory to compute an address and deploy.

I used @tinchoabbate’s excellent deep dive to get the byte code for a minimal proxy:

I used OpenZeppelin Contracts Create2 to compute the address and to deploy.

:warning: The following code hasn’t been tested nor has it been audited

A more complete version would also include initialization of the minimal proxy
(e.g. https://github.com/OpenZeppelin/openzeppelin-sdk/blob/master/packages/lib/contracts/upgradeability/ProxyFactory.sol#L32)

MinimalProxyFactory.sol

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

import "@openzeppelin/contracts/utils/Create2.sol";

contract MinimalProxyFactory {
    event MinimalProxyCreated(address minimalProxy);

    function computeAddress(uint256 salt, address implementation)
        public
        view
        returns (address)
    {
        return
            Create2.computeAddress(
                keccak256(abi.encodePacked(salt)),
                keccak256(getContractCreationCode(implementation)),
                address(this)
            );
    }

    function deploy(
        uint256 salt,
        address implementation
    ) public {
        address minimalProxy = Create2.deploy(
            0,
            keccak256(abi.encodePacked(salt)),
            getContractCreationCode(implementation)
        );
        emit MinimalProxyCreated(minimalProxy);
    }

    function getContractCreationCode(address logic)
        internal
        pure
        returns (bytes memory)
    {
        bytes10 creation = 0x3d602d80600a3d3981f3;
        bytes10 prefix = 0x363d3d373d3d3d363d73;
        bytes20 targetBytes = bytes20(logic);
        bytes15 suffix = 0x5af43d82803e903d91602b57fd5bf3;
        return abi.encodePacked(creation, prefix, targetBytes, suffix);
    }
}

Box.sol

From: https://docs.openzeppelin.com/learn/developing-smart-contracts#setting-up-a-solidity-project

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;


contract Box {
    uint256 private value;

    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);

    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }

    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

2_deploy.js

// migrations/2_deploy.js
const Box = artifacts.require("Box");
const MinimalProxyFactory = artifacts.require("MinimalProxyFactory");

module.exports = async function (deployer) {
  await deployer.deploy(Box);
  await deployer.deploy(MinimalProxyFactory);
};

Deploy a minimal proxy

Using the factory, I compute an address, then deploy a minimal proxy and interact with it.

$ npx truffle console --network rinkeby
truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> factory = await MinimalProxyFactory.deployed()
truffle(rinkeby)> await factory.computeAddress(42, box.address)
'0xFa4f101C260ebFDdf98f6b3e0A159886D876Fe0B'
truffle(rinkeby)> await factory.deploy(42, box.address)
{ tx:
   '0x301f328227120e90c90bbb3ab8c23672e4cb310e3519842b1ecad29ccbed5036',
...

truffle(rinkeby)> boxProxy = await Box.at('0xFa4f101C260ebFDdf98f6b3e0A159886D876Fe0B')
truffle(rinkeby)> await boxProxy.store(7)
{ tx:
   '0x9db5a64b67281b903114e90a488440ece6a3f3a3485c676884990484d5e5ba61',
...

truffle(rinkeby)> (await boxProxy.retrieve()).toString()
'7'
1 Like

This is brilliant @abcoathup!

I added proxy initialization and a deploy signed method to your code. Check ‘deploySignedAndInitialize’ for details:

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

    import "@openzeppelin/contracts/utils/Create2.sol";
    import "@openzeppelin/contracts/cryptography/ECDSA.sol";

    contract MinimalProxyFactory {event MinimalProxyCreated(address minimalProxy);

    function getDeploymentAddress(uint256 _salt, address _sender, address _implementation) public view returns (address) {
        bytes32 salt = _getSalt(_salt, _sender);
        return
            Create2.computeAddress(
                keccak256(abi.encodePacked(salt)),
                keccak256(getContractCreationCode(_implementation)),
                address(this)
            );
    }

    function deploySignedAndInitialize(uint256 _salt, address _logic, bytes memory _data, bytes memory _signature) public returns (address) {
        address signer = getSigner(_salt, _logic, _data, _signature);
        require(signer != address(0), "Invalid signature");
        address proxyAddress = _deploy(_salt, _logic, signer);
        emit MinimalProxyCreated(proxyAddress);
        (bool success,) = proxyAddress.call(_data);
        require(success);
        return proxyAddress;
    }

    function _deploy(uint256 _salt, address _implementation, address _sender) internal returns (address) {
        bytes32 salt = _getSalt(_salt, _sender);
        address minimalProxy = Create2.deploy(
            0,
            keccak256(abi.encodePacked(salt)),
            getContractCreationCode(_implementation)
        );
        return minimalProxy;
    }

    function _getContractCreationCode(address logic)
        internal
        pure
        returns (bytes memory)
    {
        bytes10 creation = 0x3d602d80600a3d3981f3;
        bytes10 prefix = 0x363d3d373d3d3d363d73;
        bytes20 targetBytes = bytes20(logic);
        bytes15 suffix = 0x5af43d82803e903d91602b57fd5bf3;
        return abi.encodePacked(creation, prefix, targetBytes, suffix);
    }

    function getSigner(uint256 _salt, address _logic, bytes memory _data, bytes memory _signature) public view returns (address) {
        bytes32 msgHash = ECDSA.toEthSignedMessageHash(
          keccak256(
            abi.encodePacked(
              _salt, _logic, _data, address(this)
            )
          )
        );
        return ECDSA.recover(msgHash, _signature);
    }

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

I tested and it works, but not sure if I am missing something.
@abcoathup Do you see anything wrong with the new code?

Thanks!! you guys rock.

1 Like

Hi @Mario,

Welcome to the community :wave:

Thanks for sharing. I would update the internal function names to have an underscore prefix and remove the underscore prefix from any public function names.

If you wanted to use in production as part of a solution then I would suggest comprehensive unit tests and being included in an audit.

1 Like

Beautiful!

Yep, tests need to be added.
Thank @abcoathup!

1 Like