Issue with UUPS Upgradeability: initializeV2 Function Not Behaving as Expected

I'm using the OpenZeppelin UUPS upgradeability pattern and working on upgrading my contract from version 1 to version 2. In the new implementation (version 2), I’ve added an initializeV2 function that uses the reinitializer(2) modifier. The goal is for this function to mint some tokens to the owner's address upon upgrade.

Problem

I'm testing this upgradeability using the Hardhat-Upgrades plugin along with the Chai framework. However, when I call the upgradeProxy method and then try to execute the initializeV2 function, the tokens are not being minted as expected. The upgrade seems to happen, but the initializeV2 function doesn't produce the intended results.

:1234: Code to reproduce

contract ERC20UpgradeableV1 is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable{
    constructor(){
        _disableInitializers();
    }

    function initialize(address initialOwner) initializer public{
        __ERC20_init("TestToken", "Test");
        __Ownable_init(initialOwner);
        __UUPSUpgradeable_init();
    }

    function mint(address to, uint256 amount) onlyOwner public {
        _mint(to, amount);
    }

    function _authorizeUpgrade(address newImplementation)
    internal 
    onlyOwner
    override 
    {}
}

contract ERC20UpgradeableV2 is ERC20UpgradeableV1 {
    constructor() {
        _disableInitializers();
    }

    function initializeV2() reinitializer(2) public {
        mint(msg.sender, 1000000 * 10 ** decimals());
    }

}

Could someone help me understand why the initializeV2 function isn't behaving as expected during the upgrade? Any insights into what might be going wrong or how to properly handle this scenario would be greatly appreciated.

The following is my test file:

describe("Upgrade ERC20UpgradeableV1 to ERC20UpgradeableV2", function () {
async function setUpV2() {
    const signers = await ethers.getSigners();
    const owner = signers[0];
    const signer2 = signers[1];

    // Deploy the V1 contract
    const ERC20UpgradeableV1 = await ethers.getContractFactory('ERC20UpgradeableV1');
    let erc20Upgradeable = await upgrades.deployProxy(
        ERC20UpgradeableV1, 
        [owner.address], 
        { initializer: 'initialize', kind: 'uups', unsafeAllow: "constructor" }
    );
    await erc20Upgradeable.waitForDeployment();

    // Upgrade to V2 contract
    const ERC20UpgradeableV2 = await ethers.getContractFactory('ERC20UpgradeableV2');
    erc20Upgradeable = await upgrades.upgradeProxy(
        erc20Upgradeable.target, 
        ERC20UpgradeableV2, 
        { initializer: 'initializeV2', kind: 'uups', unsafeAllow: "constructor" }
    );
    await erc20Upgradeable.waitForDeployment();

    return { erc20Upgradeable, owner, signer2 };
}
    it('should correctly mint the required number of tokens in the owner address', async function () {
      const {erc20UpgradeableV2} = await loadFixture(setUpV2);
      const totalSupply = await erc20UpgradeableV2.totalSupply();
      expect(totalSupply).to.equal(ethers.parseEther('1000000'));
    })
});

:computer: Environment

  • Hardhat - v2.22.5
  • Hardhat-Upgrades Plugin - v3.2.0
  • Chai - v4.2.0
  • OpenZeppelin Upgradeable-Contracts - v5.0.2

For upgradeProxy, use the call option instead of the initializer option. See https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#upgrade-proxy. For example: call: 'initializeV2'