Skip to content

Latest commit

 

History

History
501 lines (310 loc) · 25.8 KB

Libraries.md

File metadata and controls

501 lines (310 loc) · 25.8 KB

Solidity Tutorial: all about Libraries (edit)

Table of Contents

  1. What is a Library in Solidity ?
  2. How to create a library in Solidity ?
  3. How to deploy a Library in Solidity ?
  4. How to use a Library in a smart contract ?
  5. How to interact with a library in Solidity ?
  6. Understanding functions in libraries.
  7. Events and Libraries
  8. Mappings in Library = more functionalities !
  9. Linking libraries with other libraries
  10. Use modifier in libraries
  11. What you can’t do with library in Solidity ?
  12. Some already available libraries

What I do not understand about libraries in Solidity

References

1. What is a Library in Solidity ?

Libraries can be seen as implicit base contracts of the contracts that use them (Solidity doc)

A library in Solidity is a different type of smart contract that contains reusable code. Once deployed on the blockchain (only once), it is assigned a specific address and its properties / methods can be reused many times by other contracts in the Ethereum network.

They enable to develop in a more modular way. Sometimes, it is helpful to think of a library as a singleton in the EVM, a piece of code that can be called from any contract without the need to deploy it again.

Benefits

However, libraries in Solidity are not only limited to reusability. Here are some of their advantages :

  • Usability : Functions in a library can be used by many contracts. If you have many contracts that have some common code, then you can deploy that common code as a library.
  • Economical : Deploying common code as library will save gas as gas depends on the size of the contract too. Using a base contract instead of a library to split the common code won’t save gas because in Solidity, inheritance works by copying code.
  • Good add-ons : Solidity libraries can be used to add member functions to data types. For instance, think of libraries like the standard libraries or packages that you can use in other programming languages to perform complex math operations on numbers.

Limitations

Libraries in Solidity are considered stateless, and hence have the following restrictions

  • They do not have any storage (so can’t have non-constant state variables)
  • They can’t hold ethers (so can’t have a fallback function)
  • Doesn’t allow payable functions (since they can’t hold ethers)
  • Cannot inherit nor be inherited
  • Can’t be destroyed (no selfdestruct() function since version 0.4.20)

“Ideally, libraries are not meant to change state of contract, it should only be used to perform simple operations based on input and returns result”

However, libraries have the benefits to save substantial amount of gas (and therefore, not contaminating the blockchain with repetitive code), since the same code doesn’t have to be deployed over and over. Different contract on Ethereum can just rely on the same already deployed library.

The fact that multiple contracts depend on the exact piece of code, can make for a more secure environment. Imagine not only having well audited code for common endeavours (like the tremendous job the guys at Zeppelin are doing), but relying on the same deployed library code that other contracts are already using. It would certainly have helped in this case, where all balances of an ERC20 token (nothing too fancy), that was intended to raise a maximum of $50M, were whipped out.


2. How to create a library in Solidity ?

Define a library and variables allowed

You define a library contract with the keyword library instead of the traditional contract keyword used for standard smart contracts. Just declare libary under the pragma solidity statement (compiler version). See our code example below.

pragma solidity ^0.5.0;

library libraryName {

	// struct, enum or constant variable declaration
	// function definition with body  
	
}

As we have seen, libraries contracts do not have storage. Therefore, they can’t hold state variables (state variables that are non-constant). However, libraries can still implement some data type :

  • struct and enum : these are user-defined variables.
  • Any other constant variable (immutable) : constant variables are stored in the stack in Ethereum, not in its storage.

Let’s start with a simple example : a library for mathematical operation. The SafeMath library described below contains basic arithmetic operation which takes 2 unsigned integer as input and returns the arithmetic operation result.

pragma solidity ^0.5.0;

