When do constructor and initialize funcs get called?

I'm using hardhat to test a UUPS upgradable contract. However since this deploys a "clean" new copy of the contract each time, I wonder if things are different from the production environment where contracts are upgraded.
Specifically question is when and how is the initializer function called?

My contract is based on the wizard UUPS below.
When running with hardhat tests, it seems the ROLES are not initialized, even the DEFAULT_ADMIN_ROLE.

contract RevelToken is
    Initializable,
    ERC721Upgradeable,
    ERC721EnumerableUpgradeable,
    UUPSUpgradeable
{

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

    function initialize() public initializer {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
         // etc for other roles
    }

In this case how does initialize ever get called?
When a first 'instance' of logic or proxy is deployed, it seems initializers will be disabled before ever being called?

My tests are failing because of needed roles - even for the deployer wallet. So Instead I removed the disableInitializers() and explicitly call initializer() after deploy. Now tests pass but it feels like a hack. I have some feeling the above code is so that on upgrading the impl. contract, initializers are not called again to modify the data in the proxy?

Is there a recommended way to test these type of proxy contracts?
Or a map of lifecycle (constructor, initialize) events for both proxy and upgrades?

Thanks!

When you deploy RevelToken contract, the function constructor() will execute to disable initialize contract later, but when you use it as an implementation for a proxy, you can can initialize in the proxy contract to initialize contract.

Maybe you can have a look at the docs, hope it can give you some idea
Upgrades - OpenZeppelin Docs

1 Like

Thanks for the reply.

I also discovered you can pass an initializer to the deploy method, but I have had trouble with that so far with passing the initialize function name.

    const instance = await upgrades.deployProxy(factory, {
      initializer: 'initialize',
    })

errors with:

reverted with reason string 'Initializable: contract is initializing'

  • initializer: set a different initializer function to call (see Specifying Fragments), or specify false to disable initialization

I read that constructor funcs are (or should not?) be called at all? But also that the initialize not being called is an attack vector.

To prevent the implementation contract from being used, you should invoke the _disableInitializers function in the constructor to automatically lock it when it is deployed:

is this only on upgrading a contract? ie in the logic/impl. contract?

On calling this on deploy I get all kinds of issues (see above). So this is leading my hunch that deploy and upgrade need different code somehow for the initialize.

It works for me.

When you use this tool to deploy upgradable contracts, all your contracts address are kept in the file name .openzeppelin, you can delete contract address to re-deploy.

Hi @dcsan, there are important differences between constructors and initializers. They are both used for different purposes. See my posts here and here for more details.

The error 'Initializable: contract is initializing' comes from _disableInitializers. This function should only be called from a constructor. Other initialization logic should go in an initializer. This is also explained in the links above.

1 Like

I think what happened here is that the initializer is not called unless you add a blank parameter to the deploy function but still checking this.

eg this works:
const instance = await upgrades.deployProxy(ContractFactory, [])

this doesn't
const instance = await upgrades.deployProxy(ContractFactory)

(my constructor doesn't take any params)

The plugin will call the initialize function by default in both cases, unless there is no initialize function in the implementation.

If you are still encountering an issue, please provide the information listed in Required information when requesting support for Upgrades Plugins so that we can help look into it further.