Solidity – Can the Storage Keyword be Used in a Solidity Library?

librarysolidity

I was reading about libraries and came to articles saying library cant have storage.

But I have seen libraries where storage keyword has been used.

Example:
in Aave protocol v3

function calculateUserAccountData(
    address user,
    mapping(address => DataTypes.ReserveData) storage reservesData,
    DataTypes.UserConfigurationMap memory userConfig,
    mapping(uint256 => address) storage reserves,
    uint256 reservesCount,
    address oracle
  )
    internal
    view
    returns (
      uint256,
      uint256,
      uint256,
      uint256,
      uint256
    )
  {
 ...........
 DataTypes.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress];

Does this mean that in internal functions storage can be used as the functions are imported from the library?

Am I missing something?

Best Answer

It's not that the storage keyword is forbidden in libraries, it's that they cannot have state variables of their own, because they are stateless.

A state variable is declared at the Contract scope level, hence it is part of the global state of the Contract.

A local storage variable is declared at the function scope level, it is part of the "function's state" as long as the function is executing, then this variable is discarded. The important point is that the local storage variable itself may very well be a reference, so, discarding it doesn't mean that what it refers to is also discarded.

So this is forbidden :

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

library TestLibrary {
    uint256 stateVariable;
}

Because stateVariable is well.. a state variable since it is declared at the contract / library level. The compilation fails with TypeError: Library cannot have non-constant state variables as expected.

The code you provided on the other hand is different, receiving a storage parameter only really make sense if that parameter is a state variable of the calling contract. This is a reference to the caller's state variable, allowing the library function to read / write the calling contract's storage.

Those library calls are actually compiled with delegatecall if the library is deployed, if the library is embedded it will simply be included in the contract's code. Your example is an embedded library if I'm not wrong, but it doesn't really matter.

The second usage :

DataTypes.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress];

Simply creates a storage variable that is a direct reference to reservesData[vars.currentReserveAddress].

From the documentation :

Assignments from storage to a local storage variable also only assign a reference.

Now I might have missed something but it really seems to me like this is just a way to avoid writing reservesData[vars.currentReserveAddress] to access that variable. It can now be accessed with currentReserve.

The point is that this is also not a state variable of the library, it's only a reference to a storage variable that belongs to the caller's storage / state.

Maybe a more precise example with read / write such as this one is a better way to fully answer your question regarding storage parameters / variables with (linked) libraries :

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

struct Wrapper {
    uint256 value;
}

library TestLibrary {

    function testLocalStorageParameter(Wrapper storage wrapper) public {
        // wrapper is a reference to a storage variable from the calling contract
        // Modifying wrapper here modifies the state variable of the calling contract.
        wrapper.value = 10;
    }

    function testLocalStorageVariable(Wrapper storage wrapper) public {
        // wrapper is a reference to a storage variable from the calling contract

        // secondWrapper is also a reference to a storage variable from the the calling contract
        // "Assignments from storage to a local storage variable also only assign a reference."
        Wrapper storage secondWrapper = wrapper;

        // Modifying secondWrapper here also modifies the state variable of the calling contract.
        secondWrapper.value = 20;
    }
}

contract TestContract{
    
    Wrapper wrapper; // wrapper.value = 0 by default

    function getValue() public view returns(uint256) {
        return wrapper.value;
    }

    function calltestLocalStorageParameter() public {
        // After this library call wrapper.value  = 10
        TestLibrary.testLocalStorageParameter(wrapper);
    }

    function calltestLocalStorageVariable() public {
        // After this library call wrapper.value  = 20
        TestLibrary.testLocalStorageVariable(wrapper);
    }
}

The contract is giving storage references of its own state variables to the library so that the library may act on it. The library never had a state variable of its own, it is however allowed to act on the state variable(s) of the contract that is calling it if it is given references to it as parameter.

I hope that answers your question.

Related Topic