Upgradable contracts initializers how many is to many? ... 2?

Hi, have been looking at some examples for OZ upgradeable smart contracts and I have been having problems with the initializer of said contracts.

TLDNR; What I understood from the documentation is that you can only run an initializer ONCE per implementation Contract.
But from what I am experiencing, it seems you really can only run ONE initializer per Proxy. Can somebody confirm what is the case?

Perhaps the documentation isn’t clear enough about initializers (or my implementation is wrong). To me, they seem like a hook to reconfigure your smart contract when you upgrade.

So while nothing stops you for adding an initializer to implementation B - Z, if you execute implementation. initialize B-Z can never be executed?

At this point I am not certain isn’t a problem with my implementation or my understanding, but it seems to me that you can only run ONE an initializer per Proxy smartcontract. see example below.

Is my assertion that only one initializer can be run per Proxy correct? And if not what is there a correct way to do it? Is this documented someplace else?
All I can find are guides that don’t address this specific scenario ( most of don’t use initializers) and really haven’t stumble upon something like the documentation of the function. I guess the next step is to read the code and (or keep guessing which I hate).

Example

const proxyA =  TestHelper().createProxy(
   Contracts.fromLocal('implementationA'), 
   {
     contractName: "implementationA", initMethod: "initialize", initArgs: [a, b,c .. ]
   }
 
);
await proxyA.methods.initilialize( a, b,c ) # This workd
const proxyB =  TestHelper().updateProxy( 
  proxyA.address ,
  Contracts.fromLocal('implementationB'),
  { contractName: 'implementationB'}
)

 // show available implementations
proxyB.updateProxy().implementations // shows {implementationA},{immplementationB}
await proxyA.methods.initilialize( a, b,c ) // This doesn't work

// ^ Error: Returned error: VM Exception while processing transaction: revert Contract instance has already been initialized

2 Likes

Hi @ccolorado,

We can only run one initializer per proxy.

We should only initialize a Proxy contract once (see Initialization) when the contract is created.

The Proxy holds the state (see How Upgrades Work) and with upgradeable contracts we interact with the Proxy.

OpenZeppelin provides Initializable to assist with initialization, to ensure it is only called once.

Whilst Initializable exists for the creation of a contract, what I don’t think we currently have documented is what to do in upgrades, as we can’t call Initializable again on the Proxy.

The example below shows a version 0 and a version 1 of a logic contract. When upgrading to use version 1, we need to initialize any state, but can’t use Initializable as the Proxy has already been initialized when it was created. I have added an upgrade function to initialize any upgraded state. We need to ensure that we guard against the upgrade function from being called multiple times. In this example I use a boolean flag.

Let me know if you need more information, and how we could document this better to make it easier to understand.

MyContract (v0)

pragma solidity ^0.5.0;

contract MyContract is Initializable {
    uint256 private _value;

    function initialize(uint256 value) public initializer {
        _value = value;
    }

    function getValue() public view returns (uint256) {
        return _value;
    }
}

MyContract (v1)

pragma solidity ^0.5.0;

import "@openzeppelin/upgrades/contracts/Initializable.sol";

contract MyContract is Initializable {
    uint256 private _value;
    bool private _upgraded;

    function initialize(uint256 value) public initializer {
        _value = value;
    }

    function upgrade(uint256 value) public {
        require(!_upgraded, "MyContract: already upgraded");
        _upgraded = true;
        _value = value;
    }

    function getValue() public view returns (uint256) {
        return _value;
    }
}
2 Likes

Thanks for this answer it is very complete.
I think the upgrade() mechanism to be probably the way to go.

1 Like

Hi @ccolorado,

I would also suggest that any initialization function in upgraded contracts is appropriately tested and audited, including ensuring that it can only be called once.

The sample above has only had basic manual testing.

Feel free to ask all the questions that you need. It also helps build the knowledge base for the entire community.

1 Like