Tiempo estimado: 10 minutos de lectura

Guía sobre cómo utilizar Hardhat Ignition para desplegar smart contracts en redes compatibles Ethereum. Se analizan distintas herramientas para automatizar el proceso.

¿Cómo desplegar smart contracts con Hardhat?

Ignition es el sistema declarativo de Hardhat para desplegar smart contracts en redes compatibles con Ethereum.

El uso de este componente conlleva la instalación de los siguientes paquetes si aún no se ha hecho anteriormente:

yarn add --dev @nomicfoundation/hardhat-ignition-ethers @nomicfoundation/hardhat-ethers 
                @nomicfoundation/hardhat-ignition @nomicfoundation/ignition-core ethers

Además es preciso añadir al archivo «hardhat-config.js» el siguiente plugin:

require("@nomicfoundation/hardhat-ignition-ethers");

Los despliegues se definen mediante módulos. Cada módulo encapsula un grupo de smart contracts y las operaciones que se efectúan para posibilitar el despliegue global de todo el proyecto.

Los módulos se deben crear en la carpeta «ignition/modules» del proyecto hardhat. Se definen en ficheros javascript. Un fichero típico sería:

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

module.exports = buildModule("moduleID", (m) => {
  const futureName = m.contract("contractName", [args]);  

  return { futureName };
});

La llamada a «buildModule» crea el módulo de Ignition. Se recomienda definir un único módulo por cada fichero JS, es decir una única llamada a «buildModule».

El primer argumento de la llamada a «buildModule» es el «moduleID». Habitualmente coincide con el nombre del fichero javascript en que se define. El segundo argumento es la «callback function», las operaciones a ejecutar en los smart contracts durante el despliegue.

La constante «futureName» representa el concepto de «Future» en Hardhat Ignition. Se trata de un paso de ejecución necesario en el proceso global de despliegue. Es devuelto en la callback function para poderlo usar desde otros módulos.

¿Qué tipos de operaciones se pueden efectuar en los smart contacts durante el despliegue?

Los detalles completos se pueden analizar en la documentación sobre los distintos tipos de futures en Hardhat

Se ilustran algunas de las operaciones más habituales mediante ejemplos:

Despliegue de un contrato con argumentos
// CryptoTaskManager.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

module.exports = buildModule("CryptoTaskManager", (m) => {
  const cryptoTaskManager = m.contract("CryptoTaskManager", [100]);  

  return { cryptoTaskManager };
});

El Future «cryptoTaskManager» representa el despliegue de un contrato al que se le suministra un argumento numérico en su constructor.

Dependencia entre módulos y llamada a función de escritura
// CryptoTaskFactory.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
const CryptoTaskManager = require("./CryptoTaskManager");

module.exports = buildModule("CryptoTaskFactory", (m) => {
    const { cryptoTaskManager } = m.useModule(CryptoTaskManager);
    
    const cryptoTaskFactory = m.contract("CryptoTaskFactoryWrapperVTest", [cryptoTaskManager]);

    m.call(cryptoTaskManager, "setCryptoTaskFactoryImplementation", [cryptoTaskFactory]);  

    return {cryptoTaskFactory};
    
});

El nuevo Future «cryptoTaskFactory» representa el despliegue de un contrato al que se suministra como argumento la dirección del contrato «cryptoTaskManager» procedente del módulo anterior.

Para referenciar Futures exportados de otros módulos se usa la llamada «useModule».

Por último se ilustra la llamada de escritura sobre el método «setCryptoTaskFactoryImplementation», suministrando como argumento la dirección del nuevo contrato recién desplegado.

Despliegue de librerías
// Libraries.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

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

  return { cryptoTaskLibrary };
});

Se crean dos Futures correspondientes a dos librerías. La primera además está vinculada (linked) a la segunda.

En el artículo sobre librerías de Solidity se pueden conocer más detalles sobre el uso de librerías.

Vinculación de librería (linked library) en un contrato
// CryptoTask.js
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 };
    
});

El Future «cryptoTask» representa el despliegue de un contrato suministrando a su constructor la dirección de otro contrato desplegado previamente y vinculando una librería igualmente desplegada en otro módulo.

Dependencias entre los módulos y revisión de pasos

La división del despliegue global del proyecto entre distintos módulos es útil para dividir el proceso en bloques pequeños interrelacionados mediante el mecanismo de la dependencia entre módulos (llamada a «useModule»).

El despliegue total del proyecto se puede efectúar con una única llamada al módulo principal. Para el caso de los ejemplos presentados sería el módulo «CryptoTask» presentado en último lugar.

Antes de proceder al despliegue efectivo en la blockchain es útil revisar qué operaciones se efectuarán. Siguiendo con nuestro ejemplo:

yarn hardhat ignition visualize .\ignition\modules\CryptoTask.js

Como resultado se abre una página html con los pasos necesarios para desplegar los contratos definidos en el módulo, desplegando también todas las dependencias establecidas:

