WEB3DEV Español

Cover image for Almacenamiento simple de Contratos Inteligentes en el Blockchain Avalanche con Solidity, TypeScript, EVM, HardHat y EtherJs
Hector
Hector

Posted on

Almacenamiento simple de Contratos Inteligentes en el Blockchain Avalanche con Solidity, TypeScript, EVM, HardHat y EtherJs

Este artículo es una traducción de Nawodya Ishan, hecha por Héctor Botero. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_eshttps://twitter.com/web3dev_es en Twitter.

Introducción

Avalanche es una nueva plataforma de blockchain, relativamente nueva, que apunta a resolver algunos de los problemas de escalabilidad y eficiencia que otras redes de blockchain se enfrentan, como Bitcoin y Ethereum. Fue creado por un equipo de científicos de la computación, dirigido por Emin Gün Sirer, una figura pública muy conocido en la comunidad del blockchain.

Avalanche usa un novedoso mecanismo de consenso llamado “Avalanche consensus” el cual, permite una alta tasa de transferencia efectiva, baja latencia y una alta firmeza en la transacción. A diferencia de los algoritmos de consenso Proof-of-Work (Prueba de Trabajo) y el Proof-of-Stake (Prueba de Participación), Avalanche se basa en una muestra aleatoria de la red de participantes para, rápidamente, llegar a un consenso en el estado de la red.

Una de las características claves de Avalanche, es su capacidad para soportar múltiples subredes o “blockchains dentro de un blockchain”. Esto quiere decir que diferentes grupos de usuarios pueden crear sus propias subredes con sus propias reglas y sus estructuras de gobernanza mientras siguen siendo capaces de interactuar con la larga red de Avalanche.

Otra característica notable es que, Avalanche es su soporte para la creación de contratos inteligentes usando el idioma de programación de Solidity el cual, es también usado por Ethereum. Esto permite a los desarrolladores a construir aplicaciones descentralizadas (dApps) en la plataforma de Avalanche con herramientas y lenguajes familiares.

En general, Avalanche es una plataforma de blockchain prometedora que ofrece mejores significantes en escalabilidad, velocidad y flexibilidad sobre otras redes existentes de blockchain. Su mecanismo de consenso único y el soporte a múltiples subredes, hace que sea una opción interesante para los desarrolladores y para los usuarios que buscan construir aplicaciones descentralizadas o participar en un ecosistema rápido y eficiente de blockchain.

Pasos

Prerequisitos

Antes de comenzar, asegúrate tener las siguientes herramientas instaladas:

  1. Node.js (v14+)
  2. Npm (v7+)
  3. HardHat (v2+) 4.Solidity (v0.8+)

Primer Paso: Configurando el proyecto

Crea un nuevo directorio para el proyecto y navega en él:

mkdir simple-storage
cd simple-storage
Enter fullscreen mode Exit fullscreen mode

Inicializa un nuevo proyecto Node.js e instala las dependencias requeridas:

npm init -y
npm install --save-dev hardhat @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers typescript ts-node dotenv @types/dotenv @types/mocha @types/chai avalanche
Enter fullscreen mode Exit fullscreen mode

Inicializa HardHat:

npx hardhat
Enter fullscreen mode Exit fullscreen mode

Elige "Create an empty hardhat.config.js" cuando te salga la opción.

Segundo Paso: Configura TypeScript y HardHat

Crea un nuevo archivo tsconfig.json en el directorio principal del proyecto con el siguiente contenido:

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "dist"
  },
  "include": ["./scripts", "./test"],
  "files": ["./hardhat.config.ts"]
}
Enter fullscreen mode Exit fullscreen mode

Renombra hardhat.config.js a hardhat.config.ts y actualiza su contenido:

import { HardhatUserConfig } from 'hardhat/config';
import '@nomiclabs/hardhat-waffle';
import '@nomiclabs/hardhat-ethers';
import 'dotenv/config';

const config: HardhatUserConfig = {
  solidity: '0.8.0',
  networks: {
    hardhat: {
      chainId: 31337,
    },
  },
  mocha: {
    timeout: 20000,
  },
};

export default config;
Enter fullscreen mode Exit fullscreen mode

Tercer Paso: Creando el Contrato de Almacenamiento Simple

Crea un nuevo directorio contracts y nuevo archivo de Solidity SimpleStorage.Sol dentro de él:

mkdir contracts
touch contracts/SimpleStorage.sol
Enter fullscreen mode Exit fullscreen mode

Añade el siguiente código a SimpleStorage.Sol:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract SimpleStorage {
  uint256 private _storedData;

  function set(uint256 value) public {
    _storedData = value;
  }

  function get() public view returns (uint256) {
    return _storedData;
  }
}
Enter fullscreen mode Exit fullscreen mode

Vistazo

El Contrato de Almacenamiento simple está diseñado para demostrar las funciones básicas de un contrato inteligente, permitiéndole a los usuarios almacenar y retirar datos del blockchain de Ethereum. El contrato consiste en un solo estado variable que mantiene un número entero sin firmar y dos funciones para configurar y obtener los valores almacenados.

Estados Variables

El contrato tiene un estado variable:

  • storedData: este número entero sin firmar (uint256) mantiene los datos almacenados. Es el lugar central para almacenar los datos del contrato y su valor puede ser modificado usando la función set() y ser retirado usando la función get().

Funciones

El contrato tiene dos funciones principales:

**set()**

Esta función permite a los usuarios actualizar el valor de la variable de estado storedData. Toma un simple parámetro de entrada, un valor uint256 el cual representa los nuevos datos a ser almacenados.

function set(uint256 x) public {
    storedData = x;
}
Enter fullscreen mode Exit fullscreen mode

Cuando se invoca, esta función actualiza los estados variables de storedData con el valor de entrada ingresado.

**get()**

Esta función permite a los usuarios retirar el valor actual del estado variable de storedData. No toma parámetros de entrada y regresa el valor de uint256 almacenado en storedData.

function get() public view returns (uint256) {
    return storedData;
}
Enter fullscreen mode Exit fullscreen mode

Cuando se invoca, esta función regresa el valor actual al estado variable de storedData, sin modificarlo.

Flujo de Interacción

Los siguientes pasos describen el flujo de interacción típica para los usuarios que interactúan con el contrato de almacenamiento simple:

  1. Despliega el contrato: el usuario despliega el contrato de almacenamiento simple al blockchain de Ethereum. Al desplegarlo, el estado variable de storedData es inicializado con el valor de 0.
  2. Configura los datos almacenados: el usuario llama la función set(), proveyendo el valor uint256 como entrada. La función actualiza el estado variable de storedData con el valor proveído.
  3. Obtén los datos almacenados: el usuario llama la función get() para retirar el valor actual del estado variable de storedData. La función regresa el valor de uint256 sin modificarlo.

Conclusión

El contrato inteligente de almacenamiento simple demuestra la funcionalidad básica de un contrato inteligente en la blockchain de Ethereum. Le permite a los usuarios almacenar y retirar el valor de los números enteros sin firmar, a través de dos funciones simples: set() y get(). Este contrato sirve como un punto inicial para entender los fundamentos del desarrollo de un contrato inteligente y puede ser expandido para implementar características más complejas y casos de uso.

Cuarto Paso: Escribiendo pruebas para el contrato

Crea un directorio nuevo test y un nuevo archivo TypeScript SimpleStorage.test.ts dentro de él:

mkdir test
touch test/SimpleStorage.test.ts
Enter fullscreen mode Exit fullscreen mode

Añade el siguiente código a SimpleStorage.test.ts:

// @ts-ignore
import { ethers, waffle } from "hardhat";
import { Contract } from "ethers";
import { expect } from "chai";

describe("SimpleStorage", () => {
    let simpleStorage: Contract;

    beforeEach(async () => {
        const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
        simpleStorage = await SimpleStorage.deploy();
        await simpleStorage.deployed();
    });

    it("Initial stored data should be 0", async () => {
        const storedData = await simpleStorage.get();
        expect(storedData.toString()).to.equal("0");
    });

    it("Should set stored data to a new value", async () => {
        await simpleStorage.set(42);
        const storedData = await simpleStorage.get();
        expect(storedData.toString()).to.equal("42");
    });
});
Enter fullscreen mode Exit fullscreen mode

Quinto Paso: Compila el contrato y ejecuta pruebas

Compila el contrato usando HardHat:

npx hardhat compile
Enter fullscreen mode Exit fullscreen mode

Ejecuta las pruebas:

npx hardhat test
Enter fullscreen mode Exit fullscreen mode

Si todo está configurado correctamente, deberías ver que las pruebas pasaron.

Sexto Paso: Despliega el contrato en una red local

Crea un directorio nuevo scripts y nuevo archivo TypeScripts deploy.ts dentro de él:

mkdir scripts
touch scripts/deploy.ts
Enter fullscreen mode Exit fullscreen mode

Añade el siguiente código a deploy.ts

// @ts-ignore
import { ethers } from "hardhat";

async function main() {
    const SimpleStorageFactory = await ethers.getContractFactory("SimpleStorage");
    const simpleStorage = await SimpleStorageFactory.deploy();
    await simpleStorage.deployed();

    console.log("SimpleStorage deployed to:", simpleStorage.address);
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });
Enter fullscreen mode Exit fullscreen mode

Ejecuta el script para desplegar:

npx hardhat run scripts/deploy.ts
Enter fullscreen mode Exit fullscreen mode

Deberías ver el contrato desplegado en una dirección local.

Séptimo Paso: Despliega el contrato en la testnet de Avalanche usando Ether.js

Para desplegar el contrato en la red de prueba de Avalanche, necesitas una cuenta de prueba con AVAX. Puedes obtener alguna desde Avalanche Faucet.

Añade la siguiente configuración a tu hardhat.config.ts:

import { HardhatUserConfig } from 'hardhat/config';
import '@nomiclabs/hardhat-waffle';
import '@nomiclabs/hardhat-ethers'; // Add this line
import 'dotenv/config';

const config: HardhatUserConfig = {
  solidity: '0.8.0',
  networks: {
    hardhat: {
      chainId: 31337,
    },
    fuji: {
      url: "https://api.avax-test.network/ext/bc/C/rpc",
      chainId: 43113,
      gasPrice: 225000000000,
      accounts: [process.env.PRIVATE_KEY || ""],
    },
  },
  mocha: {
    timeout: 20000,
  },
};

export default config;
Enter fullscreen mode Exit fullscreen mode

Asegúrate de reemplazar la PRIVATE_KEY en el array accounts con tu clave privada.

Ejecuta el script de despliegue en la red de prueba de Avalanche:

npx hardhat run scripts/deploy.ts --network fuji
Enter fullscreen mode Exit fullscreen mode

Deberías ver el contrato desplegado en la dirección de la red de pruebas de Avalanche.

Ahora haz creado, exitosamente, un contrato de almacenamiento simple en Solidity con TypeScript, EVM y AvalanceJS, junto a las pruebas locales y despliegues de HardHat.

Octavo Paso: Interactuando con el contrato usando AvalancheJS

Para interactuar con el contrato desplegado, crea un nuevo archivo TypeScript interact;ts dentro del directorio scripts:

touch scripts/interact.ts
Enter fullscreen mode Exit fullscreen mode

Añade el siguiente código a interact.ts:

import {ethers} from 'ethers';
import * as dotenv from 'dotenv';

dotenv.config();

const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS as string|| '';
if (!CONTRACT_ADDRESS) {
    throw new Error('Please set your contract address in the interact.ts script');
}
// Your ABI Here
const ABI = [
    {
        "inputs": [],
        "name": "get",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "value",
                "type": "uint256"
            }
        ],
        "name": "set",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
];

const FUJI_RPC_URL = "https://api.avax-test.network/ext/bc/C/rpc";
const PRIVATE_KEY = process.env.PRIVATE_KEY as string || '';

async function interact() {
    dotenv.config();
    if (!CONTRACT_ADDRESS) {
        throw new Error("Please set your contract address in the interact.ts script");
    }

    const provider = new ethers.providers.JsonRpcProvider(FUJI_RPC_URL);
    const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
    console.log("Wallet address:", wallet.address);

    const simpleStorage = new ethers.Contract(CONTRACT_ADDRESS, ABI, wallet);
    console.log("Contract address:", CONTRACT_ADDRESS);
    console.log("Contract instance:", simpleStorage);

    // Lee los datos almacenados
    const storedData = await simpleStorage.get();
    console.log("Stored data:", storedData.toString());

    // Configura los nuevos datos
    const tx = await simpleStorage.set(100);
    await tx.wait();

    // Lee la actualización de los datos almacenados
    const updatedStoredData = await simpleStorage.get();
    console.log("Updated stored data:", updatedStoredData.toString());
}