library MathLib {  
      
    struct MathConstant {  
        uint Pi;             // π (Pi) ≈ 3.1415926535...  
        uint Phi;            // Golden ratio ≈ 1.6180339887...  
        uint Tau;            // Tau (2pi) ≈ 6.283185...  
        uint Omega;          // Ω (Omega) ≈ 0.5671432904...  
        uint ImaginaryUnit;  // i (Imaginary Unit) = √–1  
        uint EulerNb;        // Euler number ≈ 2.7182818284590452...  
        uint PythagoraConst; // Pythagora constant (√2) ≈ 1.41421...   
        uint TheodorusConst; // Theodorus constant (√3) ≈ 1.73205...   
    }  
      
}

SafeMath library available in open zeppelin smart contracts collection is a popular solidity library that is used to protect from overflow.


3. How to deploy a Library in Solidity ?

Library deployment is a bit different from regular smart contract deployment. Here are two scenarios :

  1. Embedded Library: If a smart contract is consuming a library which have only internal functions, then the EVM simply embeds library into the contract. Instead of using delegate call to call a function, it simply uses JUMP statement(normal method call). There is no need to separately deploy library in this scenario.
  2. Linked Library : On the flip side, if a library contain public or external functions then library needs to be deployed. The deployment of library will generate a unique address in the blockchain. This address needs to be linked with calling contract.

4. How to use a Library in a smart contract ?

Step 1 : Importing the library

Under the pragma , just specify the following statement :

import LibraryName from “./library-file.sol”;

Your file could also contain multiple libraries. Using curly braces {} in the import statement, you can specify which library you would like to import. Imagine you have a file my-lib.sol like this :

pragma solidity >0.5.0;

library Library1 {  
 // Code from library 1  
}

library Library2 {  
 // Code from library 2  
}

library Library3 {  
 // Code fom library 3  
}

You can specify which library you want to use in your main contract as follow :

pragma solidity ^0.5.0;

// We choose to use only Library 1 and 3 here, and exclude Library 2  
import {Library1, Library3} from "./library-file.sol";

contract MyContract {  
      
    // Your contract code here

}

Step 2 : Using the Library

To use a library within a smart contract, we use the syntax **using** LibraryName **for** Type . This directive can be use to attach library functions (from the library LibraryName) to any type (Type).

  • LibraryName = the name of the library we want to import.
  • Type = the variable type we intend to use the library for. (We can also use the wildcard operator * to attach functions from the library to all types).
// Use the library for unsigned integers  
**using** MathLib **for** uint;

// Use the library for any data type  
**using** MathLib **for** *;

In both previous situations, all functions from the library are attached to the contract, including those where the type of the first parameter does not match the type of the object. The type is checked at the point the function is called and function overload resolution is performed.

By including a library, its data types including library functions are available without having to add further code. When you call a library function, these functions will receive the object they are called on as their first parameter, much like the variable self in Python

import {MathLib} from  './lib-file.sol';

using MathLib for uint;

uint a = 10;  
uint b= 10;

uint c = a.subUint(b);

We could still do uint c = a - b; It will return the same result which is 0. However our library has some added properties to protect from overflow for example; assert(a >= b); which checks to make sure the first operand ais greater than or equal to the second operand b so that the subtraction operation doesn’t result to a negative value.

I don’t understand : The _using A for B;_ directive is active only within the current contract, including within all of its functions, and has no effect outside of the contract in which it is used. The directive may only be used inside a contract, not inside any of its functions.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Another good syntax that makes our code easily understandable is to use the keyword using with a library function as a method of its first parameter. With our MathConstant example, it would be :

using MathLib for MathLib.MathConstant

The using keyword allows for calling functions in MathLib for all functions that take a MathConstant as a first argument, as if they were a method of the struct.

This construct is pretty similar of how you can execute methods on Go structs, without them being fully-fledged objects.


5. How to interact with a library in Solidity ?

Introduction : a bit of theory

If library functions are called, their code is executed in the context of the calling contract

Let’s dive a bit deeper into the Solidity documentation. The doc states the following : “Libraries can be seen as implicit base contracts of the contracts that use them” :

  • They will not be explicitly visible in the inheritance hierarchy…
  • …but calls to library functions look just like calls to functions of explicit base contracts (L.f() if L is the name of the library).

