Solidity

Solidity Libraries

Solidity Libraries Main Tips

  • Libraries in Solidity are similar to contracts, but their purpose is that they are meant to get deployed once to an address to have their functions executed in the context of the calling address.
  • A library being an isolated block of source code, it only accesses state variable of the contract calling it if the variables are supplied explicitly.

Solidity Libraries

Libraries and contracts are largely similar, except that libraries’ purpose is being deployed once at a specified address with its code being reused via the DELEGATECALL (until Homestead, CALLCODE was used) feature of the EVM. Meaning that when library functions are called, the code gets executed inside the calling contract’s context, for example, this would point to the contract that is calling, making its storage accessible.

Since libraries are isolated pieces of source code, they may only access the calling contract’s state variables when they are supplied explicitly (otherwise it could not name them).

You can look at Libraries as implicit base contracts of the contracts using them. They are not to be explicitly visible inside the inheritance hierarchy, however, library function calls seem exactly like calls to functions which belong to explicit base contracts (Lib.f() with Lib being the name of the library). Moreover, internal library functions can be seen inside every contract, as if the library were a base contract. Calls to functions that are internal utilize the internal calling convention, meaning that every internal type may be passed with the memory types getting passed by reference and instead of being copied. In order to realize this inside the EVM, internal library function code and every function called from there would get pulled inside the calling contract, with a regular JUMP call being used instead of a DELEGATECALL.

The code example below shows libraries are meant to be used (make sure you check out the more advanced example to see how sets are implemented).

Example

pragma solidity ^0.4.11;

library SetLib {
  // This will define a whole new struct data type which is going
  // to be used for holding its data inside the calling contract.
  struct LibData { mapping(uint => bool) flagsMapping; }

  // Notice how the first parameter's type is "storage
  // reference" therefore it is only its storage address instead of
  // its contents being passed along with the call. That is the
  // Library functions' special feature. Calling the first
  // parameter "selfStore" is idiomatic, if it happens so that the 
  // function can be seen as that object's.
  function insertData(LibData storage selfStore, uint value)
      returns (bool)
  {
      if (selfStore.flagsMapping[value])
          return false; // is already there
      selfStore.flagsMapping[value] = true;
      return true;
  }

  function removeData(LibData storage selfStore, uint value)
      returns (bool)
        {
      if (!selfStore.flagsMapping[value])
          return false; // is not there
      selfStore.flagsMapping[value] = false;
      return true;
  }

  function contains(LibData storage selfStore, uint value)
      returns (bool)
  {
      return selfStore.flagsMapping[value];
  }
}

contract Cont {
    SetLib.LibData valuesKnown;

    function registerData(uint value) {
        // The functions of the library may be called without a
        // certain instance of the library, because the
        // "instance" is going to be the current contract.
        require(SetLib.insertData(valuesKnown, value));
    }
    // Inside this contract, it's possible to directly access valuesKnown.flagsMapping too, in case we want.
}

 

Try on Remix Try live on Hosting

However, this is not the only way libraries can be used – you don’t have to define struct data types to use them. Additionally, these functions work without requiring any storage reference parameters, so they may have multiple storage parameters of reference and in any position.

The calls to SetLib.insert, SetLib.contains and SetLib.remove all are compiled as calls (DELEGATECALL) to an external contract/library.

When you use libraries, make sure that a real external function call gets performed. msg.valuemsg.sender and this are going to retain their values in that call, though (before Homestead, because of CALLCODE being used, msg.value and msg.sender changed, though).

The below example will show how memory types and internal functions inside libraries get used to implementing custom types without external function call overhead:

Example

pragma solidity ^0.4.0;

library IntBig {
    struct intbig {
        uint[] currentLimbs;
    }

    function fromUint(uint var1) internal returns (intbig r) {
        r.currentLimbs = new uint[](1);
        r.currentLimbs[0] = var1;
    }

    function add(intbig _var1, intbig _var2) internal returns (intbig r) {
        r.currentLimbs = new uint[](max(_var1.currentLimbs.length, _var2.currentLimbs.length));
        uint carry = 0;
        for (uint i = 0; i < r.currentLimbs.length; ++i) {
            uint var1 = currentLimb(_var1, i);
            uint var2 = currentLimb(_var2, i);
            r.currentLimbs[i] = var1 + var2 + carry;
            if (var1 + var2 < var1 || (var1 + var2 == uint(-1) && carry > 0))
                carry = 1;
            else
                carry = 0;
        }
        if (carry > 0) {
            // too bad, we have to add var1 currentLimb
            uint[] memory newerLimbs = new uint[](r.currentLimbs.length + 1);
            for (i = 0; i < r.currentLimbs.length; ++i)
                newerLimbs[i] = r.currentLimbs[i];
            newerLimbs[i] = carry;
            r.currentLimbs = newerLimbs;
        }
    }

    function currentLimb(intbig _var1, uint _limb) internal returns (uint) {
        return _limb < _var1.currentLimbs.length ? _var1.currentLimbs[_limb] : 0;
    }

    function max(uint var1, uint var2) private returns (uint) {
        return var1 > var2 ? var1 : var2;
    }
}

contract C {
    using IntBig for IntBig.intbig;

    function f() {
        var var1 = IntBig.fromUint(7);
        var var2 = IntBig.fromUint(uint(-1));
        var var3 = var1.add(var2);
    }
}

 

Try on Remix Try live on Hosting

Because the compiler has no way of knowing where the library will be deployed at, it is required that the addresses are filled into the final bytecode via a linker. In the scenario that the addresses are not supplied in the form of arguments to the compiler, the hex code that is compiled is going to contain placeholders of the form __SetLib______ (SetLib being the library’s name). The address may be filled manually replacing all of the 40 symbols next to the hex encoding of the library contract’s address.

Here is a list of restrictions that libraries have (which contracts don’t):

  • Cannot receive Ether transactions
  • Cannot inherit/ be inherited
  • No state variables

Note: The restrictions mentioned here may be lifted at some point.

Read previous post:
Solidity Interfaces

Solidity Interfaces Main Tips Interfaces in Solidity are similar to abstract contracts, but the main difference is that they cannot have...

Close