I'm working on an upgradeable smart contract using OpenZeppelin's upgradeable contracts. I follow the best practice of calling initializer functions in my contract, but I am considering an approach where:
- I always call initializers of direct parent contracts (even if they are currently empty).
- I skip calling initializers of indirect parent contracts (parents of parents) if they are empty, assuming the direct parent will handle them.
Example Scenario
Here’s an example with UUPSUpgradeable
(direct parent) and NoncesUpgradeable
(indirect parent):
contract MyContract is Initializable, UUPSUpgradeable, ERC2771ContextUpgradeable, NoncesUpgradeable {
function initialize() public initializer {
__UUPSUpgradeable_init(); // Direct parent → Call even if empty
__ERC2771ContextUpgradeable_init(); // Direct parent → Call even if empty
// Skipping __NoncesUpgradeable_init() since it's inherited through ERC2771ContextUpgradeable and empty
}
}
Reasoning
- If OpenZeppelin updates
__UUPSUpgradeable_init()
to include necessary logic, my contract is already prepared. - If
__NoncesUpgradeable_init()
needs changes, the responsibility to handle it lies withERC2771ContextUpgradeable
, not my contract. - This approach keeps the contract clean and modular while ensuring upgrade safety.
Question
- Does this approach follow best practices in upgradeable contract development?
- Are there any risks in skipping empty initializers for indirect parents?
- Is there an alternative, more standardized approach?
Any insights would be appreciated!