Librerías en Solidity: Simplifica y optimiza


Cuando desarrollamos smart contracts siempre buscamos la forma de optimizar y reducir los costos de gas. Una de las formas más simples de lograr esto son las librerías. Si bien pueden parecer similares a los contratos convencionales, las librerías están diseñadas específicamente para ser reutilizadas en varios contratos, sin tener su propio almacenamiento ni estado.

¿Qué son las librerías en Solidity?

Las librerías en Solidity son contratos especiales diseñados para contener funciones reutilizables. A diferencia de los contratos inteligentes tradicionales, las librerías no tienen almacenamiento propio ni pueden recibir ether. Actúan como piezas de código que pueden ser llamadas por otros contratos sin necesidad de desplegarlas repetidamente. Esta característica las convierte en una solución ideal para encapsular funciones comunes y reutilizables.

Ventajas de las librerías

  • Reutilización de código: Las funciones dentro de una librería pueden ser usadas por múltiples contratos. Esto evita la duplicación de código y facilita el mantenimiento.
  • Eficiencia de gas: Al evitar desplegar la librería, se ahorra en costos de gas, dado que el tamaño del contrato afecta directamente el costo de despliegue.

Limitaciones

Las librerías no tiene estado por lo que presentan las siguientes restricciones:

  • No tienen almacenamiento, por lo que no pueden tener variables de estado que no sean constantes.
  • No pueden almacenar ethers, lo que significa que no pueden tener funciones de fallback.
  • No se permiten funciones payable ya que no pueden manejar ethers.
  • No pueden heredar ni ser heredadas.
  • No pueden emitir eventos.
  • No se pueden destruir, ya que no disponen de la función selfdestruct() desde la versión 0.4.20.

Creación de una librería en Solidity

Crear una librería en Solidity es muy sencillo. En lugar de utilizar la palabra clave contract, se usa la palabra clave library.

pragma solidity ^0.8.17;

library Factorial {
 function factorial(uint n) public pure returns (uint) {
  uint result = 1;
  for (uint i = 1; i <= n; i++) {
   result *= i;
  }
  return result;
 }
}

Como hemos visto, los contratos de librerías no tienen almacenamiento, por lo que no pueden tener variables de estado que no sean constantes. Sin embargo, las librerías pueden implementar ciertos tipos de datos:

  • Structs y enums: Estos son tipos de datos definidos por el usuario.
  • Variables constantes (immutable): Las variables constantes, que se almacenan en el bytecode del contrato en lugar de en el almacenamiento, también son permitidas.

Usar una librería en un Contrato

Para utilizar una librería dentro de un contrato, primero se debe importar la librería y luego aplicar el modificador using para hacer disponibles las funciones de la librería en el contexto del contrato:

import "./Factorial.sol";

contract FactorialCalculator {
 using Factorial for uint;

 uint public factorial;

 constructor(uint n) public {
  factorial = n.factorial();
 }
}

Aquí, la librería Factorial es importada y usada en el contrato FactorialCalculator. El modificador using Factorial for uint indica que la función factorial de la librería puede ser llamada directamente sobre cualquier variable de tipo uint dentro del contrato.

También puede llamarse a una librería de esta forma:

import "./Factorial.sol";

contract FactorialCalculator {
 
 uint public factorial;

 constructor(uint n) {
  factorial = Factorial.factorial(n);
 }
}

Despliegue de librerías

El despliegue de librerías puede realizarse de dos maneras:

  • Librería Embebida: Si una librería contiene solo funciones internas, su bytecode se incrusta directamente en el contrato que la utiliza. Esto evita llamadas externas y reduce costos de gas, ya que el código se ejecuta dentro del contexto del contrato llamante. La desventaja es que el tamaño del contrato aumenta.
  • Librería Vinculada: Si una librería contiene funciones públicas o externas, debe ser desplegada por separado en la blockchain. Luego, los contratos que quieran utilizar la librería deben estar vinculados a la dirección específica donde la librería fue desplegada. Esto se realiza mediante el uso de delegatecall, lo que permite que el código de la librería se ejecute en el contexto del contrato llamante.

Para vincular una librería con un contrato, tienes que vincularlo cuando se despliega el contrato, para evitar que el bytecode de la librería se incluya.

const library = await (await ethers.getContractFactory('Factorial')).deploy()
await library.waitForDeployment()
const factorial = await (await ethers.getContractFactory(
   'FactorialCalculator', 
   { libraries: { 'Factorial': library }}
 )).deploy(3)
await factorial.waitForDeployment()

Conclusion

Las librerías en Solidity nos permite crear contratos inteligentes más modulares, eficientes y fáciles de mantener. Desde la reducción de costos de gas hasta la simplificación de operaciones complejas, las librerías te permitirán escalar tus proyectos sin comprometer la calidad o la seguridad.