New Storage Layout For Proxy Contracts and Diamonds

Nick Mudge
1MillionDevs
Published in
6 min readMar 11, 2020

Note: A diamond is a contract that gets its external functions from other deployed contracts called facets. A diamond implements the diamond standard.

Storage

In my last article about storage layout I explained how storage layout works in Solidity and I showed three different ways proxy contracts and diamonds handle storage layout. And I explained the downsides of each approach.

But now, all of a sudden, there is a new way, a better way. It has none of the downsides of the old ways.

The new way is made possible by a change to the Solidity compiler that happened yesterday. Here is what the Solidity changelog says:

It is now possible to set storage slots for storage reference variables from inline assembly. We expect this to allow new patterns in connection to delegatecall proxies and upgradable contracts.

It is now possible to create storage pointers to arbitrary places in contract storage. This new capability makes possible a new storage layout pattern I started calling Diamond Storage because it is particularly useful to diamonds.

Here is the Diamond Storage pattern:

  1. Create a contract. This is a Diamond Storage contract but let’s call it “storage contract” because that is shorter.
  2. Create a struct in the storage contract that contains the state variables you care about.
  3. Choose a position in storage to read and write your struct.
  4. Write a function in the storage contract that creates a storage pointer for the struct and returns it.
  5. Any contract/proxy/facet that needs to read/write the state variables defined in the storage contract just needs to inherit the storage contract and call the function that returns the storage pointer.

Here is a simple, made up example of a contract that uses Diamond Storage:

contract MyStorageContract {// The state variables we care about.
struct MyStorage {
uint aVar;
bytes myBytes;
mapping(uint => bytes32) myMap;
}
// Creates and returns the storage pointer to the struct.
function myStorage() internal pure returns(MyStorage storage ms) {
// ms_slot = keccak256("com.mycompany.my.storage")
assembly {ms.slot := 0xabcd55b489adb030b...d09c4154cf0}
}
}

If you want to see a real example check out the reference implementation of the diamond standard which was recently updated to use Diamond Storage. Specifically look at LibDiamond.sol.

Here is an example of code that uses the above MyStorageContract:

contract MyContract is MyStorageContract { function doSomething(uint selector, bytes32 myData) external {
MyStorage storage ms = myStorage();
ms.myMap[selector] = myData;
ms.aVar = uint(myData);
//... more code;
}
function returnMyData(uint selector) external view returns(bytes32){
MyStorage storage ms = myStorage();
bytes32 data = ms.myMap[selector];
//... more code
return data;
}
}

Why is Diamond Storage Good?

If you read my prior article about storage layout you will understand that proxy contracts can’t use Solidity’s default storage layout. As a result people have resorted to three different ways to handle storage layout. Each of these ways has downsides. Diamond Storage is good because it doesn’t have the downsides of the other ways. Let’s compare:

Unstructured Storage

What is it:

Solidity’s storage layout can be bypassed with assembly allowing a programmer to set and store values at arbitrary positions in contract storage. This is the Unstructured Storage pattern.

Downsides:

1. A getter and setter function needs to be defined and used for each storage variable.

2. This works for simple values. It does not work for structs or mappings.

Diamond Storage has some similarity to the Unstructured Storage pattern because it bypasses Solidity’s default storage layout using assembly. But Diamond Storage has several major differences and does not have its downsides.

  1. Diamond Storage only has one getter function that returns a storage pointer to a struct which can contain any number of values. The storage pointer is used directly to read and write values to storage.
  2. Diamond Storage works with structs which can contain multiple simple values, arrays, maps and other structs.

Inherited Storage

What is it:

A proxy contract and its facets inherit a storage contract that contains the storage variables that they use. This ensures that the proxy contract and its facets all declare the same state variables in the same order so there is no conflict.

Downsides:

1. Facets need to inherit storage contracts that may contain many state variables that they don’t use.

2. Facets become tightly coupled to specific proxy contracts and facets and cannot be used by other proxy contracts and facets that declare different state variables.

Diamond Storage doesn’t have these downsides.

  1. Proxy contracts, diamonds and facets only need to inherit the Diamond Storage contracts that contain the state variables that they use. This is cleaner.
  2. Diamond Storage decouples facets and proxy contracts. Facets that use Diamond Storage can be used with different proxy contracts/diamonds and other facets. Diamond Storage makes facets and proxy contracts/diamonds more like legos that can be plugged together and reused in different ways.

Eternal Storage

What is it:

Using Solidity mappings to create an API for contract storage. A proxy contract and facets can use the same Eternal Storage API to create and use whatever storage variables are needed without conflict.

Downsides:

1. The state variable syntax and organization of Eternal Storage is clumsy. It is full of `getInt`, `setInt`, `getString`, `setString` type of functions.

2. It works directly for simple values and arrays but it does not work in a simple, generic way for mappings and structs.

3. A proxy contract and all of its facets must use the same storage API.

4. Not easy to see at a glance what state variables exist because they are not declared together anywhere.

Diamond Storage does not have these downsides.

  1. Diamond Storage has a nicer syntax for state variables. And using a local storage pointer makes it explicit that state variables are being read and written to.
  2. Diamond Storage works with structs which can contain multiple simple values, arrays, mappings and other structs.
  3. There is no enforced storage API, just structs and storage pointers to them.
  4. Each Diamond Storage contract has a struct that declares the state variables together.

Diamonds

I have mentioned that Diamond Storage is particularly good for diamonds. Let me tell you why.

A diamond is a proxy contract that delegates to different facets based on what function is called. Many designs of diamonds are possible but I am certain that in some designs there will be a number of facets that contain distinct functionality. For example a diamond could be structured like this:

  1. The diamond has 10 facets.
  2. Each facet has 1 to 25 functions.
  3. Each facet covers a distinct area of functionality.
  4. Each facet has its own state variables declared it its own Diamond Storage contract, or it shares state variables with a small number of facets via one or more Diamond Storage contracts.

In the above structure each facet doesn’t have to know about the state variables of other facets. The Diamond Storage pattern helps separate and compartment facets into separate areas of functionality.

It is possible that a particular facet gets too large, reaching the 24KB max contract size. It is then possible to break the facet into two facets that share the same Diamond Storage contract.

See the diamond standard for more info about diamonds.

Upgradable Proxy Contracts with a Single Facet

Upgradeable proxy contracts that only delegate to a single logic contract/facet work well with the Unstructured Storage pattern because functionality is not broken up into different pieces for different facets that need different state variables.

In this case the proxy contract uses Unstructured Storage to get and set a couple important state variables like the owner of the contract and the address of the facet to delegate to. The facet that is delegated to uses Solidity’s regular storage layout.

Such a proxy contract could use Diamond Storage in a similar way that it uses Unstructured Storage.

--

--

Nick Mudge
1MillionDevs

Ethereum contract programmer, security auditor and standards author. Author of the diamond standard.