DEV Community

Nick Mudge
Nick Mudge

Posted on • Edited on

AppStorage Pattern for State Variables in Solidity

I have written about the diamond storage pattern for organizing contract state variables in proxy contracts and diamonds.

As a quick refresher, a state variable or storage layout organizational pattern is needed when writing proxy contracts or diamonds in Solidity because Solidity's builtin storage layout system doesn't work for them.

Diamond storage is particularly good for isolating or compartmenting state variables to specific facets or functionality. This is great for creating modular facets that can be understood as their own units and be added to diamonds. A diamond with a lot of functionality is well organized and understandable if each of its facets can be understood in isolation. Diamond storage helps make that possible.

However, I have found that when building applications with diamonds I often want to share state variables specific to my application with facets that are specific to my application. It gets tedious to call a diamondStorage() function in every function that I want to access state variables.

I have successfully utilized a new pattern in contracts called AppStorage. It is a nicer and more convenient way to access application specific state variables that are shared among facets.

The pattern works in the following way:

1) Define a struct called AppStorage that contains all the state variables specific to your application and that you plan to share with different facets. Store AppStorage in a file. Any of your facets can now import this file to access the state variables.

// AppStorage.sol
struct AppStorage {
  uint256 secondVar;
  uint256 firstVar;
  uint256 lastVar;
  ...
}
Enter fullscreen mode Exit fullscreen mode

2) In a facet that imports the AppStorage struct declare an AppStorage state variable called s. This should be the only state variable declared in the facet. Here's an example:

import "./AppStorage.sol"

contract StakingFacet {
  AppStorage internal s;
  ...
Enter fullscreen mode Exit fullscreen mode

Now in your facet you can access all the state variables in AppStorage by prepending state variables with s.. Here's a simple example:

import "./AppStorage.sol"

contract StakingFacet {
  AppStorage internal s;

  function myFacetFunction() external {
    s.lastVar = s.firstVar + s.secondVar;
  }
Enter fullscreen mode Exit fullscreen mode

This is a very nice convention because it makes state variables concise, easy to access, and it distinguishes state variables from local variables and prevents name clashes/shadowing with local variables and function names. It helps identify and make explicit state variables in a convenient and concise way. It is also a little more gas efficient than diamond storage access.

Since AppStorage s is the first and only state variable declared in facets its position in contract storage is 0. This fact can be used to access AppStorage in Solidity libraries using diamond storage access. Here's an example of that:


library MyLib {
  function appStorage() 
    internal 
    pure 
    returns (AppStorage storage ds) 
  {    
    assembly {
      ds.slot := 0
    }
  }

  function someFunction() internal {
    AppStorage storage s = appStorage();
    ... do stuff
  }
}
Enter fullscreen mode Exit fullscreen mode

AppStorage s can be declared as the one and only state variable in facets or it can be declared in a contract that facets inherit.

AppStorage won't work if state variables are declared outside of AppStorage and outside of Diamond Storage. It is a common error for a facet to inherit a contract that declares state variables outside AppStorage and Diamond Storage. This causes a misalignment of state variables.

One downside is that state variables can't be declared public in structs so getter functions can't automatically be created this way. But I think it is better anyway to make your own getter functions for state variables because its more explicit.

The GHSTStakingDiamond is a good example of a diamond that uses the AppStorage pattern.

For a large, in production, successful example of using the AppStorage pattern, checkout the Aavegotchi diamond. The AppStorage struct is defined in this file: LibAppStorage.

Notice that the AppStorage struct utilizes other structs in arrays and mappings.

Top comments (9)

Collapse
 
amxx profile image
Hadrien Croubois

Regarding the use of an AppStorage struct, I always achieved similar result by creating an abstract AppStorage contract, that all my module/facets inherit from (first inheritance).

Do you have any strong opinion/argument against storage through inheritance ?

Collapse
 
mudgen profile image
Nick Mudge • Edited

What you describe is the inherited storage pattern. It is totally fine. AppStorage is similar and can also be placed in a storage contract that is inherited by all facets.

What I like about AppStorage is that it adds a "s." prefix to all state variables which separates them from arguments, return variables and local variables. For me this makes things more explicit, clear and prevents name shadowing with function names or other variable names.

Collapse
 
binzydev profile image
Binzy

The s. is pretty great. Hey have you run into any storage access issues? I cut a new facet and use a library get() function like someFunction() that returns the slot (hashed with keccak256). But it returns empty storage. It's definitely the same diamond contract but seems to get mixed up. Maybe I did something wrong?

library ArcaneBarracksStorage {
bytes32 constant namespace = keccak256("game.arcane.storage.barracks");

struct Storage {
    mapping (address => mapping (uint8 => uint256)) items;
}

function get() internal pure returns (Storage storage s) {
    bytes32 addr = namespace;
    assembly {
        s.slot := addr
    }
}
Enter fullscreen mode Exit fullscreen mode

}

Thread Thread
 
mudgen profile image
Nick Mudge

You will run into trouble if you declare state variables outside AppStorage or DiamondStorage or inherit contracts that do that.

Collapse
 
tannakartikey profile image
Kartikey Tanna

We actually don't need the following line in the code sample:
bytes32 position = DIAMOND_STORAGE_POSITION;

Because we are always pointint to the slot 0, or the first variable declaration.

Thank you for the post.

Collapse
 
mudgen profile image
Nick Mudge

You are right, so I removed that line.

Collapse
 
encodetype profile image
Teepakorn.A

Hi Nick

some question please.

for this approach. can we upgrade AppStorage when we need to append more variable, or struct ? can old data still existed

Thks nick

Collapse
 
mudgen profile image
Nick Mudge

Yes, add new state variables to the end of the AppStorage struct. Check out this article for rules on upgrading: eip2535diamonds.substack.com/p/dia...

Collapse
 
voidcenter profile image
Justin Zhang

nice post! is a down side for this approach the loss of public getters?