I've generated the a contract using the wizard, and I am trying to test it and getting the following error:
Error: Contract `contracts/TokenV1.sol:TokenV1` is not upgrade safe
@openzeppelin/contracts-upgradeable/governance/utils/VotesUpgradeable.sol:261: Variable `op` is an internal function
Use external functions or avoid functions in storage.
If you must use internal functions, skip this check with the `unsafeAllow.internal-function-storage`
flag and ensure you always reassign internal functions in storage during upgrades
https://zpl.in/upgrades/error-009
Code to reproduce
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract TokenV1 is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) initializer public {
__ERC20_init("xxx", "xxx");
__ERC20Burnable_init();
__ERC20Permit_init("xxx");
__ERC20Votes_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
_mint(msg.sender, 2222 * 10 ** decimals());
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
// The following functions are overrides required by Solidity.
function _update(address from, address to, uint256 value)
internal
override(ERC20Upgradeable, ERC20VotesUpgradeable)
{
super._update(from, to, value);
}
function nonces(address owner)
public
view
override(ERC20PermitUpgradeable, NoncesUpgradeable)
returns (uint256)
{
return super.nonces(owner);
}
}
and the test code:
import {
time,
loadFixture,
} from "@nomicfoundation/hardhat-toolbox-viem/network-helpers";
import { expect } from "chai";
import hre from "hardhat";
import { getAddress, parseGwei } from "viem";
describe("TokenV1", function () {
// and reset Hardhat Network to that snapshot in every test.
async function deployFixture() {
const decimals = 18;
const TokenV1 = await hre.ethers.getContractFactory("TokenV1");
// Contracts are deployed using the first signer/account by default
const [owner, otherAccount] = await hre.viem.getWalletClients();
const token = await hre.upgrades.deployProxy(TokenV1, []);
const publicClient = await hre.viem.getPublicClient();
return {
decimals,
token,
owner,
otherAccount,
publicClient,
};
}
describe("Deployment", function () {
it("Should return the right decimals", async function () {
const { token, decimals } = await loadFixture(deployFixture);
expect(await token.decimals()).to.equal(decimals);
});
});
});