DEV Community

loading...
Cover image for Solidity Libraries Can't Have State Variables -- Oh Yes They Can!

Solidity Libraries Can't Have State Variables -- Oh Yes They Can!

mudgen profile image Nick Mudge ・3 min read

It is well known that Solidity libraries can't have state variables.

If today you do a quick search on the web to find out if Solidity libraries can have state variables you will find that the answer is no, they can't.

Here's from Solidity documentation about libraries:

Libraries cannot have state variables

Notice the first limitation: libraries cannot have state variables.

But documentation will show that it is possible to pass a storage pointer to a library function and access state variables that way. That's well known and has been documented for years.

But what if you want to define, create and use new state variables from within libraries and use them without passing them as parameters?

What if you want to modify whatever contract storage you want, whenever you want, and without passing storage pointers?

Is it possible to do these things with Solidity libraries?

Well from looking at the Solidity documentation the answer seems to be no. If you searched around on the web for how to do this like I have then you will probably find that the answer is no, unless of course you found this blog post.

So I'll say it:

Solidity libraries CAN have state variables!

I hate to be at odds with Solidity documentation, and almost everyone in the world who knows Solidity at this point. But hopefully that won't be for long.

Notice the little line at the bottom of the library limitations:

(These might be lifted at a later point.)

Well, the first limitation that libraries can't have state variables was lifted on 10 March 2020 and nobody noticed.

Adding state variables to libraries isn't just a nice technical trick. Libraries with state variables are useful.

How to Add State Variables to Libraries

Libraries can have/create/use/modify state variables by using Diamond Storage.

What is Diamond Storage? Check out this quote:

Since Solidity 0.6.4 it is possible to create pointers to structs in arbitrary places in contract storage.

That's Diamond Storage. The quote comes from the contract storage section of the Diamond Standard. The Diamond Standard and people implementing diamonds have been pioneering the use of Diamond Storage.

I wrote a blog post about Diamond Storage here: New Storage Layout For Proxy Contracts and Diamonds

To understand better how Diamond Storage can be used to add state variables to libraries see the example below.

Example of Library with State Variables

Here is a simple toy example of a library with state variables. It is written for ease in reading and understanding. It compiles with no errors or warnings.

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

// This library has the state variables 'contractAddress' and 'name'
library Library {

  // defining state variables
  struct DiamondStorage {
    address contractAddress;
    string name;
    // ... any number of other state variables
  }

  // return a struct storage pointer for accessing the state variables
  function diamondStorage() 
    internal 
    pure 
    returns (DiamondStorage storage ds) 
  {
    bytes32 position = keccak256("diamond.standard.diamond.storage");
    assembly { ds.slot := position }
  }

  // set state variables
  function setStateVariables(
    address _contractAddress, 
    string memory _name
  ) 
    internal 
  {
    DiamondStorage storage ds = diamondStorage();
    ds.contractAddress = _contractAddress;
    ds.name = _name;
  }

  // get contractAddress state variable
  function contractAddress() internal view returns (address) {
    return diamondStorage().contractAddress;
  }

  // get name state variable
  function name() internal view returns (string memory) {
    return diamondStorage().name;
  }
}

// This contract uses the library to set and retrieve state variables 
contract ContractA {

  function setState() external {
    Library.setStateVariables(address(this), "My Name");
  }

  function getState() 
    external 
    view 
    returns (address contractAddress, string memory name) 
  {
    contractAddress = Library.contractAddress();
    name = Library.name();
  }
}

Notice that the Library functions setStateVariables, contractAddress and name() are internal functions. These internal functions will be added to ContractA's bytecode, increasing its size. But internal function calls use less gas than external calls, so that's good.

The library functions can be made external instead and they will still work. In that case they will not be added to ContractA's bytecode. They will be externally called using the delegatecode opcode. That's how library functions work.

Note that different libraries will need to use different storage slots and so use a different keccak256ed string. This is to prevent two or more libraries writing to the same locations in contract storage.

Thanks to Aditya Palepu for discovering libraries with state variable with me.

Discussion (1)

pic
Editor guide
Collapse
dhaileytaha profile image
Taha Dhailey

This is amazing article and very nicely explained.
It would be best to add this with diamond standard reference implementation. As new to EIP2535 i could not understand how to change state variables in diamond facets.