Doesn't deployProxy() initialize the implementation contract?

I want to know if this is the intended behavior and if I should call initialize() of implementation contract myself.
I confirmed proxy contract has beend initialized already called and it is not callable anymore.
But implementation contract initialize() still callable and has not initialized.
Doesn't deployProxy() initialize the implementation contract automatically?

:1234: Code to reproduce

const factory = await ethers.getContractFactory("Box")
const proxyContract = await upgrades
      .deployProxy(factory, [], { initializer: "initialize" })
      .then((contract) => contract.deployed())
const implAddress = await upgrades.erc1967.getImplementationAddress(proxyContract.address)
const implContract = factory.attach(implAddress)
await expect(implContract.initialize()).to.be.revertedWith("contract is already initialized") // DO NOT REVERT!

:computer: Environment

Hardhat test environment

1 Like

Hi, welcome to the community! :wave:

IIRC, I think you have got to initialize the implementation contract manually, and maybe you can invoke the _disableInitializers function in the constructor to automatically lock it when it is deployed, such as:

constructor() {
    _disableInitializers();
}
2 Likes

Come to think of it, is there no need to call initialize() in the implementation contract?
Is it correct that I don't need to call initialize because the implementation doesn't store the actual state of use?
Let me know if there's anything wrong

I think this depends on the logic of your implementation contract, for me, I would like to initialize implementation contract all the time.

1 Like

Thank you.
Is there a way to disable initializers while maintaining upgradeable?

Not sure what you want to do.

The plugins help call the initializer on the proxy, while the constructor as Skyge mentioned can be used to disable initializers on the implementation contract itself. See this post for more details.

The reason for disabling initializers on the implementation is explained here. Note that this still allows you to call your contract's initializer through the proxy address (which is what deployProxy() does).

I understand that the constructor should not be used to maintain the upgradeable.
So isn't this way upgradeable because it uses a constructor?
Please let me know if there is anything wrong with me. Thank you.

Maybe you can have a look at here: Proxy Upgrade Pattern - OpenZeppelin Docs

1 Like

This constructor is only for the implementation contract. The proxy does not have access to this constructor and still needs to use an initializer.

1 Like

Hi,

I am finding when using Hardhat Upgrades that I am unable to put a constructor in my implementation and am getting this error message:

contracts\testImplementation.sol:10: Contract testImplementation has a constructor
Define an initializer instead

I am using deployBeaconProxy(), is there a way I can still ensure that the implementation is unable to be initialized using the Hardhat Upgrades API?

thanks

@MichaelGrinstead You can use the following in your implementation contract, including the annotation:

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