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.
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'));
})
});
Environment
- Hardhat - v2.22.5
- Hardhat-Upgrades Plugin - v3.2.0
- Chai - v4.2.0
- OpenZeppelin Upgradeable-Contracts - v5.0.2