Below are two mock contracts to reproduce the situation that I'm facing:
-
MyContractV1
is deployed on the local chain -
MyContractV2
is upgraded using the OpenZeppelin Upgrades hardhat plugin
Observed: when checking for the.owner()
forMyContractV2
, the address0x0
is returned instead of the owner of theMyContractV1
.
Expected: the owner address should be the same as forMyContractV1
.
Now, this is very weird, since everything works with the tests, and all of them pass. But the actual upgrade, then, fails, and I can't call any function due to ownership issues (in the real contract).
MyContractV1.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./access/OwnableAccessControlUpgradeable.sol";
import "./proxy/UUPSUpgradeable.sol";
contract MyContractV1 is OwnableAccessControlUpgradeable, UUPSUpgradeable {
string public name;
function setName(string memory _newName) public {
name = _newName;
}
function initialize() public initializer {
__OwnableAccessControl_init();
__UUPSUpgradeable_init();
}
}
MyContractV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./MyContractV1.sol";
contract MyContractV2 is MyContractV1 {
string public version;
function setVersion(string memory _newVersion) public {
version = _newVersion;
}
}
OwnableAccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./OwnableUpgradeable.sol";
import "./AccessControlUpgradeable.sol";
contract OwnableAccessControlUpgradeable is OwnableUpgradeable, AccessControlUpgradeable {
bytes32 public constant AUTHORIZED_OPERATOR =
keccak256("AUTHORIZED_OPERATOR");
function __OwnableAccessControl_init() internal initializer {
__Ownable_init();
__AccessControl_init();
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
}
V1 Deployment Tests
const { ethers } = require("hardhat");
const { Contract } = require("ethers");
const { expect } = require("chai");
// Start test block
describe("V1 test", function () {
let myContractV1;
let MyContractV1;
let deployer;
beforeEach(async function () {
accounts = await ethers.getSigners();
deployer = accounts[0];
console.log(deployer.address, "deployer addr");
MyContractV1 = await ethers.getContractFactory("MyContractV1");
myContractV1 = await upgrades.deployProxy(MyContractV1, {
kind: "uups",
});
console.log("MyContractV1 address:", myContractV1.address);
await myContractV1.setName("My Contract V1");
});
context("V1 test", async () => {
it("deployer is the contract owner", async () => {
const owner = await myContractV1.owner();
expect(owner.toString()).to.equal(deployer.address.toString());
});
it("checks the name", async () => {
const name = await myContractV1.getName();
expect(name, toString()).to.equal("My Contract V1");
});
});
});
V2 Upgrade Tests
const { ethers, upgrades } = require("hardhat");
const { Contract } = require("ethers");
const { expect } = require("chai");
// Start test block
describe("V2 upgrade tests", function () {
let myContractV1;
let MyContractV1;
let MyContractV2;
let myContractV2;
let deployer;
beforeEach(async function () {
accounts = await ethers.getSigners();
deployer = accounts[0];
MyContractV1 = await ethers.getContractFactory("MyContractV1");
myContractV1 = await upgrades.deployProxy(MyContractV1, {
kind: "uups",
});
MyContractV2 = await ethers.getContractFactory("MyContractV2");
myContractV2 = await upgrades.upgradeProxy(myContractV1, MyContractV2);
console.log(myContractV2.address, "v2 address");
await myContractV2.setVersion("2");
});
context("V2 test", async () => {
it("deployer is the contract owner", async () => {
const owner = await myContractV2.owner();
expect(owner.toString()).to.equal(deployer.address.toString());
});
it("expects the version of 2", async () => {
const version = await myContractV2.version();
expect(version.toString()).to.equal("2");
});
});
});
./scripts/deploy.js
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
const MyContractV1 = await ethers.getContractFactory("MyContractV1");
const myContractV1 = await upgrades.deployProxy(MyContractV1, {
kind: "uups",
});
console.log("MyContractV1 address:", myContractV1.address);
await myContractV1.setName("My Contract V1");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
./scripts/prepare_upgrade.js
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
const myContractV1Address = "0x64296acc5af4B9DAF59C04412b987DEe9EA167E0";
const MyContractV2 = await ethers.getContractFactory("MyContractV2");
console.log("Deploying contracts with the account:", deployer.address);
console.log("Preparing V2 upgrade...");
const myContractV2Address = await upgrades.prepareUpgrade(
myContractV1Address,
MyContractV2
);
console.log("V2 address: ", myContractV2Address);
const myContractV2 = await MyContractV2.attach(myContractV2Address);
await myContractV2.setVersion("2");
// Checking for the same owner
const owner = await myContractV2.owner(); // this logs 0x0
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Environment
- Using Hardhat, 6.4.15
- EthersJS, 6.4.15