DEV Community

Nick Mudge
Nick Mudge

Posted on • Edited on

How Diamond Storage Works

Diamond storage is a contract storage strategy that is used in proxy contract patterns and diamonds.

It greatly simplifies organizing and using state variables in proxy contracts and diamonds.

Diamond storage relies on Solidity structs that contain sets of state variables that are easy to read and write.

A struct can be defined with state variables and then used in a particular position in contract storage. The position can be determined by a hash of a unique string. The string acts like a namespace for the struct. For example a diamond storage string for a struct could be com.mycompany.projectx.mystruct. That will look familiar to you if you have used programming languages that use namespaces.

Namespaces are used in some programming languages to package data and code together as separate reusable units. Diamond storage packages sets of state variables as separate, reusable data units in contract storage.

Let's look at a simple example of diamond storage:

library MyStructStorage {
  bytes32 constant MYSTRUCT_POSITION = 
    keccak256("com.mycompany.projectx.mystruct");

  struct MyStruct {
    uint var1;
    bytes var2;
    mapping (address => uint) var3;
  }

  function myStructStorage()
    internal 
    pure 
    returns (MyStruct storage mystruct) 
  {
    bytes32 position = MYSTRUCT_POSITION;
    assembly {
      mystruct.slot := position
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The diamond storage defined above can be used like this:

function myFunction()
  external
{
  MyStructStorage.MyStruct storage mystruct =
    MyStructStorage.myStructStorage();

  mystruct.var1 = 10;
  uint var3 = mystruct.var3[address(this)];

  // etc
}
Enter fullscreen mode Exit fullscreen mode

Instead of using a hash of a string other schemes can be used to create random positions in contract storage. Here is a scheme that could be used:

bytes32 constant MYSTRUCT_POSITION = 
    keccak256(abi.encodePacked(
      ERC1155.interfaceId, 
      ERC1155.name, 
      address(this)
    ));
Enter fullscreen mode Exit fullscreen mode

Diamond storage is easy to use correctly, but like any tool it needs to be used correctly.

Don't make these mistakes:

  1. Do not use the same namespace string for different structs. Because the two structs will overwrite each other in storage.

  2. Don't do the following: Deploy a contract that uses a particular struct. Then upgrade your contract with a modified version of the struct that is stored at the same location and with new state variables added or state variables removed in the beginning or middle. This is similar to storing two different structs at the same location in contract storage. However it is safe to add new state variables to the end of structs that are already being used.

  3. Don't put structs directly in structs unless you are sure you won't want to add new state variables to the end of the inner structs. You won't be able to add new state variables to inner structs in upgrades. But you can put structs in mappings, and still extend the structs in the future. To understand this more read about how state variables are laid out in contract storage here: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html

These mistakes are easy to avoid by being aware of them and keeping track of what namespaced strings are used. Also a naming convention can be used to give structs unique strings.

Here are some additional references that cover diamond storage:

Diamond storage is also called common storage. Smart contract developer Jules Goddard writes about it:

Top comments (2)

Collapse
 
jjiajun profile image
Tristan Teo

Concise introduction to diamond storage, thanks Nick!

Collapse
 
web3_manu profile image
Manu Kapoor

Woah... it just made me realize that I need to dig too deep into state variables - storage stuff to get good at using Diamond Storage.

And, thanks Nick for sharing Jules' articles, will read.