In part 1 of this series, we tried to understand why the EIP-2535: Diamond standard is required. In short, this standard allows us to write “transparently” upgradable, flexible, modular and logically easy to maintain smart contracts. In this part, let’s understand exactly how diamonds enable all of this.
What are diamonds?
So following diagram represents the diamonds architecture:
Diamond internally consists of a set of smart contracts which segregates the functionality. These smart contracts are abstracted away from the outside world. They are called “Facets” in diamond’s cool terminology. So facets are responsible for writing and exposing external functions which are then utilized by diamond smart contracts.
However, as mentioned, these smart contracts are internal to diamond architecture. External world doesn’t need to know about them. They only care to interact with the main diamond contract. This main diamond contract itself won’t expose any external function which deals with the particular functionality. When an external function is called on a diamond, the diamond checks to see if it has a facet with that function in the internal mapping of functions to facets and uses it if it exists. Whoosh, we are slowly making progress, hold tight!
Directly from Nick Mudge’s ( author of EIP 2535: Diamond standard) substack: These points are important in order to understand the diamonds:
- A diamond is a smart contract. Its Ethereum address is the single address that outside software uses to interact with it.
- Internally a diamond uses a set of contracts called facets for its external functions.
- All state variable storage data is stored in a diamond, not in its facets.
- The external functions of facets can directly read and write data stored in a diamond. This makes facets easy to write and gas efficient.
- A diamond is implemented as a fallback function that uses delegatecall to route external function calls to facets.
- A diamond often doesn’t have any external functions of its own — it uses facets for external functions which read/write its data.
So, the main diamond contract is nothing but a proxy contract, which “delegates” execution flow to facets. And since external functions in facets are going to be executed in the context of the main diamond proxy, all of the storage is managed by the main diamond itself i.e. msg.sender and msg.value values do not change and only the diamond’s contract storage is read and written to. Having said that, this standard also allows facets to declare its own state data and structs. However, all of the facet’s state is stored in diamond. This point is particularly important. If you don’t understand how proxy-delegate pattern works in solidity, check out this article. The following code represents how fallback function of the diamond looks like:
A diamondCut function
A main diamond proxy has “diamondCut” function to add/replace/remove any number of functions from any number of facets in a single transaction. “diamondCut” updates the mapping “function to facet” shown in the above architecture diagram. Exactly how this diamond function should look is not hard and fast. Standard says:
- It should update the mapping “function to facet”.
- It should emit the relevant event clearly depicting whether a specific function from a specific facet was added / removed / replace to report what changed.
Example implementation for diamondCut can be found here.
In the next part of the series, let’s understand what different storage patterns can be used in diamond standard. Storage patterns define rules for declaring state variables in main diamond as well as facets. If you like the content, do follow me on my socials and feel free to ping me there. Always up for some interesting conversations.
Leave a Reply