1

There is an existing unanswered question from another author with the same problem, I am creating a more detailed one with the hopes both can be closed.

I have a solidity contract based on the ideas from this post. Basically it is factory contract that allows the deployment of forwarder contracts. To minimize gas costs, after creating a forwarder you can create a clone of it.

I wish to calculate the address that will be generated using create2, which works well with the createForwarder function:

ForwarderFactory.sol

    function createForwarder(address forwardAddress, uint256 salt)
        public
        onlyOwner
        returns (Forwarder forwarder)
    {
        bytes memory bytecode =
            abi.encodePacked(
                type(Forwarder).creationCode,
                uint256(uint160(address(forwardAddress)))
            );

        assembly {
            forwarder := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
        }

        emit Deployed(address(forwarder), salt);
    }

CalculateAddress.js

let bytecode = `${forwarderBytecode}${encodeParam(
  "address",
  forwardAddress
).slice(2)}`;

function buildCreate2Address(creatorAddress, saltHex, byteCode) {
  return `0x${web3.utils
    .sha3(
      `0x${["ff", creatorAddress, saltHex, web3.utils.sha3(byteCode)]
        .map((x) => x.replace(/0x/, ""))
        .join("")}`
    )
    .slice(-40)}`.toLowerCase();
}

That works well. However when generating a new clone address, the previous buildCreate2Address function does not work anymore because the 2nd and 3rd parameter in the create2 function are different.

ForwarderFactory.sol

    function generateCloneAddress(address target, uint256 salt)
        private
        returns (address cloneAddress)
    {
        bytes20 targetBytes = bytes20(target);
        assembly {
            let clone := mload(0x40)
            mstore(
                clone,
                0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
            )
            mstore(add(clone, 0x14), targetBytes)
            mstore(
                add(clone, 0x28),
                0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
            )
            cloneAddress := create2(0, clone, 0x37, salt)
        }
    }

I am not quite sure if its the bytecode that is changing, or if its the buildCreate2Address that needs to be updated.

2 Answers 2

2

It turns out that the clone is created using the standard EIP 1167, so it has a different creation bytecode than the original forwarder. So to calculate the deterministic address of the clone you either modify the bytecode parameter of the buildCreate2Address function, or you can implement the following function in your contract:

    function predictCloneAddress(address forwarderAddress_, uint256 salt_)
        public
        view
        returns (address)
    {
        address predictedAddress =
            Clones.predictDeterministicAddress(
                forwarderAddress_,
                bytes32(salt_)
            );

        return predictedAddress;
    }
2
  • Could you please clarify which modifications we need to apply to bytecode parameter. Your solution works well, but it requires contract call. I want to do the calculations entirely off-chain.
    – Sabine
    Jun 8, 2021 at 12:14
  • The runtime bytecode is 363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3 with the bytes from indices 10 to 29 (inclusive) being the 20-bytes address of your cloned contract. You can the details here: blog.openzeppelin.com/deep-dive-into-the-minimal-proxy-contract
    – Franco
    Jun 13, 2021 at 16:32
-1

I suggest to use @openzeppelin/contracts/proxy/Clones.sol to clone your Contracts. You can predict clone address off-chain with this javascript function:

const { formatBytes32String, getAddress, solidityKeccak256 } = require('ethers').utils

/**
 * Computes the address of a clone deployed using @openzeppelin/contracts/proxy/Clones.sol
 *
 * @param implementation the address of the master contract
 * @param salt integer or string value of salt
 * @param deployer the address of the factory contract
 */
function predictDeterministicAddress(implementation,salt,deployer){
  let assembly = `3d602d80600a3d3981f3363d3d373d3d3d363d73${implementation.toLowerCase().slice(2)}5af43d82803e903d91602b57fd5bf3ff${deployer.toLowerCase().slice(2)}${formatBytes32String(String(salt)).slice(2)}`
  assembly += solidityKeccak256(['bytes'],["0x"+assembly.slice(0, 110)]).slice(2)
  return getAddress(solidityKeccak256(['bytes'],["0x"+assembly.slice(110, 280)]).slice(-40))
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.