Flujo de despliegue con Ignition

Despliegue del proyecto en la Hardhat Network

Para ilustrar los conceptos de este apartado se usarán los contratos de los módulos de ejemplo y como blockchain la Hardhat Network como proceso de larga duración, por tanto se ejecutará previamente:

yarn hardhat network

El comando de despliegue sería el siguiente:

yarn hardhat ignition deploy .\ignition\modules\CryptoTask.js --network localhost

Se muestra el siguiente flujo por la consola; se observa que se corresponde con los pasos detallados mediante la revisión efectuada al invocar «ignition visualize»:

Al final se recogen los Futures desplegados identificados por su «FutureID» y sus direcciones de red.

Como resultado de la ejecución exitosa se habrá creado en el directorio «ignition/deployments/» una carpeta correspondiente al «deploymentID» corriente. Si no se define de forma explícita mediante el modificador «–deployment-id» será «chain-$chainID». Para la Hardhat Network el identificador de la red (chainID) es el 31337.

Dentro de esa carpeta se ubican los contratos compilados y un fichero «deployed_addresses.json» con las direcciones de los Futures desplegados como se mostraron por la consola.

Se puede revisar el estado del despliegue mediante el comando «ignition status», suministrando el deploymentID:

yarn hardhat ignition status chain-31337

En caso de que suceda algún error, será posible retomar el despliegue por el punto de interrupción una vez se haya enmendado. Basta volver a invocar el comando «ignition deploy» para que Hardhat Ignition lo gestione de forma automática.

También es posible modificar los ficheros de definición de los módulos para volver a ejecutarlos de modo que Hardhat Ignition intente desplegar las diferencias.

En caso de querer descartar alguno de los Futures desplegados hay que invocar el comando «ignition wipe». Por ejemplo para descartar el último contrato desplegado:

yarn hardhat ignition wipe chain-31337 CryptoTask#CryptoTaskV100

Si existe alguna dependencia que deba ser resuelta, recibiremos un mensaje explicativo:

Error en wipe en Hardhat Ignition

Será necesario solventar el error entonces:

yarn hardhat ignition wipe chain-31337 CryptoTask#CryptoTaskManager~CryptoTaskManager.setCryptoTaskImplementation
yarn hardhat ignition wipe chain-31337 CryptoTask#CryptoTaskV100

Como alternativa para reiniciar completamente el despliegue de los módulos se puede eliminar directamente la carpeta «ignition/deployments/chain-$chainID».

Despliegue en una blockchain real

La diferencia respecto del despliegue en la Hardhat Network es la definición de una blockchain real en la invocación del comando. Por ejemplo para la testnet Sepolia:

yarn hardhat ignition deploy .\ignition\modules\CryptoTask.js --network sepolia:

En el fichero «hardhat.config.js» se debe definir la URL del endpoint RPC-JSON del proveedor de acceso a la blockchain y la clave privada del address que se usará para el despliegue (en el ejemplo se usa un fichero «.env» de propiedades):

sepolia: {
      chainId: 11155111,
      url: `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_SEPOLIA_API_KEY}`,
      accounts: [`0x${SEPOLIA_ADDRESS_PRIVATE_KEY}`]
},

Al ejecutar las transacciones sobre la blockchain se monitorizán las confirmaciones producidas hasta llegar al mínimo definido en la configuración, quedando a la espera las transacciones posteriores. Si tras un tiempo máximo de espera no se alcanza el umbral de confirmaciones, la transacción será reintentada aumentando las «fees». Si tras N reintentos no se consigue, el despliegue abortará.

En caso de efectuar alguna operación de escritura durante el despliegue, será simulada previamente mediante «eth_call».

Todas las configuraciones relativas a las transacciones con la blockchain durante el despliegue se recogen como propiedades de configuración de Ignition en el fichero «hardhat.config.js».

Conclusión: despliegue de contratos muy versátil

En este artículo se ha detallado la forma de desplegar contratos en la blockchain mediante la herramienta Hardhat Ignition.

Se trata de un componente que renueva completamente la forma de interactuar con la blockchain mediante el antiguo plugin hardhat-deploy

Entre las ventajas que aporta:

  • La posibilidad de dividir los despliegues entre módulos relacionados que pueden ser ejecutados de forma independiente.
  • La recuperación automática de errores de despliegue, retomando el proceso por el punto de interrupción.
  • La gestión de un repositorio de despliegues efectuados en distintas blockchains con posibilidad de visualizar el estado y las direcciones de los contratos desplegados.
  • La opción de verificar la secuencia de despliegue para validarla antes de su ejecución.

¿Qué herramienta utilizas habitualmente para desplegar tus contratos en la blockchain? Te animo a que compartas tu experiencia a través de los comentarios.

Si estás interesado en desplegar en la blockchain algún contrato, puedo ayudarte. Me dedico al desarrollo de proyectos blockchain, contáctame y vemos cómo podemos colaborar. ¡Muchas gracias!