The Diamond Standard is a way to organize your Solidity code and contracts to give them the right amount of modularity and cohesion for your system. In addition, flexible upgrade capability is a key part of it.
Let's get defined what a diamond actually is. Generally a diamond is a set of contracts that can access the same storage variables and share the same Ethereum address.
Specifically, and more technically, a diamond is a proxy contract that supports using multiple logic/delegate contracts at the same time. These logic contracts are called facets.
This means that if a function is called on a diamond proxy contract the diamond proxy contract will look in a mapping to see which facet that function belongs to and then call that function from the facet and return the value.
And of course, a diamond is implemented according to the Diamond Standard. That way tools can be used with them and it is easier to understand and work with people's contracts because they follow the standard.
Let's look at a diagram that shows a simple diamond. For simplicity generic names of things are used in this diagram. You should use names that make sense for your system.
In the diagram above you can see that functions func1 and func2 are associated with FacetA. Functions func3, func4, func5 are associated with FacetB. Functions func6 and func7 are associated with FacetC.
Also in this diagram you see that different structs within the proxy are used by different facets. FacetA uses DiamondStorage3. FacetB uses DiamondStorage3 and DiamondStorage2. FacetC uses DiamondStorage2 and DiamondStorage1.
The diagram above shows the DiamondStorage structs in the proxy contract. It is true that all contract storage data is stored in the diamond proxy, not in its facets. But the struct definitions exist in the facet source code.
A key part of the Diamond Standard is how it helps you with contract storage. It introduces a new storage technique called Diamond Storage. It is a new technique because until recently it wasn't possible to do.
By default, when you create new state variables like unsigned integers, structs, mappings etc. Solidity automatically takes care of where exactly these things are stored within contract storage. But this default automatic functionality becomes a problem when upgrading proxy contracts with new facets. New facets declaring new state variables clobber existing state variables -- data for new state variables gets written to where existing state variables exist.
Diamond Storage solves this problem by bypassing Solidity's automatic storage location mechanism by enabling you to specify where your data gets stored within contract storage.
This might sound risky but it is not if you use a hash of a string that applies to your application or is specific to your application. Use the hash as the starting location of where to store your data in contract storage.
Doing that might seem risky to you too. But it is not. Realize that this is how Solidity's storage location mechanism works for maps and arrays. Solidity uses hashes of data for starting locations of data stored in contract storage. You can do it too.
In the past it was common for a logic contract or facet to contain within its source code every single state variable that was ever used by the proxy, in the order they were first declared. Or at least these facets contained state variables in their source code that they didn't use. This was done to avoid the problem of a new facet clobbering existing state variables.
This problem is now solved with Diamond Storage. With Diamond Storage the source code of a facet can just contain the state variables that it actually needs and there is no concern about overwriting existing state variables.
This means that facets that use Diamond Storage are independent from each other and it means that these facets can be reused by different diamonds. In this way facets become reusable libraries for diamonds.
diamondCut function enables you to add, replace and remove any number of functions from a diamond in a single transaction. For example in one function call to
diamondCut you can add 3 new functions, replace 6 functions and remove 4 functions. Or you could call
diamondCut one time to add one function. This is really flexible.
In addition the
diamondCut function emits an event that shows all changes made to a diamond. It records all additions, replacements and removals of functions.
A loupe is a magnifying glass that is used to look at diamonds.
The Diamond Standard provides 4 functions that are used to provide information about what functions and facets are currently stored or used in a diamond. Together these functions are called 'the loupe'. Any diamond implements these functions. See the Diamond Standard for more information about them.
The most important thing is reading and understanding the Diamond Standard. If something is unclear let me know!
The second important thing is using the Diamond Standard reference implementation.
The reference implementation is more than a reference implementation. It is the boilerplate code you need for a diamond. It is tested and it works. Use it. Also, using the reference implementation makes your diamond compliant with the standard.
The DiamondExample.sol contract could be used as is, or it could be used as a starting point and customized. The contract name should be changed to what you want to call your diamond. This contract is the diamond proxy.
The DiamondStorageContract.sol contract could be used as is. It shows how to implement Diamond Storage. This contract includes contract ownership which you might want to change if you want to implement DAO-based ownership or other form of contract ownership. Go for it. Diamonds can work with any kind of contract ownership strategy.
In order to call a function that exists in a diamond you need to use the ABI information of the facet that has the function.
Here is an example that uses web3.js:
let myUsefulFacet = new web3.eth.Contract( MyUsefulFacet.abi, diamondAddress )
In the code above we create a contract variable so we can call contract functions with it.
In this example we know we will use a diamond because we pass a diamond's address as the second argument. But we are using an ABI from the MyUsefulFacet facet so we can call functions that are defined in that facet. MyUsefulFacet's functions must have been added to the diamond (using diamondCut) in order for the diamond to use the function information provided by the ABI of course.
Similarly you need to use the ABI of a facet in Solidity code in order to call functions from a diamond. Here's an example of Solidity code that calls a function from a diamond:
string result = MyUsefulFacet(diamondAddress).getResult()