OpenZeppelin Upgrades: Step by Step Tutorial for Hardhat

Hi, welcome! :wave:

I think for the initializer option, it means when you deploy a proxy contract, you want to execute a function that is equal to the constructor in the implementation contract, and for more details, I think you can have a look at the documentation: Writing Upgradeable Contracts - OpenZeppelin Docs

1 Like

@Skyge Thank you for your reply.

I understood that we can use initialize as constructor.

Could you tell me why you are using store in this sample code?
Also, do you know any other option other than store?

I tried finding these answers by reading openzeppelin source code and document, but I could not find the answer by myself.

I think the tutorial just show a way you can call a function when deploy a contract, so it can be any method you want to call, and it is ok if you want to call this function separately later.

In the tutorial, there are only three functions: store, retrieve and increment, two write data functions and one read data function, so if you do not want to call store, maybe you can try to call increment

1 Like

@Skyge Thank you. I really appreciate your answer.
Now I understand store comes from contract's functions, and we can call a contract function by using initialize option with deploy.

1 Like

Hi, amazing tutorial.
I think I have it more or less figured out but I am having an issue with typescript in tests and deployments.
For un-upgradeable contracts I am able to use a type from the typechain, so I have all the functions of the contract available.

On the other hand, when I use the upgrades.deployProxy function the return type is Contract and all methods are lost. Is there a way around this or am I doing something wrong?

Hi, thanks for the tutorial. I followed it step by step, and at the end it doesn't keep the store.
await box2.retrieve() returns 0 instead of '42', however, all the tests pass.
What can be the reason of it? Am I missing an important step here?

The Upgrades plugin doesn't directly support TypeChain yet. Sorry about that. You can probably do manual casting of the return type in the meantime...

You may be using the wrong address when you call the attach method.

I checked it several times, I copy the output of Box.sol deployment, then paste it into prepare_upgrade.js. I can see that the increment() function is there, and it works, however the initial state is 0 when I retrieve first.

As you can see on the screenshot below. If it's the wrong address, then which one I should paste? Also, note, that all tests are passing. Would really appreciate help here!


The address is wrong, you need to attach the proxy address (in your case 0x1E24...). The address shown in the console is the address of the implementation contract, which you should not interact directly with.

1 Like

What if instead of a multisig you want a community of token holders to approve an upgrade? How could we implement that without bypassing all the safety that the upgrades plugin provide?

The Upgrades plugin runs off chain and there is no way to enforce it on chain. Even with a multisig setup you're not using the plugin itself to upgrade. In the governance proposal for the upgrade you should include a link where people can verify the code of the new contract implementation, and where they could also verify upgade safety by themselves through running a test suite that uses the Upgrades plugin.

1 Like


I completed all upgrades contract follow this Topic, thanks!
I transferred ownership to Gnosis Safe.

deployed my ERC1155 Contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.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 MyToken is Initializable, ERC1155Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    function initialize() initializer public {

    function setURI(string memory newuri) public onlyOwner {

    function mint(address account, uint256 id, uint256 amount, bytes memory data)
        _mint(account, id, amount, data);

    function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
        _mintBatch(to, ids, amounts, data);

    function _authorizeUpgrade(address newImplementation)

now, I want to call setURI method, I found it is impossible to connect contract, [OpenZeppelin] app( Gnosis Safe's apps) only support upgrades.

Does anyone know how to connect my Gnosis Safe wallet to call setURI method?

To call functions on your contract you go through the normal Gnosis Safe process, not our OpenZeppelin app. Look for the "Transaction Builder" app in "Apps".

Hi abcoathup
Can you tell me about it
I want to ask that we've deployed an up-gradable contract yesterday on polygon by hardhat plugin and it was deployed in one single transaction and now today we're deploying the same up-gradable contract now from same hardhat it's deploying in 3 transactions can you tell me why it's taking 2 extra transactions?

contract address deployed in one single transaction:0x6a4581fffdeaC8246B2DC3662F1035B6F5E8e618

Please do not ask the same questions everywhere, one time is enough. Thanks!

Great tutorial! However - in my case I deploy multiple contracts on multiple networks and thus I have multiple deploy scripts in different files. Before migrate all my contracts to upgradeable contracts I was using hardhat-deploy plugin and the way I was getting the addresses of the already deployed contracts was:
const contractAddress = await deployments.get("MyContract");
Is there a similar functionality on hardhat-upgrades plugin?

Other way around if to use hardhat-deploy for deploying upgradeable contracts, but the control over the deploy is not so good as well as I am getting some error that shows the hardhat-deploy adds for instance UUPSUpgradeable to my contract (I already have it and there is a collision).

We currently don't have a feature like deployments.get and our recommendation is to write this data to a JSON file or similar in your deployment scripts.

Can you elaborate and the error you see with hardhat-deploy?

5 posts were split to a new topic: How do function interactions work with a proxy

I've done this:

import { Box, Box__factory } from '../typechain-types';

let Box: Box__factory;
let box: Box;

// Start test block
describe('Box (proxy)', function () {
    beforeEach(async function () {
        Box = await ethers.getContractFactory("Box");
        box = await upgrades.deployProxy(Box, [42], { initializer: 'store' }) **as** Box;