Failing test case for contract upgrade

I have a test case that tests for the upgrading of a contract, as shown below:

const { expect } = require("chai");

const { ethers, upgrades } = require("hardhat");

describe("Token contract upgrade", function () {
    it('Should upgrade successfully', async () => {

        const MyToken = await ethers.getContractFactory("MyToken");
        const MyTokenTestUpgrade = await ethers.getContractFactory("MyTokenTestUpgrade");

        const instance = await upgrades.deployProxy(MyToken, [], { initializer: 'initialize', kind: 'uups' });
        expect(await instance.totalSupply()).to.equal('500000000000000000000000000');

        const upgraded = await upgrades.upgradeProxy(instance.address, MyTokenTestUpgrade, [], { kind: 'uups' });
        await upgraded.store(42);

        const value = await upgraded.retrieve();
        expect(value.toString()).to.equal('42');

        // just a simple sanity check to make sure the total supply is unchanged
        expect(await instance.totalSupply()).to.equal('500000000000000000000000000');
    });
});

When trying to run it however, the following error is encountered:

1) Token contract upgrade
       Should upgrade successfully:
     Error: New storage layout is incompatible

@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol:40: Renamed `_PERMIT_TYPEHASH_DEPRECATED_SLOT` to `_PERMIT_TYPEHASH_DEPRECATED_SLOT`
      at assertStorageUpgradeSafe (node_modules/@openzeppelin/upgrades-core/src/storage/index.ts:35:11)
      at deployImpl (node_modules/@openzeppelin/hardhat-upgrades/src/utils/deploy-impl.ts:110:31)
      at Proxy.upgradeProxy (node_modules/@openzeppelin/hardhat-upgrades/src/upgrade-proxy.ts:25:32)
      at Context.<anonymous> (test/MyToken-proxy.js:14:26)

Looking into my autogenerated unknown-31337.json file, the line regarding "renamedFrom" looked strange and could be related to the observed error:

          {
            "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT",
            "offset": 0,
            "slot": "354",
            "type": "t_bytes32",
            "contract": "ERC20PermitUpgradeable",
            "src": "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol:40",
            "renamedFrom": "_PERMIT_TYPEHASH"
          },

After examining this section of draft-ERC20PermitUpgradeable.sol:

    // solhint-disable-next-line var-name-mixedcase
    bytes32 private constant _PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    /**
     * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
     * However, to ensure consistency with the upgradeable transpiler, we will continue
     * to reserve a slot.
     * @custom:oz-renamed-from _PERMIT_TYPEHASH
     */
    // solhint-disable-next-line var-name-mixedcase
    bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;

I tried removing the line * @custom:oz-renamed-from _PERMIT_TYPEHASH1 and that seems to resolve the issue. Just wanted to check if I am dealing with this correctly or if I am butchering something else but was unaware.

Wanted to also highlight that I didn't have this issue if it was a transparent proxy, seems to only appear if it was UUPS.

For reference, my MyToken contract is as follows:

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

import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/proxy/utils/UUPSUpgradeable.sol";

contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20SnapshotUpgradeable, OwnableUpgradeable, PausableUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, UUPSUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize() initializer public {
        __ERC20_init("MyToken", "MTK");
        __ERC20Burnable_init();
        __ERC20Snapshot_init();
        __Ownable_init();
        __Pausable_init();
        __ERC20Permit_init("MyToken");
        __ERC20Votes_init();
        __UUPSUpgradeable_init();

        _mint(msg.sender, 500_000_000 * 10**decimals());
    }

    function snapshot() public onlyOwner {
        _snapshot();
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override(ERC20Upgradeable, ERC20SnapshotUpgradeable)
    {
        super._beforeTokenTransfer(from, to, amount);
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        onlyOwner
        override
    {}

    // The following functions are overrides required by Solidity.

    function _afterTokenTransfer(address from, address to, uint256 amount)
        internal
        override(ERC20Upgradeable, ERC20VotesUpgradeable)
    {
        super._afterTokenTransfer(from, to, amount);
    }

    function _mint(address to, uint256 amount)
        internal
        override(ERC20Upgradeable, ERC20VotesUpgradeable)
    {
        super._mint(to, amount);
    }

    function _burn(address account, uint256 amount)
        internal
        override(ERC20Upgradeable, ERC20VotesUpgradeable)
    {
        super._burn(account, amount);
    }
}

and my upgraded contract is as follows(added a new variable and a few more functions):

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

import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable@4.7.3/proxy/utils/UUPSUpgradeable.sol";

contract MyTokenTestUpgrade is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20SnapshotUpgradeable, OwnableUpgradeable, PausableUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, UUPSUpgradeable {

    // *** added for testing ***
    // Note the pitfalls when adding new contract state variables:
    // https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#modifying-your-contracts
    uint256 private value;

    // *** added for testing ***
    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);

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

    function initialize() initializer public {
        __ERC20_init("MyToken", "MTK");
        __ERC20Burnable_init();
        __ERC20Snapshot_init();
        __Ownable_init();
        __Pausable_init();
        __ERC20Permit_init("MyToken");
        __ERC20Votes_init();
        __UUPSUpgradeable_init();

        _mint(msg.sender, 500_000_000 * 10**decimals());
    }

    function snapshot() public onlyOwner {
        _snapshot();
    }

    // *** added for testing ***
    function getCurrentSnapshotId() public view returns (uint256) {
        return _getCurrentSnapshotId();
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override(ERC20Upgradeable, ERC20SnapshotUpgradeable)
    {
        super._beforeTokenTransfer(from, to, amount);
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        onlyOwner
        override
    {}

    // The following functions are overrides required by Solidity.

    function _afterTokenTransfer(address from, address to, uint256 amount)
        internal
        override(ERC20Upgradeable, ERC20VotesUpgradeable)
    {
        super._afterTokenTransfer(from, to, amount);
    }

    function _mint(address to, uint256 amount)
        internal
        override(ERC20Upgradeable, ERC20VotesUpgradeable)
    {
        super._mint(to, amount);
    }

    function _burn(address account, uint256 amount)
        internal
        override(ERC20Upgradeable, ERC20VotesUpgradeable)
    {
        super._burn(account, amount);
    }

    // *** added for testing ***
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }

    // *** added for testing ***
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

:computer: Environment

I was using Hardhat 2.9.2 for this testing.

This looks related to an issue that was fixed in version 1.14.2 of @openzeppelin/upgrades-core.

Can you check if you are using the latest versions of @openzeppelin/upgrades-core and @openzeppelin/hardhat-upgrades?

To update the core package, use npm install @openzeppelin/upgrades-core@latest

Hi Eric,

Thanks for pointing me in the right direction! I was using version 1.17.0 of @openzeppelin/hardhat-upgrades. After upgrading it to 1.20.0 (and @openzeppelin/upgrades-core to 1.19.1, didn't have that installed originally) I don't see that issue anymore.

1 Like