Tiempo estimado: 5 minutos de lectura

Las librerías de Solidity en la programación de smart contracts optimizan el uso de gas y reducen el tamaño del código desplegado, permitiendo además la reutilización de funciones.

¿Qué es una librería de Solidity?

Una librería de Solidity es un conjunto de funciones desplegadas en la blockchain como un tipo de smart contract especial para ser reutilizadas por otras librerías y por otros contratos convencionales. Sus principales limitaciones son la imposibilidad de declarar variables de estado y no poder almacenar ethers ni definir funciones «payable», al tratarse de código compartido.

Un análisis completo se puede leer en este artículo sobre los fundamentos de las librerías en Solidity

La referencia oficial se puede consultar en la página del lenguaje Solidity.

¿Para qué se utilizan las librerías de Solidity en las aplicaciones descentralizadas?

Algunos casos de uso serían estos:

  • Reducción del uso de gas: supongamos una aplicación descentralizada que crea un smart contract principal por cada usuario. Si extraemos la mayor parte de la lógica a una librería se ahorrará mucho gas durante el despliegue de los contratos.
  • Salvaguardar el límite máximo de gas en el despliegue de un contrato: cuando el código de un contrato es tan extenso que supera el máximo admisible de 24.576 kilobytes derivado de la EIP170, una opción es dividirlo entre varios contratos o librerías.
  • Modificar las variables de estado de los contratos llamantes: si bien el uso ideal de una librería debería ser ejecutar funciones que devuelvan siempre los mismos resultados de salida a partir de los mismos parámetros de entrada, estas ofrecen una versatilidad que va mucho más allá. Se puede aprovechar la posibilidad extendida que presentan las librerías para recibir mappings como parámetros de entrada en funciones externas para modificar el valor del almacenamiento en los contratos llamantes.
  • Revertir partes de una transacción: la función externa desarrollada en la librería podría revertir con error, deshaciendo los cambios en la transacción Ethereum, pero el contrato llamante podría capturar el error para finalizar correctamente la transacción señalizando la ocurrencia del error. Se ilustra con los siguientes fragmentos de código:
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");
        }
    } 
}

¿Cómo se despliegan las librerías de Solidity?

En la aplicación descentralizada se utiliza Hardhat como entorno de desarrollo para los smart contracts. Se ha optado por emplear la herramienta Hardhat Ignition como mecanismo de despliegue de los contratos en la red.

A continuación se ilustra el despliegue de dos librerías: la librería «CryptoTaskLibraryV100» utiliza a su vez  la librería «TokenManagerLibraryV100» mientras que el contrato «CryptoTaskV100» depende de la librería  «CryptoTaskLibraryV100».

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 };
    
});

Errores conocidos

Durante el desarrollo de las librerías se ha detectado un error no solventado en el compilador de Solidity relacionado con el uso de tipos enumerados en las librerías: el ABI generado por el compilador no es correcto en caso de que las funciones de la librería definan parámetros de tipo «enum». Para solventarlo es necesario efectuar conversiones explícitas a tipos enteros.

Se ilustra en la función «transfer» del fragmento de código de la librería TokenManagerLibraryV100. El parámetro «tokenType» no puede ser declarado como enumerado TokenType, es necesario aplicar conversión explícita al tipo uint8.

Conclusión: modularizando el código

Las librerías de Solidity son un mecanismo muy útil para estructurar la lógica de los smart contracts en unidades de código reutilizables, contribuyendo además a la reducción del uso de gas durante la fase de despliegue. 

Por contra es preciso tener en cuenta sus limitaciones y el ligero aumento de gasto en gas al ejecutar sus funciones externas desde los smart contracts llamantes. 


¿Te parece un uso interesante de las librerías de Solidity para optimizar el código? Te invito a dejar tus comentarios.

También puedes contactarme si estás pensando desarrollar una aplicación descentralizada. ¡Gracias!