upgradeProxy - trying to add ERC2771-support to an existing ERC721 contract

Hi all,

I tried adding ERC2771-Support to an existing contract.

constructor(address forwarder)
        ERC2771ContextUpgradeable(forwarder)
    {}

Unfortunately, when I try to run the hardhat upgradeProxy() function, I receive the following error:

StorageUpgradeErrors: New storage layout is incompatible

@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211: Upgraded `__gap` to an incompatible type
  - Bad storage gap resize from 49 to 50
    Size cannot increase

Generally, my code works (I have used it in several deployments for months now).
It's just the upgrade of this particular contract that is not working.

Any chance that I can perform this upgrade or is this an edge case where upgrading is generally not possible.

:computer: Environment

I use OpenZeppelin v4.7.0

Please share the rest of that contract, or by the least - the global (storage) variables in it, since the error-message clearly points to the contract's storage layout as the source of the problem.

Please share your current implementation contract and the new one that you want to upgrade to.

Adding a new inherited contract (ERC2771Context in your case) can be tricky, we need to see more of the code to understand if it can be done.

I see. Here is the relevant code:

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

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/metatx/ERC2771ContextUpgradeable.sol";

contract XYZ is
    Initializable,
    ERC721Upgradeable,
    ERC721URIStorageUpgradeable,
    ERC721BurnableUpgradeable,
    AccessControlUpgradeable,
    PausableUpgradeable,
    UUPSUpgradeable,
    ERC2771ContextUpgradeable
{
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
    address registryAddress;
    address collectionAddress;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor(address forwarder)
        ERC2771ContextUpgradeable(forwarder)
    {}

    function initialize() public initializer {
        __ERC721_init("MyName", "MYSYMBOL");
        __ERC721URIStorage_init();
        __Pausable_init();
        __AccessControl_init();
        __ERC721Burnable_init();
        __UUPSUpgradeable_init();

        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(PAUSER_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
        _grantRole(UPGRADER_ROLE, msg.sender);

        registryAddress = address(0x...);
        collectionAddress = address(0x...);
    }

the key part that has changed is adding ERC2771ContextUpgradeable + the constructor:

    constructor(address forwarder)
        ERC2771ContextUpgradeable(forwarder)
    {}

Right so the problem is that currently in your storage layout you have the registryAddress and collectionAddress variables, and by inserting ERC2771ContextUpgradeable those variables are getting shifted in the layout.

The way that you can solve this is by putting the storage in its own contract and arranging the contract like this:

contract XYZStorageV1 {
    address registryAddress;
    address collectionAddress;
}

contract XYZ is
    Initializable,
    ERC721Upgradeable,
    ERC721URIStorageUpgradeable,
    ERC721BurnableUpgradeable,
    AccessControlUpgradeable,
    PausableUpgradeable,
    UUPSUpgradeable,
    XYZStorageV1,
    ERC2771ContextUpgradeable
{

It worked! Thanks a lot @frangio!

1 Like