Tu primer Smart Contract con Solidity y Hardhat

por | Feb 13, 2023 | Blockchain, Hardhat, Solidity | 0 Comentarios

First Smart Contract with Solidity and Hardhat

¿Qué es Solidity?

Solidity es un lenguaje de programación de alto nivel para crear contratos inteligentes en la plataforma Ethereum. Los contratos inteligentes son programas autónomos que se ejecutan en la red Ethereum y pueden ser utilizados para implementar diversos tipos de aplicaciones descentralizadas, como exchanges descentralizados (DEX), juegos, votaciones y mucho más.

Se parece a lenguajes de programación como Javascript, Python o C++, pero tiene algunas características únicas que lo hacen adecuado para la creación de contratos inteligentes. Por ejemplo, tiene una serie de tipos de datos incorporados que permiten la representación de monedas y tokens. Además, permite la creación de funciones y eventos que se activan cuando se ejecutan acciones específicas en la red Ethereum.

¿Qué es Hardhat?

Hardhat es un framework de desarrollo para Ethereum que proporciona herramientas y recursos para construir y probar aplicaciones descentralizadas (dApps) en la red Ethereum. Así, los desarrolladores pueden crear contratos inteligentes y probarlos en un entorno controlado antes de implementarlos en la red principal.

Incluye una red local para probar contratos, una consola de depuración, herramientas de monitoreo de contratos, una biblioteca de contratos predefinidos y una interfaz de línea de comandos para una experiencia de desarrollo ágil. Es una solución gratuita y de código abierto, lo que significa que es accesible para cualquier persona que quiera aprender a construir aplicaciones descentralizadas.

Para comenzar a usar Hardhat, es necesario tener un conocimiento básico de programación en el lenguaje Solidity, que es el lenguaje principal para escribir contratos inteligentes en Ethereum. Una vez que se tiene una comprensión básica de Solidity, es posible instalar Hardhat y comenzar a crear y probar contratos.

Tecnologías

El stack de versiones que se usarán para el desarrollo del proyecto es el siguiente.

  • Node (16.18.1)
  • Npm (9.2.0)
  • Typescript (4.8.4)

Objetivo

