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:
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.
Top comments (6)
Hello!... thanks for shared your job...
any example to apply some pattern to divide logic and data structures of a contract type erc721?
I have a contract that I am building that is at the limit, (a marketplace with auction)... I would like to have 3 contracts, one for the logic of the marketplace, another for the auction, and the main one on top of these...
question, I've been researching but I don't understand which is the best way to do it.
Whether to use library, or to make 3 contracts and import the 2 in the main one and instantiate them....
Which would be the best way?
Thanks!
great post! I wonder where can I find some code examples for "The library functions can be made external instead and they will still work. In that case ... They will be externally called using the delegatecode opcode." ? I tested locally and for some errors.
if we use diamond storage with delegatecall, then what's the difference between contract and library?
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.
Very interesting!
"They will be externally called using the delegatecode opcode. That's how library functions work."... Did you mean 'DELEGATECALL' here?
Thank you for this, I learnt a lot.
AMAZING read.
Thanks Nick for bringing this up.