Cannot deploy proxy - no support for ERC7201

Hi, I want to test deployment of proxy contract (TransparentUpgradableProxy) with hardhat-upgrades plugin and see how defender takes care of the flow.

I must emphasise that non-proxy contract deployment flow works as expected (defender.deployProxy), problem is when I want to deploy proxy which is described below.

EDIT: It seems defender doesn't like ERC7201 to structure proxy storage, using standard "gap" approach worked as expected. Any solution to this ?

:laptop: Environment
Hardhat: 2.22.17
metamask: 12.16.0
hardhat-upgrades: 3.9.0

:memo:Details
I setup only approval process for my EOA (for account I will sign the approval later in Defender), not upgrade approval process as I am not using it. Also I tested deploying non-proxy contract with similar script and it worked as expected.

Once I execute below script I get correct on terminal message with:

ACTION REQUIRED: Go to https://defender.openzeppelin.com/v2/#/deploy to submit the pending deployment.

The process will continue automatically when the pending deployment is completed.

Waiting for pending deployment of contract TransparentUpgradeableProxy with deployment id ddc725f8-9e8d-4560-a015-fd33a76ffde6...

Problem is when I connect my metamask wallet (for approval) and click "Deploy" in Defender I get big error log:

Sending deploy transaction failed, ensure your EOA is connected and provider operational: cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (reason="execution reverted", method="estimateGas", transaction=....

My metamask is connected and as I explained it worked when deploying non-proxy contract with defender.deployContract.

Any help would be appreciated.

:1234: Code to reproduce

Script:

const TestContract = await ethers.getContractFactory("TestContract");


  const testContract = await defender.deployProxy(TestContract, {
    kind: "transparent",
  });

  await testContract.waitForDeployment();

Proxy contract:

contract TestContract is OwnableUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.TestContract
    struct TestContractStorage {
        string _version;
    }

    // keccak256(abi.encode(uint256(keccak256("fija.storage.TestContract")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant TestContractStorageLocation =
        0xca9ab86016606c0de0fd52d033f885973b1d29b412e722f528ec4c1b4b023800;

    function _getTestContractStorage()
        private
        pure
        returns (TestContractStorage storage $)
    {
        assembly {
            $.slot := TestContractStorageLocation
        }
    }

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize() public initializer {
        __Ownable_init(msg.sender);

        TestContractStorage storage $ = _getTestContractStorage();

        $._version = "V1";
    }

    function version() external view returns (string memory) {
        TestContractStorage storage $ = _getTestContractStorage();
        return $._version;
    }
}

Seems defender doesn't like erc7201 to structure proxy storage. Using "standard" gap approach works as expected. Anyone knows why is that? Any solutions to this?

Thank you.

Yeah, this is a known pain point right now with OpenZeppelin Defender and ERC-7201.
why defender does not like it
might be cause by
1.storage layout diffing
2.manifest tooling
3.standardization lag

1 Like

Is the support in roadmap? and when?

Currently, there is no publicly available roadmap or timeline for when Defender will support ERC-7201. OpenZeppelin has acknowledged the need for enhanced tooling to handle namespaced storage layouts, but specific details regarding implementation or release dates have not been provided.

1 Like

if you’re relying on Defender for contract upgrades, it’s advisable to continue using the traditional storage gap pattern. This approach ensures compatibility with Defender’s existing upgrade safety checks. If you choose to adopt ERC-7201, be prepared to perform manual validations or bypass certain safety checks, keeping in mind the associated risks

1 Like

I was testing upgrades plugin(without defender) with erc7201 and works ok it validates struct storage, there are some annotation you need to do, but works ok.

Yep — you’re absolutely right.

The OpenZeppelin Upgrades Plugin does support ERC-7201 fairly well now — but with some caveats. The key is what you mentioned: it can validate struct-based storage layouts as long as you annotate the storage properly and the plugin can track it.

1 Like

But to follow-up, what I can see now using latest upgrades plugin. Is I get

     Error: Missing OpenZeppelin Defender API key and secret in hardhat config. Add the following to your hardhat.config.js configuration:
defender: {
  "apiKey": "YOUR_API_KEY",
  "apiSecret": "YOUR_API_SECRET"
}

when I want to deploy with upgrades.deployProxy, note I am using upgrades not defender and even I tried with useDefenderDeploy = false. But this happens only when I want to deploy erc7201, if I go with gap approach it works. I am doing this on forked arbitrum hardhat node. Seems that upgrades plugin doesn't like erc7201, but that is something new to me, cause I already tested this feature and it worked (with older version of upgrades plugin) using with erc7201. So I do not know if it is due to new version ?

Are you using discord or tg

Yeah — this is a really good observation, and you’re almost certainly right: the issue you’re seeing is due to recent changes in the @openzeppelin/hardhat-upgrades plugin, where Defender integration is now enabled by default in newer versions — even if you didn’t intend to use it.

And what’s strange (but explainable) is that this only triggers when you’re using ERC-7201-style storage — that isn’t a coincidence.

Using discord, is it possible to join ?

You can join @devkane1

are you able to solve it yet

You can join on TG
Now

@Openzeppelin, you should try to cleanup spam here, its terrible