_disableInitializers() was added in Contracts v4.6.0 as part of the reinitializers support. The Caution note in Initializer provides more details on why this is needed.
Thank you for mentioning the difference with the upgrades documentation. The contructor() initializer {} usage was recommended for previous version of Contracts. I've opened an issue to update the upgrades documentation to refer to _disableInitializers().
Hello @ericglau , just curious if we can still use constructor() initializer{} to prevent the implementation contract initialization issue after 4.6.0?
@QiLOL It is recommended to use _disableInitializers() so that the implementation cannot be initialized to any version.
If you use constructor() initializer{}, you can see that the initializer modifier only sets the _initialized version to 1, meaning that it could still be possible for the implementation to be reinitialized to a higher version number (if the implementation had a way to do so).
@tibas To summarize, the implementation contract (logic contract) should look similar to the first post. It makes sense to have a both _disableInitializer constructor and initialize function. The constructor is to disable the implementation contract from being initialized, while the initializer function is to allow the proxy to be initialized.
Hello, If we are updating the implementation contract (i.e. using implementationV1.upgradeTo(address(implementationV2))) is it still true that we need both the constructor with _disableInitializer AND the initialize function?
I'm little confused here because when running proxy.upgradeTo() function like above, it does not run the initialize function in the V2 implementation (when _disableInitializer is present in the constructor).
@ska123 If the implementationV2 is only meant to be used for an upgrade (meaning you will not be deploying a new proxy with V2 directly), then you are right that you don't need an initializer in V2. You may want to have a reinitializer though to act as a migration function (if your contract needs it), and call that using upgradeToAndCall() so it is called atomically with the upgrade.
Can you explain why do we need them for proxies that are not UUPS? Most contracts only include _disableIntiialziers() if the implementation only interacts with UUPS.
But doing this also has the benefit of preventing users from using the implementation directly, and helps prevent others from overtaking the implementation in general (there is more risk for UUPS since UUPS has the upgrade logic in the implementation so you really don't want someone to overtake it, but in general the risk depends on your code).
I'm working on an upgradeable Solidity contract and I'm encountering an issue with reinitialization. I've used _disableInitializers() in my constructor to prevent accidental reinitialization, but I'm still seeing my initialize() function being called multiple times during contract upgrades. I've also defined an initializeV2() function with the initializer modifier. Can you please explain why initialize() is being called multiple times and how I can prevent this?
I think the modifier _disableInitializers is used to avoid leaving an implementation contract uninitialized, such as:
constructor() {
_disableInitializers();
}
so then your implementation contract can not be initialized, that is you can not call a function which has a modifier initializer in the implementation contract, such as
function initialize() public initializer() {
xxx;
}
When you upgrade your contract, you can use the modifier reinitializer(version), such as
function migrateToV2() public reinitializer(2) {
xxx;