¿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 la aplicación descentralizada?

  • Reducción del uso de gas: en la aplicación descentralizada se crea un smart contract principal por cada tarea, encargado de regular el cumplimiento de los hitos y la asignación de recompensas. La mayor parte de esa lógica se puede extraer a una librería para reutilizar el código desde cada smart contract. De esta forma se ahorra 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á. En la aplicación descentralizada se aprovecha 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?

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. 


¿Has utilizado librerías de Solidity alguna vez en tus desarrollos? Comparte tus experiencias de uso y tus impresiones. ¡Gracias!