Need help with Tutorial on upgradeProxy

Hi, I am trying to upgrade my contract as per the OpenZeppelin tutorial ( The new contract initializes with an _admin variable, which the previous contract does not:

    function initialize(address admin) public initializer {
        _admin = admin;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

I want to upgrade to the new Box contract with _admin:

const BoxV2 = await ethers.getContractFactory("BoxV2");
      await upgrades.upgradeProxy(box3Contract.address, BoxV2, {
        constructorArgs: [owner.address],

So, I am trying to pass owner.address in the constructorArgs for initialization, but I keep getting the following error: Error: types/values length mismatch (count={"types":0,"values":1}, value={"types":[],"values":["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]}, code=INVALID_ARGUMENT, version=abi/5.7.0) Anyone familiar with upgradeProxy ?

You are passing it as contracutor arguments but you need it in initialize() function

Sorry, how do you mean call?: string | { fn: string; args?: unknown[] }, under opts in upgradeProxy?

I have tried:

      await upgrades.upgradeProxy(box3Contract.address, Box3V2, {
        call: { fn: "initialize", args: [owner.address] },

However, it now results in the following error:
Error: types/values length mismatch (count={"types":1,"values":0}, value={"types":[{"name":"admin","type":"address","indexed":null,"components":null,"arrayLength":null,"arrayChildren":null,"baseType":"address","_isParamType":true}],"values":[]}, code=INVALID_ARGUMENT, version=abi/5.7.0)

Ok, the code above works.

Just need to remove the constructor function:

    // constructor(address admin) initializer {
    //     _admin = admin;
    // }

Now, I am unsure as to why the constructor is in the code in the docs :grimacing:

Hi @Damien_Teo,

I'd like to clarify the concepts here on constructors vs. initializers:

  • Generally an upgradeable implementation should not have a constructor. You should use an initializer to initialize your contract's logic.
  • For deployProxy, args and initializer are for the initializer's arguments and name, respectively.
  • For upgradeProxy, if you need to call a function during the upgrade process, that is done with the call?: string | { fn: string; args?: unknown[] }, option as you mentioned. This could be a function marked as reinitializer so that it can only be called once.
  • A constructor with the below content is still recommended for disabling initialization of the implementation contract itself (at the implementation's address). The reasoning is described in this post.
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
1 Like

Thanks Eric,

Very good explanation.

Think another issue was that I removed:

/// @custom:oz-upgrades-unsafe-allow constructor

This was causing an error stating that the contract was not update-safe