Cómo crear tu propio smart contract con Solidity y Hardhat 🔥🚀
Josué Andrés
Posted on May 12, 2023
Introducción
En esta oportunidad, vamos a explorar el potencial de los smart contracts de Ethereum mediante el uso de Solidity y Hardhat como nuestro entorno de desarrollo.
A través de la creación de una pequeña aplicación, desplegaremos en las redes de prueba de Polygon, Avalanche para demostrar las capacidades que nos ofrece el ecosistema de blockchain. Al final del proceso, tendremos una mejor comprensión del alcance y la versatilidad de los smart contracts y su aplicación práctica en el mundo real.
Instalación de dependencias
Para desarrollar nuestro contrato inteligente usaremos Harhat como IDE ya que nos permitirá probarlo dentro de un entorno local antes de llevarlo en una red pruebas o principal. Además de que nos proporciona herramientas de automatización de tareas, como pruebas automatizadas, pruebas de cobertura de código y generación de documentación, lo que nos facilita mucho el desarrollo y depuración de contratos inteligentes.
Además de que se adapta muy fácilmente a proyectos de cualquier tamaño y complejidad.
Para realizar la instalación de Hardhat primero necesitamos Node.js ya que este hace uso de este entorno de ejecución. Si ya cuentas con Node.js omite este paso, de lo contrario es necesario ir al sitio oficial, descargarlo e instalarlo para la plataforma que utilices.
Ahora a instalar Hardhat, para ello debemos seguir los pasos de instalación desde su sitio web.
Una vez instalado deberíamos de ver el siguiente resultado al ejecutar el comando para la creación de un nuevo proyecto:
en mi caso crearé uno basado en JavaScript, pero puedes crearlo basado en TypeScript o vació si es el caso. El proyecto lo llamaré notes_dapp.
Creación de smart contract
Dentro de la carpeta de contracts definiremos el smart contract correspondiente
// notes_dapp/contracts/TaskList.sol// SPDX-License-Identifier: SEE LICENSE IN LICENSEpragmasolidity^0.8.18;contractTaskList{structTask{bytes32description;boolcompleted;}mapping(uint=>Task)publictasks;uintpublictaskCount;functionaddTask(bytes32_description)public{taskCount++;tasks[taskCount]=Task(_description,false);}functioncompleteTask(uint_taskId)public{require(tasks[_taskId].completed==false,"Task is already completed");tasks[_taskId].completed=true;}functiondeleteTask(uint_taskId)public{require(tasks[_taskId].completed==true,"Can only delete completed tasks");deletetasks[_taskId];}functiongetTask(uint_taskId)publicviewreturns(bytes32,bool){return(tasks[_taskId].description,tasks[_taskId].completed);}functiongetTaskCount()publicviewreturns(uint){returntaskCount;}}
Task: Estructura que nos permitirá la descripción de la tarea y el status de la misma
tasks: Map que contendrá todas las tareas que vayamos definiendo a futuro
completeTask: Función que nos permitirá marcar como completada determinada tarea
deleteTask: Nos permitirá eliminar una tarea en especifico
getTask: Obtiene una tarea mediante un id proporcionado, podremos obtener los la descripción y el status de la tarea
getTaskCount: Función que nos permite obtener el numero de tareas
Podremos compilar el smart contract mediante el siguiente comando:
Nos dice que se compilaron dos archivos, esto debido a que por default hardhat genera un smart contract de ejemplo llamado Lock.sol, esto solo para los proyectos que no son generados de forma vacía, el cual fue nuestro caso. Por lo que deberías de ver algo asi en tu carpeta contracts
contracts/
├── Lock.sol
└── TaskList.sol
Tests
Otra parte fundamental para cualquier proyecto de desarrollo de software es el apartado de los tests, estos nos permitirán reducir posibles errores en desarrollos posteriores y como buenos programadores que debemos ser no omitiremos estos de nuestro proyecto.
Por lo que, dentro de la carpeta test agregare el archivo con los tests correspondientes:
// notes_dapp/test/TaskList.jsconst{expect}=require("chai")const{ethers}=require("hardhat")const{loadFixture}=require("@nomicfoundation/hardhat-network-helpers")describe("Task list contract contract",function(){asyncfunctiondeployContract(){constcontract=awaitethers.getContractFactory("TaskList")constdeploy=awaitcontract.deploy()return{deploy}}it("Contract Deploy",asyncfunction(){const{deploy}=awaitloadFixture(deployContract)const_description="test description"constdescription=ethers.utils.formatBytes32String(_description)awaitdeploy.addTask(description)consttaskOne=awaitdeploy.getTask(1)expect(taskOne[0],_description)expect(taskOne[1],false)})it("Test, when add two tasks",asyncfunction(){const{deploy}=awaitloadFixture(deployContract)const_description="test description"const_description2="test description"constdescription=ethers.utils.formatBytes32String(_description)constdescription2=ethers.utils.formatBytes32String(_description2)awaitdeploy.addTask(description)awaitdeploy.addTask(description2)consttaskOne=awaitdeploy.getTask(1)consttaskTwo=awaitdeploy.getTask(2)expect(taskOne[0],_description)expect(taskOne[1],false)expect(taskTwo[0],_description2)expect(taskTwo[1],false)expect(awaitdeploy.getTaskCount()).to.equals(2)})it("Test, set complete task, successfully",asyncfunction(){const{deploy}=awaitloadFixture(deployContract)const_description="test description"constdescription=ethers.utils.formatBytes32String(_description)awaitdeploy.addTask(description)awaitdeploy.completeTask(1)consttaskOne=awaitdeploy.getTask(1)expect(taskOne[1],true)})it("Test, Error trying to complete a completed task",asyncfunction(){const{deploy}=awaitloadFixture(deployContract)const_description="test description"constdescription=ethers.utils.formatBytes32String(_description)awaitdeploy.addTask(description)awaitdeploy.completeTask(1)awaitexpect(deploy.completeTask(1)).to.be.revertedWith("Task is already completed")})it("Test, Delete task",asyncfunction(){const{deploy}=awaitloadFixture(deployContract)const_description="test description"constdescription=ethers.utils.formatBytes32String(_description)awaitdeploy.addTask(description)awaitdeploy.completeTask(1)consttask=awaitdeploy.getTask(1)expect(task[1]).to.equals(true)awaitdeploy.deleteTask(1)})it("Test, Error deleting task",asyncfunction(){const{deploy}=awaitloadFixture(deployContract)const_description="test description"constdescription=ethers.utils.formatBytes32String(_description)awaitdeploy.addTask(description)consttask=awaitdeploy.getTask(1)expect(task[1]).to.equals(false)awaitexpect(deploy.deleteTask(1)).to.be.revertedWith("Can only delete completed tasks")})})
Podemos correr los tests ejecutando:
❯ npx hardhat test
Lock
Deployment
✔ Should set the right unlockTime (2831ms)
✔ Should set the right owner
✔ Should receive and store the funds to lock
✔ Should fail if the unlockTime is not in the future (89ms)
Withdrawals
Validations
✔ Should revert with the right error if called too soon (89ms)
✔ Should revert with the right error if called from another account (45ms)
✔ Shouldn't fail if the unlockTime has arrived and the owner calls it (45ms)
Events
✔ Should emit an event on withdrawals (38ms)
Transfers
✔ Should transfer the funds to the owner (89ms)
Task list contract contract
✔ Contract Deploy (109ms)
✔ Test, when add two tasks (291ms)
✔ Test, set complete task, successfully (44ms)
✔ Test, Error trying to complete a completed task (62ms)
✔ Test, Delete task (60ms)
✔ Test, Error deleting task (70ms)
15 passing (4s)
Puedes eliminar el archivo de test para el smart contract generado por hardhat al iniciar el proyecto, en mi caso no lo hice pero son archivos que no necesitaremos.
Deploy
Una vez generados nuestros tests para los casos de nuestro smart contract crearemos el script de deploy el cual nos servirá para realizar el deploy en las diferentes redes de prueba donde funcionara nuestro contrato.
Dentro de la carpeta scripts/ agregaremos el archivo TaskListDeploy.js con el siguiente contenido
consthre=require("hardhat")asyncfunctionmain(){constNotes=awaithre.ethers.getContractFactory("TaskList")constnote=awaitNotes.deploy()awaitnote.deployed()console.log(`Note dApp deployed on ${note.address}`)}main().catch(error=>{console.log(error)process.exitCode=1})
Por el momento nos aseguraremos de que nuestro contrato sea desplegado en nuestra red de pruebas ya proporcionada por hardhat. Para esto iremos al archivo: hardhat.config.js y agregaremos la red por default.
Hardhat al ser una herramienta completa para el desarrollo blockchain ya nos proporciona una red de pruebas local sin la necesidad de ejecutar alguna otra aplicación que simule ser un nodo.
También nos da la posibilidad de ejecutar el nodo mediante el comando npx hardhat node(HardHat node).
Ahora probaremos que nuestro contrato se despliegue correctamente en la red por defecto que hemos seleccionado:
❯ npx hardhat run scripts/TaskListDeploy.js
Note dApp deployed on 0x5FbDB2315678afecb367f032d93F642f64180aa3
si todo salio bien podremos ver la dirección de despliegue del contrato que en este caso pertenece a una dirección de nuestro nodo de pruebas. Ya que verificamos que funciona el despliegue y que el comportamiento es el que esperamos podremos desplegar nuestro contrato den las diferentes redes que se mencionaron en la introducción.
Polygon
Para poder desplegar nuestro contrato en la red de Polygon necesitamos contar con una billetera de criptomonedas, en mi caso hare uso de Metamask, pero serviría cualquiera que nos permita conectarnos las redes principales y de pruebas de las redes que usaremos.(Guía inicial de Metamask)
Posteriormente necesitaremos agregar la red pruebas de Polygon en nuestra billetera, para ello necesitamos lo siguiente:
Paso siguiente es añadir fondos a nuestra billetera, para ello necesitamos ir al siguiente sitio (https://faucet.polygon.technology/), nos aseguramos de que se encuentre seleccionada la red Mumbai y que el token sea MATIC, colocamos la dirección de nuestra billetera y damos click en submit.
se abrirá una ventana donde confirmaremos la transacción
después de un par de minutos podremos ver 0.2 Matic en nuestra dirección
Ahora agregaremos la red de MATIC a la configuración de hardhat así como las variables de entorno correspondientes:
Ya que esta configurada la red estamos listos para desplegar el contrato mediante el comando:
$ npx hardhat run scripts/TaskListDeploy.js --network mumbai
después de ejecutar el comando deberíamos de tener lo siguiente:
$ npx hardhat run scripts/TaskListDeploy.js --network mumbai
Notes dApp deployed on {tu dirección de contrato}
Para visualizarlo vamos al explorador de mumbai y colocamos la dirección del contrato, se debería de poder visualizar la transacción
Continuaremos con la creación de un script en la carpeta de scripts que nos permitirá leer la información del contrato una vez desplegado en la red de pruebas, agregaremos a nuestro archivo de variables .env la dirección del contrato
posteriormente crearemos el siguiente archivo donde agregaremos lo siguiente
// scripts/TaskList.jsconsthre=require("hardhat");constabi=require("../artifacts/contracts/TaskList.sol/TaskList.json");constCONTRACT_ADDRESS=process.env.CONTRACT_ADDRESS;constMUMBAI_URL=process.env.MUMBAI_URL;constPRIVATE_KEY=process.env.MUMBAI_PRIVATE_KEY;asyncfunctionmain(){constcontractAddress=CONTRACT_ADDRESS;constcontractABI=abi.abi;constprovider=newhre.ethers.providers.JsonRpcProvider(MUMBAI_URL);constsigner=newhre.ethers.Wallet(PRIVATE_KEY,provider);consttaskList=newhre.ethers.Contract(contractAddress,contractABI,signer);// Nos permite agregar nuevas tareas// const description = hre.ethers.utils.formatBytes32String("Deploy de contrato en tesnet");// await taskList.addTask(description);consttasks=awaittaskList.getTaskCount();console.log(`Task number: ${tasks}`);}main().catch((error)=>{console.error(error);process.exitCode=1;});
mediante el script anterior podremos interactuar con nuestro contrato sin necesidad de crear una interfaz web, pero claro el objetivo es que este se comunique con un frontend ;), ahora si ejecutamos el script deberíamos de ver 0 tareas cargadas.
$ npx hardhat run scripts/TaskList.js
Task number: 0
Si se descomenta el código y lo ejecutamos de nuevo estaríamos creando la tarea que especifiquemos, ejecutamos nuevamente el comando anterior
npx hardhat run scripts/TaskList.js
Task number: 0
este nos arrojara que son 0 tareas creadas, pero si ejecutamos nuevamente pero antes comentando el código de creación este nos devolverá ahora el total de una tarea
npx hardhat run scripts/TaskList.js
Task number: 1
y podremos ver la transacción en el explorador de bloques
Podemos agregar la función de marcar como terminada la tarea
// scripts/TaskList.jsconsthre=require("hardhat");constabi=require("../artifacts/contracts/TaskList.sol/TaskList.json");constCONTRACT_ADDRESS=process.env.CONTRACT_ADDRESS;constMUMBAI_URL=process.env.MUMBAI_URL;constPRIVATE_KEY=process.env.MUMBAI_PRIVATE_KEY;asyncfunctionmain(){constcontractAddress=CONTRACT_ADDRESS;constcontractABI=abi.abi;constprovider=newhre.ethers.providers.JsonRpcProvider(MUMBAI_URL);constsigner=newhre.ethers.Wallet(PRIVATE_KEY,provider);consttaskList=newhre.ethers.Contract(contractAddress,contractABI,signer);// Nos permite agregar nuevas tareas// const description = hre.ethers.utils.formatBytes32String("Deploy de contrato en tesnet");// await taskList.addTask(description);+// Permite marcar como terminada una tarea+awaittaskList.completeTask(1);+// Elimina una tarea+// await taskList.deleteTask(1);consttasks=awaittaskList.getTaskCount();console.log(`Task number: ${tasks}`);}main().catch((error)=>{console.error(error);process.exitCode=1;});
si ejecutamos de nuevo tendremos como resultado un numero de tareas de 1 sin embargo podremos ver en el explorador la transacción que marca como completada la tarea
npx hardhat run scripts/TaskList.js
Task number: 1
De la misma forma podemos probar todas las funciones de nuestro contrato, y asi es como finalizamos con el despliegue de contratos inteligentes en Polygon, ahora aprenderás a hacerlo en la red de Avalanche.
Avalanche
Para el despliegue del contrato en la red de Avalanche necesitamos configurar la información de la red en el archivo de configuración de hardhat hardhat.config.js
Ahora necesitamos agregar fondos a nuestra billetera para poder pagar las tarifas de gas al momento de desplegar el contrato, para ello vamos al siguiente sitio https://faucet.avax.network/, colocaremos la dirección de billetera y pediremos monedas de la red de pruebas de Avalanche
Una vez que los fondos se encuentren en nuestra billetera podremos desplegar el contrato
$ npx hardhat run scripts/TaskListDeploy.js --network fuji
Notes dApp deployed on {tu dirección de contrato}
para poder visualizar el contrato desplegado vamos a snowtrace colocamos en el buscador la dirección del contrato y podremos visualizarlo ya desplegado en la blockchain de pruebas de Avalanche
Para probar el contrato comentaremos las credenciales que pertenecían a la red de Polygon y colocaremos las correspondientes a la red de Avalanche
consthre=require("hardhat");constabi=require("../artifacts/contracts/TaskList.sol/TaskList.json");constCONTRACT_ADDRESS=process.env.CONTRACT_ADDRESS;constMUMBAI_URL=process.env.MUMBAI_URL;-constPRIVATE_KEY=process.env.MUMBAI_PRIVATE_KEY;+constMUMBAI_PRIVATE_KEY=process.env.MUMBAI_PRIVATE_KEY;+constFUJI_URL=process.env.FUJI_URL;+constFUJI_PRIVATE_KEY=process.env.FUJI_PRIVATE_KEY;asyncfunctionmain(){constcontractAddress=CONTRACT_ADDRESS;constcontractABI=abi.abi;// const provider = new hre.ethers.providers.EtherscanProvider("maticmum", process.env.MATIC_PRIVATE_KEY);-constprovider=newhre.ethers.providers.JsonRpcProvider(MUMBAI_URL);+constprovider=newhre.ethers.providers.JsonRpcProvider(FUJI_URL);-constsigner=newhre.ethers.Wallet(PRIVATE_KEY,provider);+constsigner=newhre.ethers.Wallet(FUJI_PRIVATE_KEY,provider);consttaskList=newhre.ethers.Contract(contractAddress,contractABI,signer);// Nos permite agregar nuevas tareasconstdescription=hre.ethers.utils.formatBytes32String("Deploy de contrato en tesnet");awaittaskList.addTask(description);// Permite marcar como terminada una tarea// await taskList.completeTask(1);// Elimina una tarea//await taskList.deleteTask(1);consttasks=awaittaskList.getTaskCount();console.log(`Task number: ${tasks}`);}main().catch((error)=>{console.error(error);process.exitCode=1;});
por último solo probaremos la creación y actualización a completado de nuestra tarea
npx hardhat run scripts/TaskList.js
Task number: 0
nos dirá que nuestro total de tareas son 0, pero si vamos al explorador de bloques veremos la transacción, por último probaremos la actualización de la tarea comentando el código de creación y se descomenta la función completeTask, ejecutaremos de nuevo
npx hardhat run scripts/TaskList.js
Task number: 1
veremos que el número de tareas es 1, y en el explorador de bloques podremos visualizar la transacción
de esta forma ya contamos con un smart contract funcional que podríamos llevar a las redes principales de las cadenas de bloques, para ello solo basta con agregar las direcciones correspondientes a su respectiva mainnet y se configuran en el file hardhat.config.js así como en el archivo .env colocar las URL rpc correspondientes.
Conclusiones
Como se podemos observar el uso de hardhat como entorno de desarrollo es sumamente potente, ya que nos permite realizar pruebas y testing de forma local con todas las herramientas necesarias ya integradas, ahorrandonos mucho tiempo en instalar software extra.
Por otro lado las redes de Polygon y Avalanche son muy económicas para el despliegue de contratos inteligentes dentro de su mainnet y en cuanto a pruebas estas te proporcionan todo un entorno muy completo para que puedas validar tus desarrollos, en lo personal estas dos redes son las que más me han agradado hasta el momento.
Si llegaste hasta aquí espero que el tutorial te haya servido tanto si eres nuevo en el mundo de la blockchain o si ya tienes experiencia, y que esta tecnología te parezca tan interesante como a mi, seguiré agregando posts relacionados a blockchain y sobre otro temas relacionados, así que no te pierdas de novedades.
Puedes encontrar más posts como este en mi sitio web: LiteralCoder
Esta es una Dapp (aplicación descentralizada) de notas construida con Hardhat y Ethereum. Esta aplicación permite a los usuarios crear, leer, actualizar y eliminar notas. La Dapp está conectada a dos redes blockchain diferentes: Polygon y Avalanche.
Funcionalidades
La Dapp de Notas tiene las siguientes funcionalidades:
Crear notas: los usuarios pueden crear nuevas notas ingresando un contenido
Numero de notas: los usuarios pueden ver el numero de notas creadas
Actualizar notas: los usuarios marcar como completada una nota en especifico
Eliminar notas: los usuarios pueden eliminar una nota existente.
Tecnologías utilizadas
Hardhat: una herramienta de desarrollo de Ethereum que permite compilar, probar y desplegar contratos inteligentes
React: una biblioteca de JavaScript para construir interfaces de usuario
ethers.js: una biblioteca de JavaScript que permite interactuar con contratos inteligentes de Ethereum.
Polygon: una red blockchain que permite transacciones rápidas y baratas.
Avalanche: una red blockchain escalable y de alta…