Hi experts,
I've playing with upgrade suite. Met some unexpected behavior.
First let me reproduce my actions
1. Deploy v1
Contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is
Initializable,
ERC20Upgradeable,
AccessControlUpgradeable,
UUPSUpgradeable
{
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}
function initialize() public initializer {
__ERC20_init("MyToken", "MTK");
__AccessControl_init();
__UUPSUpgradeable_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(UPGRADER_ROLE, msg.sender);
}
function _authorizeUpgrade(address newImplementation)
internal
override
onlyRole(UPGRADER_ROLE)
{}
}
JS:
async function deploy() {
const HCETokenV1 = await ethers.getContractFactory("MyToken");
const hce = await upgrades.deployProxy(HCETokenV1, []);
await hce.deployed();
console.log("Box deployed to:", hce.address);
}
Call:
v1.symbol() // ==> MTK
2. Upgrade to v2
Contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyTokenV2 is
Initializable,
ERC20Upgradeable,
AccessControlUpgradeable,
UUPSUpgradeable
{
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}
function initialize() public initializer {
__ERC20_init("MyToken V2", "MTK V2"); // Update the name & symbol
__AccessControl_init();
__UUPSUpgradeable_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(UPGRADER_ROLE, msg.sender);
}
// new function
function hello() public pure returns (string memory) {
return "hello";
}
function _authorizeUpgrade(address newImplementation)
internal
override
onlyRole(UPGRADER_ROLE)
{}
}
JS:
async function upgrade() {
const ADDR = "0x0165878A594ca255338adfa4d48449f69242Eb8F";
const HCETokenV2Test = await ethers.getContractFactory("MyTokenV2");
const upgraded = await upgrades.upgradeProxy(ADDR, HCETokenV2Test);
console.log("Upgrade finished:");
}
Call:
async function testD() {
const ADDR = "0x0165878A594ca255338adfa4d48449f69242Eb8F";
const HCETokenV2Test = await ethers.getContractFactory("MyTokenV2");
const upgraded = HCETokenV2Test.attach(ADDR);
console.log(await upgraded.symbol()); // ==> Still `MTK`, not `MTK V2`
console.log(await upgraded.hello()); // ==> works
}
Question
-
hello()
working should mean the underlying impl is upgraded successfully. right? - But how
symbol()
still return v1's setup? shouldn't all call delegated to v2?
Thanks!