Vamos a crear un Smart Contract sencillo que nos permita almacenar nuestro número favorito.

    Setup del proyecto con Hardhat

    Creamos una carpeta nueva llamada FavoriteNumber, y abrimos una terminal dentro de ella. Inicializamos el proyecto.

      npm init
      > package name: favorite-number
      > version: 0.1.0
      > description: Hardhat project with a simple Smart Contract to store your favorite number
      > entry point: (default)
      > test command: (default)
      > git repository: (default)
      > keywords: (default)
      > author: Ivan Gabriel Pajon Rodriguez
      > license: MIT
      ...
      > Is this OK? (default)

      Después de finalizar el asistente vamos a instalar Hardhat.

        npm install --save hardhat

        Inicializamos el proyecto de Hardhat.

          npx hardhat
          ? What do you want to do? ...
          > Create a TypeScript project
           
          > Hardhat project root: (default)
          > Do you want to add a .gitignore? (default)

          Ahora, ya tendremos creada la estructura principal, incluyendo un Smart Contract de ejemplo con sus tests (contracts/Lock.sol y test/Lock.ts) y un script (scripts/deploy.ts).

          Borramos los archivos contracts/Lock.sol, test/Lock.ts y scripts/deploy.ts.

          Si nos fijamos en los logs del comando npx hardhat veremos una indicación para que instalemos las dependencias si queremos poder correr el proyecto. Vamos a ello.

            npm install --save-dev @nomicfoundation/hardhat-toolbox

            Smart Contract

            ¡Por fin! Vamos a comenzar con el desarrollo de nuestro primer contrato inteligente. Para ello, creamos un archivo llamado FavoriteNumber.sol en la carpeta contracts. Hay que saber que todos los contratos inteligentes tienen 3 cosas en común:

            • Definición de la licencia SPDX
            • Versión del compilador de Solidity
            • Definición del propio contracto

            Sabiendo esto, el archivo quedaría así.

            FavoriteNumber.sol

            // SPDX-License-Identifier: MIT
            pragma solidity 0.8.18;
             
            contract FavoriteNumber {
             
            }

            Tipos Primitivos

            Solidity es un lenguaje tipado estáticamente. Existen tipos primitivos y tipos complejos. En este ejemplo solo veremos los primitivos y haremos uso de alguno de ellos.

            • boolean
              • Puede ser true ó false
            • uint
              • Desde 0 hasta 2 ** 256 – 1
            • int
              • Desde -2 ** 255 hasta 2 ** 255 – 1
            • address
              • Cualquier dirección 0x

            Implementación

            Vamos a necesitar una variable privada donde guardar nuestro número favorito. Una función que nos permita cambiarlo. Y otra función que nos devuelva el número favorito almacenado.

            La variable favoriteNumber es prefijada con s_ siguiendo las buenas prácticas de Solidity, que nos indican que así sea cuando la variable está destinada a almacenar un dato y éste puede cambiar.

            La función setFavoriteNumber recibe por parámetro un número y lo guarda en la variable privada s_favoriteNumber. Está marcada como external ya que solo va a ser llamada desde fuera del contrato.

            Y, por último, la función getFavoriteNumber nos devuelve el número favorito almacenado. Está marcada como public view returns (uint) ya que devuelve un número, pero lo lee de la memoria, sin modificar el estado (state) del contrato.

            FavoriteNumber.sol

            // SPDX-License-Identifier: MIT
            pragma solidity 0.8.18;
             
            contract FavoriteNumber {
                uint private s_favoriteNumber;
             
                constructor() { }
             
                function setFavoriteNumber(uint favoriteNumber) external {
                    s_favoriteNumber = favoriteNumber;
                }
             
                function getFavoriteNumber() public view returns (uint) {
                    return s_favoriteNumber;
                }
            }

            ¡Ya lo tenemos! Seguramente haya sido más fácil de lo que te esperabas, pero… ¿Y cómo sabemos que funciona correctamente? 🤨

            Calma, de eso nos encargamos ahora.

            Deploy

            Primero vamos a instalar el plugin hardhat-deploy, el cual nos ayudará a desplegar nuestros contratos inteligentes y testearlos.

            npm install --save-dev hardhat-deploy @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers ethers

            Actualmente hay un conflicto de dependencias en proceso de solucionarse, y puede que salga un error al hacer npm install. Simplemente ejecutamos el comando con el flag –force y listo. Una vez instalado, actualizamos la configuración del proyecto.

            hardhat.config.ts

            import '@nomicfoundation/hardhat-toolbox';
            import 'hardhat-deploy';
            
            import { HardhatUserConfig } from 'hardhat/config';
             
            const config: HardhatUserConfig = {
              solidity: '0.8.18',
              defaultNetwork: 'hardhat',
              // localhost / hardhat -> 'http://127.0.0.1:8545/'
              networks: {
                localhost: {
                  chainId: 31337,
                },
                hardhat: {
                  chainId: 31337,
                },
              },
              // Optional node for hardhat-deploy plugin
              namedAccounts: {
                // Alias
                deployer: {
                  default: 0, // First account by default
                },
              },
            };
             
            export default config;

            Creamos una carpeta llamada deploy en la raíz del proyecto. Dentro de la carpeta creamos un archivo que se llame 01-FavoriteNumber.deploy.ts. Dentro crearemos una función que será la encargada de desplegar nuestro contrato cada vez que sea necesario, de forma automatizada.

            01-FavoriteNumber.deploy.ts

            import { DeployFunction } from "hardhat-deploy/dist/types";
            import { HardhatRuntimeEnvironment } from "hardhat/types";
             
            const deployFn: DeployFunction = async ({
              getNamedAccounts,
              deployments
            }: HardhatRuntimeEnvironment) => {
              // Gets deploy and log functions
              const { deploy, log } = deployments;
              // Gets account of the deployer alias
              const { deployer } = await getNamedAccounts();
             
              log('---------------------------------------------------------------');
             
              // Deploys FavoriteNumber contract
              const favoriteNumber = await deploy('FavoriteNumber', {
                from: deployer,
                args: [],
                log: true,
                waitConfirmations: 1,
              });
              // log(favoriteNumberContract);
             
              log('---------------------------------------------------------------');
            };
             
            // Custom tags to target with --tags flag in hardhat cli
            deployFn.tags = ['all', 'favorite-number'];
             
            // Exports deploy function
            export default deployFn;

            Ya podemos ejecutar el comando para desplegar. Al ser la primera vez que lo ejecutamos, compilará nuestro contrato y generará sus tipos, abi, etc. antes de desplegar. Veremos que nos crea las carpetas artifactscachetypechain-types.

            npx hardhat deploy
            > Generating typings for: 1 artifacts in dir: typechain-types for target: ethers-v5
            > Successfully generated 6 typings!
            > Compiled 1 Solidity file successfully
            > ---------------------------------------------------------------
            > deploying "FavoriteNumber" (tx: 0xFFFF)...: deployed at 0xFFFF with 125641 gas
            > ---------------------------------------------------------------

            Test

            Nos queda el último paso: automatizar el testeo de las diferentes partes de nuestro contrato mediante tests. Para ello, dentro de la carpeta test creamos un archivo llamado FavoriteNumber.test.ts, y escribimos un par de tests sencillos para validar la funcionalidad del contrato.

            FavoriteNumber.test.ts

            import { deployments, ethers, getNamedAccounts, network } from 'hardhat';
            
            import { FavoriteNumber } from '../typechain-types';
            import { assert } from 'chai';
             
            // Only execute tests in local environment
            if (!['hardhat', 'localhost'].includes(network.name)) {
              describe.skip;
            } else {
              describe('FavoriteNumber Unit Tests', () => {
                let favoriteNumberContract: FavoriteNumber;  // FavoriteNumber contract
                let deployer: string;  // Account from alias 'deployer'
             
                beforeEach(async () => {
                  // Gets deployer account
                  deployer = (await getNamedAccounts()).deployer;
             
                  // Executes deploy scripts with given tags to prepare test environment
                  await deployments.fixture(['favorite-number']);
             
                  // Gets FavoriteNumber contract
                  favoriteNumberContract = await ethers.getContract('FavoriteNumber');
                });
             
                it('Initial favorite number by default', async () => {
                  // Gets default favorite number
                  const favoriteNumber = (await favoriteNumberContract.getFavoriteNumber()).toString();
             
                  assert.equal(favoriteNumber, '0');
                });
             
                it('Set favorite number', async () => {
                  // New favorite number
                  const newFavoriteNumber = '10';
             
                  // Updates favorite number
                  await favoriteNumberContract.setFavoriteNumber(newFavoriteNumber);
             
                  // Gets updated favorite number
                  const updatedFavoriteNumber = (await favoriteNumberContract.getFavoriteNumber()).toString();
             
                  assert.equal(newFavoriteNumber, updatedFavoriteNumber);
                });
              });
            }

            Todo listo. Ejecutamos los tests.

            npx hardhat test
             
            >  FavoriteNumber Unit Tests
            >    √ Initial favorite number by default
            >    √ Set favorite number
             
            >  2 passing (1s)

            Si has llegado hasta aquí, ¡enhorabuena! 🥳 Acabas de montar un proyecto con Hardhat, has escrito tu primer Smart Contract con Solidity y lo has testeado en una blockchain local con sus correspondientes tests. Si se te ha quedado corto, en el siguiente post hablamos sobre los custom error.

            Github

            Todo el código fuente lo puedes encontrar aquí.