A better way to initialize upgradeable contracts

EIP-2535 Diamonds

John Reynolds
4 min readNov 19, 2022

Overview

Smart contract code is immutable, which means that the code cannot be changed after deployment. This presents challenges when developing on the blockchain because it prevents the ability to perform maintenance or updates. Smart contracts also face a 24KB contract size limit, introducing scalability dilemmas. The EIP-2535 standard offers a sophisticated proxy pattern that allows the addition, replacement, and removal of functions while virtually removing any constraint on code size.

While smart contracts are immutable, they can add and remove references to external functions on other smart contracts and still preserve storage context by using delegate calls. This is the standard architecture for proxy systems.

Example: Proxy Contract

EIP-2535 Diamond standard introduces a generic proxy contract, the Diamond, which includes an internal diamondCut() function and exposes a fallback function, which dynamically dispatches function calls to the facets called from the Diamond.

Example: fallback() function

The fallback function allows the facet functions to be executed as if the Diamond had implemented them. The msg.sender and msg.value values remain the same, and the Diamond's storage is read and written to.

A diamond proxy contract is a single source for all state variable data. Facets provide the code and state variable definitions used to read and write to a diamond proxy's contract storage.

Example: Diamond Contract with AppStorage

A diamond contains within it a mapping of function selectors to facet addresses. Functions are added/replaced/removed by modifying this mapping.

DiamondCut

The diamondCut function is used to add/replace/remove any number of facets and functions to a diamond in a single transaction, preventing an inconsistent state on the Diamond.

Example: IDiamondCut

The diamondCut() function can execute an external function with delegatecall during an upgrade. This is used to initialize state variables and make any changes needed for an upgrade.

Initializing State

The second and third arguments of the diamondCut() function are used for initializing the state after an upgrade. The _init argument holds the contract address of a function to call to initialize the state of the Diamond. The _calldata argument has a function call to send to the contract at _init.

Example - if an ERC20 Facet is added to a Diamond using diamondCut(), the_calldata argument provides the function call to set token name and symbol values and the _init argument provides the address of the contract with the init function. The init function is executed in the same transaction as the diamondCut().

The _calldata execution is skipped if the _init value is address(0), therefore, _calldata can contain 0 bytes or custom information.

DiamondInit

After adding/replacing/removing functions, the _calldata argument is executed with delegatecall on _init. This execution is done to initialize data, set up, or remove anything no longer needed. DiamondInit functions can have parameters and can be reused as needed once deployed.

Example: DiamondInit

The EIP-2535 also defines a DiamondMultiInit function that executes multiple initializer functions for a single upgrade. This can be useful when users want to support a unique initialization function per cut. DiamondMultiInit exists as a library to prevent it from being deleted as a result of a delegatecall to selfdestruct .

DiamondMultiInit

During the execution of an upgrade, using the _init and _calldata arguments allow state initialization to occur in the same transaction. This is important because it prevents the Diamond from falling into an inconsistent state, which could arise if state variables are not initialized in sync with the functions being added.

Conclusion

EIP-2535 Diamonds introduce a new and better approach for initializing upgradeable contracts. The standard provides a function for single and multiple function initializations after the diamondCut() function. The approach synchronizes upgradeability and state initialization into the same transaction, helping ensure the Diamond always maintains a consistent state.

Resources

--

--

John Reynolds

Former US Air Force Cyber 🛰️ | Identity & Zero-Knowledge at Aleo | Advancing ZK with Z_PRIZE