Estimated time: 5 minutes read

Solidity libraries in smart contract programming optimize gas usage and reduce the size of deployed code, allowing for function reuse.

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.

What are Solidity libraries used for in decentralized applications?

Some use cases would be the following:

  • Gas usage reduction: Suppose a decentralized application that creates a main smart contract for each user. If we extract most of the logic to a library, a lot of gas will be saved during the deployment of the contracts.
  • 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. It is possible to take advantage of the extended capability of libraries to receive mappings as input parameters in external functions to modify the storage value in the 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, 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. 


Do you find the use of Solidity libraries to optimize code interesting? I invite you to leave your comments.

You can also contact me if you are thinking about developing a decentralized application. Thank you!