What is a Solidity library?

A Solidity library is a set of functions deployed on the blockchain as a special type of smart contract to be reused by other libraries and conventional contracts. Its main limitations include the inability to declare state variables and the inability to store ethers or define ‘payable’ functions, due to being shared code.

A comprehensive analysis can be read in this article on the fundamentals of libraries in Solidity.

The official reference can be found on the Solidity language page.

How are Solidity libraries used in the decentralized application?

  • Gas usage reduction: In the decentralized application, a main smart contract is created for each task, responsible for managing milestone completion and reward allocation. Most of this logic can be extracted into a library for code reuse from each smart contract. This significantly saves gas during contract deployment.
  • Safeguarding the maximum gas limit in contract deployment: When a contract’s code is so extensive that it exceeds the maximum allowable limit of 24,576 kilobytes derived from EIP170, an option is to split it among several contracts or libraries.
  • Modifying state variables of calling contracts: While the ideal use of a library should be to execute functions that always return the same output results from the same input parameters, they offer versatility beyond that. In decentralized applications, the extended possibility offered by libraries is used to receive mappings as input parameters in external functions to modify the value of storage in calling contracts.
  • Reverting parts of a transaction: The external function developed in the library could revert with an error, undoing changes in the Ethereum transaction, but the calling contract could catch the error to properly finalize the transaction, signaling the occurrence of the error. This is illustrated with the following code snippets:
library TokenManagerLibraryV100  {
        
    using SafeERC20 for IERC20;

    /**
    * @dev Tries to transfer the amount of tokens at tokenAddress from calling 
    contract to receiver address.   
    * Throws if transfer opertation fails.
    */
    function transfer(uint8 tokenType, address tokenAddress, uint256 tokenId, 
    uint256 amount, address receiver) external {

        if(tokenType == uint8(TokenType.ERC20))   
            // SafeERC20 method is called. 
            IERC20(tokenAddress).safeTransfer(receiver, amount);             
        else if(tokenType == uint8(TokenType.ERC1155)) 
            IERC1155(tokenAddress).safeTransferFrom(address(this), receiver, 
            tokenId, amount, ""); 
        else if(tokenType == uint8(TokenType.ERC721))        
            IERC721(tokenAddress).transferFrom(address(this), receiver, tokenId);  
    }
}

import "./TokenManagerLibraryV100.sol";

contract CallingContract {

    /**
    * @dev Calls library function to transfer balance. Catches exception so that 
    transaction always succeeds
    */
    function transferBalance(Asset calldata asset, Reward calldata reward, address 
    receiver) external returns (TransferStatus memory status) {
            
        // Transfers balance
        try TokenManagerLibraryV100.transfer(uint8(asset.tokenType), 
            asset.tokenAddress, asset.tokenId, reward.amount, receiver) {
            status = TransferStatus(reward.amount, STATUS_SUCCESS); 
        }
        catch Error(string memory revertReason) {
            status = TransferStatus(0, string.concat("Error transfering 
            asset:",revertReason));                                
        }
        catch {                
            status = TransferStatus(0, "Error transfering asset");
        }
    } 
}

How are Solidity libraries deployed in the decentralized application?

In the decentralized application, Hardhat is used as the development environment for smart contracts. The decision has been made to employ the Hardhat Ignition tool as the mechanism for deploying contracts on the network.

Below is an illustration of the deployment of two libraries: the “CryptoTaskLibraryV100” library, in turn, utilizes the “TokenManagerLibraryV100” library, while the contract “CryptoTaskV100” depends on the “CryptoTaskLibraryV100” library.

const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

module.exports = buildModule("LibraryModule", (m) => {
    const tokenManagerLibrary = m.library("TokenManagerLibraryV100");
    const cryptoTaskLibrary = m.library("CryptoTaskLibraryV100", {
        libraries: {
            TokenManagerLibraryV100: tokenManagerLibrary,
        },
    });  

  return { cryptoTaskLibrary };
});
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
const CryptoTaskManager = require("./CryptoTaskManager");
const Libraries = require("./Libraries");

module.exports = buildModule("CryptoTask", (m) => {
    const { cryptoTaskManager } = m.useModule(CryptoTaskManager);
    const { cryptoTaskLibrary } = m.useModule(Libraries);
    const cryptoTask = m.contract("CryptoTaskV100", [cryptoTaskManager], {
        libraries: {
            CryptoTaskLibraryV100: cryptoTaskLibrary,
        },
    });  
    
    m.call(cryptoTaskManager, "setCryptoTaskImplementation", [cryptoTask]);  

    return { cryptoTask };
    
});

Known Issues

During the development of the libraries, an unresolved error has been detected in the Solidity compiler related to the use of enumerated types in libraries: the ABI generated by the compiler is incorrect if the library functions define parameters of type “enum.” To address this, it is necessary to perform explicit conversions to integer types.

This is illustrated in the “transfer” function of the code snippet from the TokenManagerLibraryV100 library. The parameter “tokenType” cannot be declared as an enumerated type TokenType; explicit conversion to the uint8 type is necessary.

Conclusion: Modularizing the Code

Solidity libraries are a very useful mechanism for structuring the logic of smart contracts into reusable code units, also contributing to the reduction of gas usage during the deployment phase.

However, it is necessary to consider their limitations and the slight increase in gas cost when executing their external functions from the calling smart contracts. 


Have you ever used Solidity libraries in your developments? Share your usage experiences and impressions. Thank you!