Provisions for upgrading from ERC721Upgradeable to ERC721EnumerableUpgradeable?

Greetings!

Say we deploy an implementation contract that is initially based on ERC721Upgradeable. If we simply upgrade it to a version that uses ERC721EnumerableUpgradeable, the new version will have a different storage layout and will thus break the deployment. (I haven't tried, but I suppose hardhat-upgrades will bark when attempting this.)

Would adding ERC721EnumerableUpgradeable's storage variables prior to the variables of the implementation contract in the initial version be sufficient to support such an upgrade?

If your contract has custom storage variables then such an upgrade would indeed be rejected. To illustrate:

contract NFTv1 is ERC721Upgradeable {
    uint customVar;
}

contract NFTv2 is ERC721EnumerableUpgradeable {
    uint customVar;
}

Note that ERC721Enumerable (dropping the Upgradeable suffix for brevity) inherits ERC721, so if customVar wasn't there the upgrade would be perfectly acceptable. The storage layout of NFTv2 would be an extension of that of NFTv1.

When you introduce customVar, this changes because customVar will be shifted by the variables introduced by ERC721Enumerable.

In this case there is a simple solution, which is to make the v2 extend the v1:

contract NFT_v2 is NFT_v1, ERC721EnumerableUpgradeable {
    ... // new logic
}

The storage layout will be compatible now.

Thank you! Great explanation.