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;
}
}
Environment
I was using Hardhat 2.9.2 for this testing.