Solidity OpenZeppelin – Difference Between __init and __init_unchained in Proxy Contracts

contract-upgradingopenzeppelinproxy-contractssolidity

I am learning to develop upgradeable smart contracts using openzeppelin library for ERC721 and ERC1155 contracts. However, I don't understand what does the unchained function mean. More specifically what's the difference between init and init_unchained functions. Below 2 functions show them being called in an ERC1155 upgradeable contract provided by Openzeppelin

function __ERC1155_init(string memory uri_) internal onlyInitializing {
        __ERC1155_init_unchained(uri_);
    }

function __ERC1155_init_unchained(string memory uri_) internal onlyInitializing {
        _setURI(uri_);
    }

Best Answer

From OpenZeppelin's docs, there is the following section:

Initializer functions are not linearized by the compiler like constructors. Because of this, each __{ContractName}_init function embeds the linearized calls to all parent initializers. As a consequence, calling two of these init functions can potentially initialize the same contract twice.

The function __{ContractName}_init_unchained found in every contract is the initializer function minus the calls to parent initializers, and can be used to avoid the double initialization problem, but doing this manually is not recommended. We hope to be able to implement safety checks for this in future versions of the Upgrades Plugins.

This basically means that multiple inheritance doesn't play nice with initializers.

Generally, a _<CONTRACT_NAME>_init_unchained method defines the initialization of the derived contract while excluding initializing the parents that it derives from (you'd have to explicitly initialize those parents in your contract), while a _<CONTRACT_NAME>_init method defines the initialization of the derived contract along with all the parents that it derives from. In your case, the unchained method happens to require no parent initialization .

In some cases with multiple inheritance, the derived contract that you define could have an collision where two its parent contracts share a parent contract that they both derive from. In such a case, the _<CONTRACT_NAME>_init call on the shared parent that exists in both initializers of the collided contracts would result in attempting to initialize the shared parent contract twice, which causes an error. OpenZeppelin's upgradable contracts have been improving a lot in the past few years in order to minimize the possibility of such collisions. Consider using the Contracts Wizards and ticking the "upgradeability" check box to showcase writing smart contracts with multiple inheritance without being faced with such issues.

Related Topic