How Libraries Work in Solidity

Libraries are special contracts in Solidity, which are repositories of reusable code available to other contracts.

Libraries themselves are stateless – they do not have storage, as they’re designed to plug in to other contracts.

We’ll use the fantastic standard contracts and libraries from OpenZeppelin to illustrate how libraries work.

Use of Libraries in dApps

DApp developers tend to use libraries for:

  1. Creating custom modules for code re-use
  2. Importing proven, tested functionality to contracts from trusted 3rd parties

1) Modularity and DRY dApp Development

Libraries can define new datatypes and static functions, which may then be re-used in contract code.  

We can also use libraries to add functionality to datatypes. For example, SafeMath in OpenZeppelin enables safer arithmetic operations for uints that guard against overflow and underflow vulnerabilities.

2) Proven Libraries Reduce Risk

Ethereum developers must employ ‘adversarial thinking’. That is: we must assume people are trying to hack our dApp, drain all the ether, gain an unfair advantage, or simply break it.

For known contract patterns like a token sale, access control or ownership, it’s smart to use well-vetted libraries that have been battle-tested and (so far) proven immune to known vulnerabilities.

We can limit the attack surface of our dApp by re-using functionality from such trusted libraries.

How do Contracts Access Libraries?

On Ethereum, contracts are compiled to bytecode and deployed to the blockchain. Contracts on the blockchain are ‘live’, and their code execution can be triggered by an incoming transaction.

Standard Contract Inheritance

Let’s quickly cover how contracts inherit from other contracts, before we look at how contracts access libraries.

When contract C inherits from contracts A and B, contract C inherits all of A and B’s non-private variables, functions and struct definitions. The rules are very similar to conventional inheritance in object-oriented languages.

When C is deployed, the code from contracts A, B and C is compiled to bytecode and deployed on the blockchain as a single contract.

Library Usage

Unlike contract inheritance, where inherited contract code is is ‘rolled into one’ and deployed as a single contract, libraries are normally deployed at their own address on the blockchain.

There are a couple of ways in which contracts access libraries:

Call the Library at its Address

If a library is already deployed to the blockchain and its address is known, a contract can access it directly via its address. 

Simultaneous Deployment of Contract and Libraries

Normally, if a contract in a dApp uses a library, the contract and the library will be deployed together.

This process is managed by a framework like Truffle, which automagically deploys libraries and contracts and links them properly.

At deployment, the library is converted to bytecode, deployed, and a linker is included in the contract bytecode.  The contract is then also deployed. This gives the contract access to the library’s functions.

Truffle and similar frameworks make development and deployment of smart contracts with library dependencies swift and convenient.

Deploying a new instance of a library with every dApp may seem wasteful – common public libraries will be replicated thousands of times across the blockchain, where data storage is at a premium. This is not a problem though, as most of the chaindata size in Ethereum comes from the blockchain itself –that is, transactions and block headers, not code or contract storage.

There’s also a security advantage to deploying new instances of libraries – developers can easily confirm that the deployed library contains the exact code they require.

Embedded Libraries

If the library contains only internal functions, the EVM actually embeds these functions into to the contract, and no separate library instance is deployed to the blockchain.

This allows developers to modularize and re-use internal functions across contracts.

How Libraries are Called – DelegateCall, or Internal Functions

Library code is executed in the context of the calling contract  – let’s say ‘contract A’. It’s as if the library code were running within the contract itself – with msg.sender referring to the caller of contract A, not contract A itself.

This happens via the delegatecall instruction. The Solidity docs explain how it works:

“A contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address.”

As mentioned, this applies unless the library contains only internal functions – then, these get embedded in the contract bytecode at compilation.

Whether the library is deployed or embedded, the result is the same – all library functions are executed as if they were part of the contract.

Examples of Library Usage

Let’s look at a couple of libraries from OpenZeppelin to see how they work.  We’ll look at:

  • Pausable.sol  – a contract that defines pausable functionality – i.e. enables a contract to be paused
  • PauserRole.sol  – a contract that defines the _pausers role
  • Role.sol  – a library that defines the generic Role struct

Roles.sol Library