Calling a function from a library will use a special instruction in the EVM: the [DELEGATECALL](https://ethervm.io/#F4) opcode (CALLCODE until Homestead, version 0.4.20). This will cause the calling context to be passed to the library, like if it was some code running in the contract itself. Let’s look at a code example :

pragma solidity ^0.5.0;

library MathLib {  
      
    function mult(uint a, uint b) public view returns (uint, address) {  
        return (a * b, address(this));  
    }  
}

contract Example {  
      
    using MathLib for uint;

address owner = address(this);  
      
    function multEx(uint _a, uint _b) public view returns (uint, address) {  
        return _a.mult(_b);  
    }

}

If we call add() function of the Math library from our contract, the address of **MyContract** will be returned and not the library address. The add() function is compiled as an external call ( DELEGATECALL ) but if we check the address from the address(this) (the contract address), we will see our contract address (not the library contract’s address). The same applies for all other msg properties ( msg.value, msg.data and msg.gas).

Warning : prior to Homestead , _msg.sender_ and _msg.value_ changed ! (because of the use of _CALLCODE_)

Exception : Library functions can only be called directly (without the use of _DELEGATECALL_) if they do not modify the state (_view_ or _pure_ functions)

Furthermore, internalfunctions of libraries are visible in all contracts, just as if the library were a base contract. What ??? Yes that sounds strange. Let’s unpack the process of calling an internal library function, to understand what happen exactly is the following :

  • Contract A calls internal function B from library L.
  • At compile time, the EVM pulls internal function B into contract A. It would be like if function B was hardcoded in contract A.
  • A regular JUMP call will be used instead of a DELEGATECALL.

Calls to internal functions use the same internal calling convention :

  • all internal types can be passed.
  • types stored in memory will be passed by reference and not copied.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — 

Despite the fact libraries do not have storage, they can modify their linked contract’s storage, by passing a storage argument to their function parameters. Hence any modification made by the library does via its function will be saved in the contract’s own storage.

Therefore, the keyword this will point to the calling contract, and refers to the storage of the calling contract to be precise.

Since a library is an isolated piece of code, it can only access state variables of from the storage of the calling contract if they are explicitly supplied. In fact, it would have no way to name them, otherwise. Let’s have a look at a simple example.

// Some code here

We want to use our library functions !

Ok enough theory, let’s look at some practical examples ! :)

To use a library function, we select the variable we want to apply the library function to and add a . followed by the library function name we want to use. Type variable = variable.libraryFunction(Type argument).

Here is an example below.

pragma solidity ^0.5.0;

contract MyContract {  
      
    using MathLib for uint;  
      
    uint256 a = 10;  
    uint256 b= 10;

uint256 c = a.add(b);  
      
}

library MathLib {  
      
    function add(uint256 a, uint256 b) external pure returns (uint256) {  
        uint256 c = a + b;  
        assert(c >= a);  
        return c;  
    }  
      
}

6. Understanding functions in libraries

A library can have functions which are not implemented (like in interfaces). As a result, these type of functions have to be declared as public or external. They can’t be internal or external.

A picture is worth a thousand words !

Here is our internal function implemented in our testConnection() function :

function testConnection(address _from, address _to) internal returns(bool) {  
     if (true) {  
         emit testConnection(_from, _to, connection_success_message);  
         return true;  
     }  
 }

An example of not implemented functions in our situation would be callback functions on connect and disconnect. You would implement your own custom code in the function body. (I am not sure about that, since it’s the role of interfaces)

function onConnect() public;  
function onDisconnect() public;

7. Events and Libraries

In the same way that libraries don’t have storage, they don’t have an event log. However, libraries can dispatch events.

Any event created by the library will be saved in the event log of the contract that calls the event emitting function in the library.

Only problem is, as of right now, the contract ABI does not reflect the events that the libraries it uses may emit. This confuses clients such as web3, that won’t be able to decode what event was called or figure out how to decode its arguments.

