1MillionDevs
Published in

1MillionDevs

Solidity Storage Layout For Proxy Contracts and Diamonds

Note: A proxy contract is a contract that delegates its function calls to another contract called a delegate contract or logic contract or facet. A diamond is a contract that can delegate function calls to different contracts called facets. Diamonds are specified by the diamond standard. Proxies and diamonds are used for contract upgrades and to reuse functionality in existing deployed contracts.

Storage Layout

Storage layout is how and where state variables in contracts are actually stored in contract storage.

Storage layout isn’t something you have to think about when writing a regular contract. It is just taken care of by the Solidity compiler.

But this not the case when writing a proxy contract or diamond. When writing a proxy contract or diamond you have to be aware of it and take care of it.

Before describing why, let’s first describe the layout of state variables in contract storage. Here’s a simplified explanation of how it works:

  1. Storage layout for state variables begin at position 0 and increments for each new state variable. So the first state variable is stored at position 0, the second state variable is stored at position 1, the third stored at position 2, etc.
  2. Each struct or array element uses the next storage position, as if each one was defined separately on its own.
  3. Dynamic arrays and maps also use the next position in storage but, due to their dynamic nature, store their values at positions based on hashes of their storage position and keys.

More details about how storage layout works is given in Solidity documentation.

The main thing to understand is that storage layout begins at position 0 and increments for each new state variable.

The Problem

A proxy contract and its delegate/logic contracts or facets (all terms for the same thing) share the same storage layout!

Here is an example to illustrate the problem. ProxyA defines two state variables, facetA and owner .

contract ProxyA {  address facetA;  
address owner;
constructor() public {
owner = msg.sender;
facetA = 0x0b22380B7c423470979AC3eD7d3c07696773dEa1;
}
fallback() external payable {
address facetAddress = facetA;
assembly {
... code omitted for simplicity
}
}
}

FacetA declares one state variable.

contract FacetA {  address user;  function getUser() external view returns(address) {
return user;
}
function setUser(address newUser) external {
user = newUser;
}
}

ProxyA delegates function calls to FacetA. The problem is that, when delegating, ProxyA and FacetA share the same storage layout. The state variable facetA is at position 0. And The state variable user is also at position 0. So if the setUser function is called it will set user and facetA to the newUser value, which is obviously not the intention, the intention just being to set user only.

This problem is especially a problem for diamonds because one diamond can have many facets and they all share the same storage layout.

People have come up with different patterns to solve this problem. Here are some patterns used:

Unstructured Storage

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. Here is an example of doing this with ProxyA:

contract ProxyA {  function getOwner() internal view returns(address owner) {
bytes32 position = keccak256("owner");
assembly {
owner := sload(position)
}
}
function setOwner(address owner) internal {
bytes32 position = keccak256("owner");
assembly {
sstore(position, owner)
}
}
function getFacet() internal view returns(address facet) {
bytes32 position = keccak256("FacetA");
assembly {
facet := sload(position)
}
}
function setFacet(address facet) internal {
bytes32 position = keccak256("FacetA");
assembly {
sstore(position, facet)
}
}
}

In the above example there are “get” and “set” functions that are used to get and set the owner and facetA storage variables. This bypasses solidity’s storage layout. This is great because using this there is no conflict with FacetA’s storage variables, or any facet’s storage variables as long as different keccak256 hashes are used. However there are some downsides to this pattern:

  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.

Inherited Storage

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. Here is an example:

contract Storage1 {
address owner;
address facetA;
address user;
}
contract ProxyA is Storage1 {
... code omitted for simplicity
}
contract FacetA is Storage1 {
... code omitted for simplicity
}

After deploying and connecting these contracts you may want to create more facets for the proxy and add additional state variables. This can be done by creating a new storage contract that inherits the old one and adds new sate variables. Here is an example:

contract Storage2 is Storage1 {
address facetB;
address nextUser;
}
contract FacetB is Storage2 {
... code omitted for simplicity
}

FacetB can still be used by ProxyA because its new state variables in Storage2 are defined after the existing ones in Storage1.

This works but there are some downsides to this pattern:

  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.

Eternal Storage

This is the idea of 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. Here is an example using our ProxyA and FacetA contracts rewritten to use Eternal Storage:

contract ProxyA {  mapping(bytes32 => uint256) internal uIntStorage;
mapping(bytes32 => uint256[]) internal uIntArrayStorage;
mapping(bytes32 => string) internal stringStorage;
mapping(bytes32 => address) internal addressStorage;
mapping(bytes32 => bytes) internal bytesStorage;
constructor() public {
addressStorage["owner"] = msg.sender;
addressStorage["facetA"] = 0x0b22380B7c423470979AC3eD7...;
}
fallback() external payable {
address facetAddress = addressStorage["facetA"];
assembly {
... code omitted for simplicity
}
}
}

FacetA contract:

contract FacetA {  mapping(bytes32 => uint256) internal uIntStorage;
mapping(bytes32 => uint256[]) internal uIntArrayStorage;
mapping(bytes32 => string) internal stringStorage;
mapping(bytes32 => address) internal addressStorage;
mapping(bytes32 => bytes) internal bytesStorage;
function getUser() external view returns(address) {
return addressStorage["user"];
}
function setUser(address newUser) external {
addressStorage["user"] = newUser;
}
}

As you can see the contracts just use the mappings to read and store state variables. This works well, but there are some downsides:

  1. Clumsy syntax for state variables.
  2. It works directly for simple values and arrays but it does not work in a simple, generic way for mapping and struct values.
  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.

Conclusion

People have come up with a number of ways for implementing and managing storage layout for proxy contracts.

What way is best depends on circumstances and personal preferences. Here are my opinions on the matter:

  1. The Unstructured Storage pattern is good for a proxy contract that delegates to a single facet.
  2. The Inherited Storage and Eternal Storage patterns are good for diamonds that delegate to different facets depending on which function is called. I prefer the Inherited Storage pattern but if I want my facets to work with different diamonds then I would use Eternal Storage.

Surprise!

The above methods work. But I am not satisfied with any of them. I have a confession to make. I was motivated in writing this article because I found a new way to handle storage layout for proxy contracts and diamonds. A new way that doesn’t have any of the downsides of the other ways.

A better way!

I dubbed the new way Diamond Storage because it is particularly useful to diamonds. Remember diamonds are proxy contracts that use multiple facets and implement the diamond standard.

In a new article I plan to tell you all about Diamond Storage!

Update: I wrote that new article here: New Storage Layout For Proxy Contracts and Diamonds

Find a job developing smart contracts using Solidity at delegatecall.careers.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nick Mudge

Nick Mudge

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