deployProxy() method question

I have read almost all the docs available about proxying, but I still don't understand one thing, in the deployProxy method, where does the factory of the proxy contract go? Because we have 2 contracts (Proxy + Implementation). But the method only asks for one: the implementation. So where is the Proxy contract definition go? You can't deploy without it because the Proxy will hold the state variables.

async function deployProxy(
  Contract: ethers.ContractFactory,
  args: unknown[] = [],
  opts?: {
    initializer?: string | false,
    unsafeAllow?: ValidationError[],
    constructorArgs?: unknown[],
    initialOwner?: string,
    timeout?: number,
    pollingInterval?: number,
    redeployImplementation?: 'always' | 'never' | 'onchange',
    txOverrides?: ethers.Overrides,
    kind?: 'uups' | 'transparent',
    useDefenderDeploy?: boolean,
  },
): Promise<ethers.Contract>

As you can see above, it only asks for 1 factory contract (the Implementation), but it should be asking for both, otherwise, how does it know which contract is going to delegate-call into implementation?

Contract: ethers.ContractFactory, <--- this is the implementation, where do I put the factory for the Proxy ?

Naturally , I would expect deployProxy() method to be defined like this:

async function deployProxy(
  ProxyContract: ethers.ContractFactory,
  ImplementationContract: ethers.ContractFactory,
  args: unknown[] = [],
  opts?: {
    initializer?: string | false,
    unsafeAllow?: ValidationError[],
    constructorArgs?: unknown[],
    initialOwner?: string,
    timeout?: number,
    pollingInterval?: number,
    redeployImplementation?: 'always' | 'never' | 'onchange',
    txOverrides?: ethers.Overrides,
    kind?: 'uups' | 'transparent',
    useDefenderDeploy?: boolean,
  },
): Promise<ethers.Contract>

This would make a better sense, wouldn't it??

I am implementing the UUPS proxy type, already have my contracts, but don't understand how to deploy them with only one factory object. Can somebody enlighten me?

The proxy contract is a Generic contract, i.e it is same for everyone unless you are trying to add additional logic to it, which makes no sense because we are supposed to follow a standard.

When we use deployProxy, it deploys a standard proxy contract by itself, and sets the implementation contract (The one we provide).

Regarding the storage layout, it is not required to tell the proxy what the storage looks like, because delegatecall just directly updates the relevant storage slot.

For example we have a variable uint256 counter; on slot 6 in our implementation, and we update it via proxy, then the proxy doesn't need to know what the variable name is etc. , it will just update slot 6.

2 Likes

To add to the above, the Hardhat Upgrades plugin deploys proxies using precompiled bytecode, so the proxy's ContractFactory is not needed when calling the deployProxy function.

1 Like

To add to the above, the Hardhat Upgrades plugin deploys proxies using precompiled bytecode

Thank you very much!
Now I have more questions, what happens if the precompiled code doesn't fit the EVM of the chain? For example Arbitrum network for long time didn't support PUSH0 instruction, if I would deploy precompiled bytecode it could possibly fail.

And another questions arise:

  • Which is the source code file that is used for Proxy contract ?
  • What if I want to extend current Proxy contract with another functionality and deploy it, could it be done easily or I would have to write my own deploProxy() method? For example, I didn't find updateTo() method in the sources, there is only updatetoAndCAll() , so if I would extend current proxy with updateTo() , I wouldn't be able to do this because there is no way to specify Proxy contract for deployProxy() method.

We compile the proxy contracts using paris EVM version, so it avoids PUSH0. The settings are defined here.

  • Which is the source code file that is used for Proxy contract ?

The proxy contracts for the precompile are imported in https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/master/packages/core/contracts/import.sol which come from the OpenZeppelin Contracts v5 dependency -- the source code is https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v5.0.2/contracts/proxy for the current version.

  • What if I want to extend current Proxy contract with another functionality and deploy it, could it be done easily or I would have to write my own deploProxy() method? For example, I didn't find updateTo() method in the sources, there is only updatetoAndCAll() , so if I would extend current proxy with updateTo() , I wouldn't be able to do this because there is no way to specify Proxy contract for deployProxy() method.

Generally we don't suggest writing your own proxy contract unless you have a strong reason to do so. The existing proxy contracts should be generic enough to support most common use cases, and have been included in audits.

For OpenZeppelin Contracts v5, as documented for UUPSUpgradeable, upgradeToAndCall(address,bytes) allows upgrading without calling another function if the second parameter is the empty byte string.

1 Like