Redundant ProxyAdmin Deployments with TransparentUpgradeableProxy and Hardhat Upgrades Plugin?

Hello everyone,

I've been working with OpenZeppelin's TransparentUpgradeableProxy in conjunction with the Hardhat Upgrades plugin and noticed a potential redundancy that I'd like to discuss.

When deploying a transparent upgradeable proxy using the Hardhat Upgrades plugin, the plugin first deploys a ProxyAdmin contract:

const adminAddress = await hre.upgrades.deployProxyAdmin(signer, opts);

Subsequently, the TransparentUpgradeableProxy is deployed, and its constructor, as defined in OpenZeppelin's contracts, also deploys its own ProxyAdmin:

constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
    _admin = address(new ProxyAdmin(initialOwner));
    // ... rest of the code

This results in two ProxyAdmin contracts being deployed: one by the Hardhat Upgrades plugin and another by the TransparentUpgradeableProxy constructor.

My questions are:

  1. Is this redundancy intentional? If so, what's the rationale behind having two separate ProxyAdmin contracts for a single proxy?
  2. If this is not intentional, is there a recommended way to ensure only one ProxyAdmin is deployed and associated with the proxy?
  3. Has anyone else encountered this situation, and how did you address it?

I appreciate any insights or recommendations on this matter. Thank you!

This is new in OpenZeppelin version 5.0.0:

TransparentUpgradeableProxy: Admin is now stored in an immutable variable (set during construction) to avoid unnecessary storage reads on every proxy call. This removed the ability to ever change the admin. Transfer of the upgrade capability is exclusively handled through the ownership of the ProxyAdmin. (#4354)

Make sure that you're using a HardHat version which is compatible with this OpenZeppelin version.

@max The Hardhat Upgrades plugin currently deploys this version of the TransparentUpgradeableProxy from OpenZeppelin Contracts v4. This version takes the proxy admin as a constructor parameter, so there is no redundancy.

In OpenZeppelin Contracts v5, TransparentUpgradeableProxy indeed deploys its own proxy admin. This version is not deployable by the Hardhat Upgrades plugin at the moment, but we plan to add support for this in this issue.

If you want to use v5 proxy contracts for now, you would need to deploy them without the plugin, then import them using upgrades.forceImport.

@ericglau I understand, thank you!

I intended to use the Upgrades Plugin for deploying/upgrading to benefit from its features, such as the upgrade-safety checks. Now, I need to decide:

  • Deploy/upgrade manually
  • Stick with the contract version supported by the hardhat-plugin.

Thank you regardless!

    "@openzeppelin/contracts": "4.8.3",
    "@openzeppelin/contracts-5.0": "npm:@openzeppelin/contracts@^5.0.0-rc.0",
    "@openzeppelin/contracts-upgradeable": "4.8.3",

@barakman Thank you for response. But response of @ericglau is closer to what I was looking for.

The two responses look exactly the same to me - they both tell you that OZ v5 has changed the related API, and that your HH version is probably incompatible with this API change, so you should either upgrade your HH version, or downgrade your OZ version.


Please consider PR #910. Could we bump contracts versions to 4.9.3 (latest release before 5) ?
No changes in JS code required, tests passed.

I commented in the issue that there are no identified changes between 4.8.3 and 4.9.3 that affect proxy functionality.

Even if you deploy/upgrade manually, you can still run validateImplementation or validateUpgrade before deploying or upgrading, respectively.

1 Like