We could do a quick hack by defining the event both in the contract and the library (As shown by this article from Gnosis about the DelegateProxy contract). This will trick clients into thinking that it was actually the main contract who sent the event and not the library.

This is our library :

library KombuchaLib {  
    event FilledKombucha(uint amountAdded, uint newFillAmount);  
    event DrankKombucha(uint amountDrank, uint newFillAmount);

// ... other library structs, enums and functions  
}

This is our contract :

contract Kombucha {  
    using KombuchaLib for KombuchaLib.KombuchaStorage;

// we have to repeat the event declarations in the contract  
    // in order for some client-side frameworks to detect them  
    // (otherwise they won't show up in the contract ABI)  
    event FilledKombucha(uint amountAdded, uint newFillAmount);  
    event DrankKombucha(uint amountDrank, uint newFillAmount);

KombuchaLib.KombuchaStorage private self;  
      
    // ... etc ...  
}

// Need some code here


8. Mappings in Library = more functionalities !

Using the mapping type inside libraries differ compared to its usage in traditional Solidity smart contracts. Here we will discuss about using it as a parameter type inside a function

Extended functionality :

  • You can use a mapping as a parameter for any function visibility : public, private, external and internal.

In comparison, mappings can only be passed as a parameter for internal or private functions inside contracts.

Warning : The data location can only be storage if a mapping is passed as function parameters

Since we are using a mapping inside a library, we can’t instantiate it inside the library (remember, libraries can’t hold state variables). Let’s look at a “possible” an example of what it could look like:

pragma solidity ^0.5.0;

library Messenger {

event LogFunctionWithMappingAsInput(address from, address to,       string message);

function sendMessage(address to, mapping (string => string) storage aMapping) public {  
        emit LogFunctionWithMappingAsInput(msg.sender, to, aMapping[“test1”]);  
    }  
}

9. Use Structs in Libraries

library Library3 {  
   
    struct hold{  
        uint a;  
    }  
   
    function subUint(hold storage s, uint b) public view returns(uint){  
   
        // Make sure it doesn’t return a negative value.          
        require(s.a >= b);   
        return s.a — b;  
   
     }  
}

Notice how the function _subUint_ receives a struct as argument. In Solidity v0.4.24, this is not possible in contracts, but possible in Solidity libraries


9. Linking libraries with other libraries

Libraries can be linked with other libraries and use them in the same way a contract would. This come however with the natural limitations of libraries.

// Deep dive here

It is then possible to include libraries inside other libraries, as explained here :

https://ethereum.stackexchange.com/questions/45032/is-it-possible-to-use-a-solidity-library-inside-another


10. Use modifiers in libraries

It is possible to use modifiers in libraries. However, we can’t export them in our contract, because modifiers are compile-time features (a sort of macros).

See this : https://ethereum.stackexchange.com/questions/68529/solidity-modifiers-in-library

Therefore, modifiers only apply to the functions of the library itself, and cannot be used by an external contract of the library.


11. What you can’t do with library in Solidity ?

  • Libraries can’t hold mutable variables. If you try to add a variable that can be modified, you will get the following error in Remix : TypeError: library can't have non-constant state variable
  • Libraries do not have event logs
  • It is not possible to destroy a library.
  • Library can’t inherit.

12. Some already available libraries

Here is a curated list of some already libraries written in Solidity and their authors :


What I do not understand about libraries in Solidity

For library view functions DELEGATECALL is used, because there is no combined DELEGATECALL and STATICCALL. This means library view functions do not have run-time checks that prevent state modifications. This should not impact security negatively because library code is usually known at compile-time and the static checker performs compile-time checks. (Solidity docs)

“One condition which should be taken care is, library functions will receive the object they are called on as their first parameter (like the _self_ variable in Python).”

Note that all library calls are actual EVM function calls. This means that if you pass memory or value types, a copy will be performed, even of the self variable. The only situation where no copy will be performed is when storage reference variables are used.

References