interact().then(r => console.log("Complete"));
Enter fullscreen mode Exit fullscreen mode

Reemplaza YOUR_CONTRACT_ADDRESS con la dirección de tu contrato desplegado y añade el ABI de tu contrato.

Ejecuta el script de interacción:

npx ts-node scripts/interact.ts
Enter fullscreen mode Exit fullscreen mode

Deberías verlos los datos almacenados, el recibo de la transacción y los datos actualizados almacenados en la output de la cónsola:

╰─ npx ts-node scripts/interact.ts

Wallet address: 0x2E3bE6ddaC75f8dA97d14009A540E917d881ea92
Contract address: 0x0697aBc8Dc960d53911f4A8BB8989826b78CaF61
Contract instance: Contract {
  interface: Interface {
    fragments: [ [FunctionFragment], [FunctionFragment] ],
    _abiCoder: AbiCoder { coerceFunc: null },
    functions: { 'get()': [FunctionFragment], 'set(uint256)': [FunctionFragment] },
    errors: {},
    events: {},
    structs: {},
    deploy: ConstructorFragment {
      name: null,
      type: 'constructor',
      inputs: [],
      payable: false,
      stateMutability: 'nonpayable',
      gas: null,
      _isFragment: true
    },
    _isInterface: true
  },
  provider: JsonRpcProvider {
    _isProvider: true,
    _events: [],
    _emitted: { block: -2 },
    disableCcipRead: false,
    formatter: Formatter { formats: [Object] },
    anyNetwork: false,
    _networkPromise: Promise { <pending> },
    _maxInternalBlockNumber: -1024,
    _lastBlockNumber: -2,
    _maxFilterBlockRange: 10,
    _pollingInterval: 4000,
    _fastQueryDate: 0,
    connection: { url: 'https://api.avax-test.network/ext/bc/C/rpc' },
    _nextId: 42
  },
  signer: Wallet {
    _isSigner: true,
    _signingKey: [Function (anonymous)],
    _mnemonic: [Function (anonymous)],
    address: '0x2E3bE6ddaC75f8dA97d14009A540E917d881ea92',
    provider: JsonRpcProvider {
      _isProvider: true,
      _events: [],
      _emitted: [Object],
      disableCcipRead: false,
      formatter: [Formatter],
      anyNetwork: false,
      _networkPromise: [Promise],
      _maxInternalBlockNumber: -1024,
      _lastBlockNumber: -2,
      _maxFilterBlockRange: 10,
      _pollingInterval: 4000,
      _fastQueryDate: 0,
      connection: [Object],
      _nextId: 42
    }
  },
  callStatic: {
    'get()': [Function (anonymous)],
    'set(uint256)': [Function (anonymous)],
    get: [Function (anonymous)],
    set: [Function (anonymous)]
  },
  estimateGas: {
    'get()': [Function (anonymous)],
    'set(uint256)': [Function (anonymous)],
    get: [Function (anonymous)],
    set: [Function (anonymous)]
  },
  functions: {
    'get()': [Function (anonymous)],
    'set(uint256)': [Function (anonymous)],
    get: [Function (anonymous)],
    set: [Function (anonymous)]
  },
  populateTransaction: {
    'get()': [Function (anonymous)],
    'set(uint256)': [Function (anonymous)],
    get: [Function (anonymous)],
    set: [Function (anonymous)]
  },
  filters: {},
  _runningEvents: {},
  _wrappedEmits: {},
  address: '0x0697aBc8Dc960d53911f4A8BB8989826b78CaF61',
  resolvedAddress: Promise { <pending> },
  'get()': [Function (anonymous)],
  'set(uint256)': [Function (anonymous)],
  get: [Function (anonymous)],
  set: [Function (anonymous)]
}
Stored data: 0
Updated stored data: 100
Complete
Enter fullscreen mode Exit fullscreen mode

¡Y eso es todo! Haz desarrollado, exitosamente, un contrato de almacenamiento simple en Solidity con TypeScript, EVM, HardHat y EtherJS, incluyendo la prueba local, despliegues en la red de prueba de Avalanche e interacciones con el contrato desplegado.

Código Fuente

https://github.com/nawodyaishan/avalanche-simple-storage

Discussion (0)