Tu primer Smart Contract con Solidity y 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 artifacts, cache y typechain-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í.
Analyst Frontend Developer en Comunytek (BBVA CIB – NOVA).
Autodidacta, apasionado de las nuevas tecnologías y de los proyectos DIY.
Trackbacks/Pingbacks