EIP-2535 Diamonds specifies the ‘diamondCut’ function which is used to add/replace/remove any number of facets and functions to a diamond in a single transaction. It can be important to execute an upgrade in a single transaction so that a diamond does not get into an inconsistent state at any time.
The ‘diamondCut’ function can also optionally execute an arbitrary external function with delegatecall during an upgrade. This is to initialize state variables and otherwise make any changes needed for an upgrade.
Here is a link to a test script that shows various examples of upgrading a diamond: https://github.com/mudgen/diamond-3-hardhat/blob/main/test/diamondTest.js
Keep State Variables Safe
It is important not to corrupt state variables during an upgrade. It is easy to handle state variables correctly in upgrades.
Here’s some things that can be done:
To add new state variables to an AppStorage struct or a Diamond Storage struct, add them to the end of the struct. This makes sense because it is not possible for existing facets to overwrite state variables at new storage locations.
New state variables can be added to the ends of structs used in mappings.
The names of state variables can be changed, but that might be confusing if different facets are using different names for the same storage locations.
Do not do the following:
If you are using AppStorage then do not declare and use state variables outside the AppStorage struct. Except Diamond Storage can be used.
Do not add new state variables to the beginning or middle of structs. Doing this makes the new state variable overwrite existing state variable data and all state variables after the new state variable reference the wrong storage location.
Do not put structs directly in structs unless you don’t plan on ever adding more state variables to the inner structs. You won't be able to add new state variables to inner structs in upgrades. This makes sense because a struct uses a fixed number of storage locations. Adding a new state variable to an inner struct would cause the next state variable after the inner struct to be overwritten. New state variables can be added to structs used in mappings because those structs are stored in random locations based on keccak256 hashing.
When using Diamond Storage do not use the same namespace string for different structs. This is obvious. Two different structs at the same location will overwrite each other.
These rules will make sense if you understand how Solidity assigns storage locations to state variables. I highly recommend reading and understanding this section of the Solidity documentation: Layout of State Variables in Storage
More detailed information about how diamond upgrades work can be found in this article: How Diamond Upgrades Work