Roles.sol is a library used for access control in dApps.  It defines a Role datatype: a struct, which maps addresses to booleans. It records whether or not an address has a given role.

library Roles {
  struct Role {
    mapping (address => bool) bearer;
  }

  function add(Role storage role, address account) internal {
    require(account != address(0));
    require(!has(role, account));
    role.bearer[account] = true;
  }

  function remove(Role storage role, address account) internal {
    require(account != address(0));
    require(has(role, account));
    role.bearer[account] = false;
  }

  function has(Role storage role, address account) internal view returns 
    (bool) {
    require(account != address(0));
    return role.bearer[account];
  }
}

Roles.sol also defines add(), remove() and has() functions that take a Role and address as parameters.

The functionality here is clear. We can assign users a role or remove it, and we can check if a user has that role.

The ‘Role’ datatype is generic, and we’ll see how contracts define specific roles using this library.

PauserRole.sol Contract

import "../Roles.sol";

contract PauserRole {
  using Roles for Roles.Role;
  
  event PauserAdded(address indexed account);
  event PauserRemoved(address indexed account);
  
  Roles.Role private _pausers;
  
  constructor () internal {
    _addPauser(msg.sender);
  }

  modifier onlyPauser() {…}
  function isPauser(address account) public view returns (bool) {…}
  function addPauser(address account) public onlyPauser {…}
  function renouncePauser() public {…}
  function _addPauser(address account) internal {…}
  function _removePauser(address account) internal {…}
}



PauserRole.sol is a contract that uses the datatype and functions defined in the Roles.sol library.

The contract has access to Roles.Role, as the file imports the Roles.sol code. A framework like Truffle will take care of the library deployment and linking the library to the contract, as long as we have the library defined/imported into the file containing the contract.

The Roles.Role datatype is available to the deployed PauserRole.sol contract because a Roles.sol instance gets deployed to the chain, referenced at it’s own address, then used via delegateCall.  

The ‘using..for’ Directive

using Roles for Roles.Role

Here, the using..for directive attaches the functions defined in Roles.sol, to the Role type.

Roles.Role private _pausers

This instantiates a new Roles struct called _pausers.

We can then functions from Roles.sol on _pausers.  This is simply syntactic sugar – instead of writing:

Roles.remove(_pausers, addr)

We can do

_pausers.remove(addr)

The PauserRole contract goes on to define specific functions – addPauser(), isPauser(), etc – that take an address parameter.

Note that the body of each of these functions calls a function defined in Roles.sol on the _pausers Role, e.g. _pausers.add().

Contract Inheriting Contract Using Library

With the PausersRole functionality defined, Zeppelin defines a Pausable.sol contract that inherits from the PausersRole contract.

contract Pausable is PausersRole {
...
}

This contract defines the actual functionality for pausing and unpausing a contract.

Pausable.sol inherits the PausersRole.sol functions and the _pauser storage variable through regular contract inheritance.

Developers can use OpenZeppelin templates to import functionality, without re-inventing the wheel or introducing vulnerabilities.

Libraries for Modifying Standard Types

As well as defining custom datatypes, we can use libraries to add functionality to standard Solidity types such as uints, strings and arrays.

One drawback of uints in Solidity is their vulnerability to overflow/underflow bugs.

The Zeppelin library SafeMath.sol is used to augment uints simple arithmetic operations that are immune to overflow/underflow.

In any contract where we want robust arithmetic operators, we can do:

import SafeMath.sol

contract A {
  using SafeMath for uint  
  ...
}

So the uint type now has functions add(), div() and sub()available to it.

We can call add() on a number:

num = num.add(4) 

Which does

num = add(num, 4)

This adds our uints, and guards against overflow for outputs >= 2**256 – the call will revert instead.

Use Libraries for Proven functionality and Keeping Code DRY

Unlike contract inheritance, libraries are deployed separately to the blockchain and accessed via the delegateCall function, with their code executed in the context of the calling contract.

Established libraries like OpenZeppelin help us dApp developers minimize the ‘attack surface’ of our dApp by implementing battle-tested and proven Solidity patterns.

We can also make custom libraries and benefit from modularity and code re-use.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *