WEB3DEV Español

Cover image for Implementación del token ERC20 en Hyperledger Fabric
Hector
Hector

Posted on • Updated on

Implementación del token ERC20 en Hyperledger Fabric

Image description

Hola, bienvenido al artículo catchTheBlock sobre Implementar el token ERC20 en Hyperledger Fabric con excelente y emocionante lógica del chaincode, diferente de la lógica por defecto, proveída por la comunidad de Hyperledger.

Antes de ir al blog detallado, deberías tener una idea básica de qué es hyperledger fabric y su estructura. Si no lo sabes, entonces no te preocupes, iremos en detalle.

Ahora, sin mucho más, empecemos…

Hagamos una pequeña introducción a la arquitectura de hyperledger fabric

Image description

En el diagrama de arriba, puedes ver la aplicación del usuario comunicándose con el contrato inteligente, al que también podemos llamar “chaincode”, y está conectada a una red privada, incluyendo 4 nodos de la red.

Ahora revistas los detalles de la red en HLF

1 Canal (micanal (mychannel)), 2 Organizaciones (Org1 y Org2), cada Org tiene 2 peer (peer0 y peer1 (par)) y 1 chaincode (erc20token) y 1 Servicio de Orden.

Link al repositorio de GitHub

Este es el repositorio más simplificado para comenzar, para cualquier proyecto de hyperledger fabric, el código completo de erc20token está en el enlace del repositorio.

Vistazo del flujo de trabajo de la app erc20token

Image description

Declaración del problema

Implementaremos esta función enumerada del erc20token como se muestra abajo, estas son todas las funciones de un contrato inteligente:

  1. TokenName
  2. Symbol (Símbolo)
  3. Decimals (Decimales)
  4. setOptions
  5. Mint (Acuñar)
  6. getBalance
  7. Transfer (Transferir)

Código del Contrato Inteligente (Chaincode)

/*
SPDX-License-Identifier: Apache-2.0
*/

'use strict';

const { Contract } = require('fabric-contract-api');
const ClientIdentity = require('fabric-shim').ClientIdentity;

// key - valor
// Nombre del token ERC20 : Aplha
// Define nombres de key para las opciones
const nameKey = 'name';
const symbolKey = 'symbol';
const decimalsKey = 'decimals';
const totalSupplyKey = 'totalSupply';
const balancePrefix = 'balance';

class TokenERC20Contract extends Contract {

async SetOption(ctx, name, symbol, decimals){
//ejemplo de buffer: [ 23 45 57 79 89 90]
await ctx.stub.putState(nameKey, Buffer.from(name)) // para crear un estado en el ledger
await ctx.stub.putState(symbolKey, Buffer.from(symbol)) // para crear un estado en el ledger
await ctx.stub.putState(decimalsKey, Buffer.from(decimals)) // para crear un estado en el ledger
return 'success';
}

async TokenName(ctx) {
const nameBytes = await ctx.stub.getState(nameKey);
return nameBytes.toString();
}

async Symbol(ctx) {
const symbolBytes = await ctx.stub.getState(symbolKey);
return symbolBytes.toString();
}

async Decimals(ctx) {
const decimalBytes = await ctx.stub.getState(decimalsKey);
return decimalBytes.toString();
}

async Mint(ctx, amount) {

// validar el rol del usuario
let cid = new ClientIdentity(ctx.stub)
const role = await cid.getAttributeValue('role'); // obtener el rol cert del usuario registrado.

if(role !== 'Minter') {
return('User is not Authorized to Mint Tokens....!')
}

// cuenta validadora
const amountInt = parseInt(amount);
if(amountInt < 0){
return('Amount should not be Zero....!');
}

// increments el balance de la acuñación
const minter = await cid.getAttributeValue('userId'); // obtén userId desde el cert del usuario registrado.
const balanceKey = ctx.stub.createCompositeKey(balancePrefix,[minter]);

const currentBalanceBytes = await ctx.stub.getState(balanceKey);

let currentBalance;
if(!currentBalanceBytes || currentBalanceBytes.length === 0 ){
currentBalance = 0;
}else {
currentBalance = parseInt(currentBalanceBytes.toString());
}
const updatedBalance = currentBalance + amountInt;
await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString()));


// increments el suministro total
let totalSupply;
const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey); // busca el valor de tokensupply desde el ledger

if(!totalSupplyBytes || totalSupplyBytes.length === 0 ){
totalSupply = 0;
console.log('Initialize the tokenSupply..!');
}else {
totalSupply = parseInt(totalSupplyBytes.toString());
}
totalSupply = totalSupply + amountInt; // añade tokenSupply con una nueva cantidad

await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); // key- valor

return 'success';
}

async getBalance(ctx) {

let balance;
let cid = new ClientIdentity(ctx.stub);
const userID = await cid.getAttributeValue('userId'); // obtén userId desde el cert del usuario registrado.
const balanceKey = ctx.stub.createCompositeKey(balancePrefix,[userID]);
const currentBalanceBytes = await ctx.stub.getState(balanceKey);

if(!currentBalanceBytes || currentBalanceBytes.length === 0 || currentBalanceBytes === 0 ){
return(`This user ${userID} has Zero balance Account`);

}
balance = parseInt(currentBalanceBytes.toString());

return balance;
}

async Transfer(ctx,to, value){

let cid = new ClientIdentity(ctx.stub);
const from = await cid.getAttributeValue('userId'); // obtén sender userId desde el cert del usuario registrado.

// Convierte el valor desde la cadena a int
const valueInt = parseInt(value);

if (valueInt < 0) { // transferencia de 0 se permite en ERC20, así que solo valida contra las cantidades negativas
return('transfer amount cannot be negative');
}

// Retira el balance actual del remitente
const fromBalanceKey = ctx.stub.createCompositeKey(balancePrefix, [from]);
const fromCurrentBalanceBytes = await ctx.stub.getState(fromBalanceKey);

if (!fromCurrentBalanceBytes || fromCurrentBalanceBytes.length === 0) {
return(`client account ${from} has no balance`);
}

const fromCurrentBalance = parseInt(fromCurrentBalanceBytes.toString());

// Revisa si el remitente tiene suficientes tokens para gastar.
if (fromCurrentBalance < valueInt) {
return(`client account ${from} has insufficient funds.`);
}

// Retira el balance actual del destinatario
const toBalanceKey = ctx.stub.createCompositeKey(balancePrefix, [to]);
const toCurrentBalanceBytes = await ctx.stub.getState(toBalanceKey);

let toCurrentBalance;
// Si el balance actual del destinatario no existe aún, lo crearemos con un balance actual de 0
if (!toCurrentBalanceBytes || toCurrentBalanceBytes.length === 0) {
toCurrentBalance = 0;
} else {
toCurrentBalance = parseInt(toCurrentBalanceBytes.toString());
}

// Actualiza el balance
const fromUpdatedBalance = fromCurrentBalance - valueInt;
const toUpdatedBalance = toCurrentBalance + valueInt;

await ctx.stub.putState(fromBalanceKey, Buffer.from(fromUpdatedBalance.toString()));
await ctx.stub.putState(toBalanceKey, Buffer.from(toUpdatedBalance.toString()));

console.log(`client ${from} balance updated from ${fromCurrentBalance} to ${fromUpdatedBalance}`);
console.log(`recipient ${to} balance updated from ${toCurrentBalance} to ${toUpdatedBalance}`);

return true;

}

}

module.exports = TokenERC20Contract;
Enter fullscreen mode Exit fullscreen mode

La lógica del chaincode es muy sencilla.

La lógica de la función de Acuñar

En esta función, acuñaremos tokens (generando tokens en el suministro total). Hemos creado un estado global para el total del suministro, y el balance del usuario cuando llamamos a la función de Acuñar, el suministro total se incrementa por cantidad y el balance también es incrementado por la cantidad. Sólo los usuarios con el rol de Acuñar puede invocar la función de Acuñar.

async Mint(ctx, amount) {

// valida el rol del usuario
let cid = new ClientIdentity(ctx.stub)
const role = await cid.getAttributeValue('role'); // obtén el rol del cert del usuario registrado.

if(role !== 'Minter') {
return('User is not Authorized to Mint Tokens....!')
}

// cantidad a validar
const amountInt = parseInt(amount);
if(amountInt < 0){
return('Amount should not be Zero....!');
}

// increments el balance del que acuña
const minter = await cid.getAttributeValue('userId'); // obtén el userId desde el cert del usuario registrado.
const balanceKey = ctx.stub.createCompositeKey(balancePrefix,[minter]);

const currentBalanceBytes = await ctx.stub.getState(balanceKey);

let currentBalance;
if(!currentBalanceBytes || currentBalanceBytes.length === 0 ){
currentBalance = 0;
}else {
currentBalance = parseInt(currentBalanceBytes.toString());
}
const updatedBalance = currentBalance + amountInt;
await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString()));


// increments el suministro total
let totalSupply;
const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey); // busca el valor tokensupply desde el ledger

if(!totalSupplyBytes || totalSupplyBytes.length === 0 ){
totalSupply = 0;
console.log('Initialize the tokenSupply..!');
}else {
totalSupply = parseInt(totalSupplyBytes.toString());
}
totalSupply = totalSupply + amountInt; // añade tokenSupply con nuevas cantidades 

await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); // key- valor

return 'success';
}
Enter fullscreen mode Exit fullscreen mode

En HLF tenemos una clase de identidad de cliente bajo la fabrica SDK para administrar las operaciones de identidad.

Ahora veamos para ejecutar esta aplicación

Primero necesitaremos crear una red con 2 Organizaciones y Orderer.

Ejecuta este script para crear una red por defecto como red de prueba. Ve a la raíz del directorio de tu repositorio clonado.

$ cd token-erc-20/
$ ./networkUp.sh
Enter fullscreen mode Exit fullscreen mode

Image description

Esto creará la red que puedes confirmar usando este comando docker para revisar todos los containers que se ejecutan.

$ docker ps -a
Enter fullscreen mode Exit fullscreen mode

Obtendrás algo similar al output de este tipo

Image description

Ahora despliega el chaincode

Ejecuta un script deployChaincode con el nombre del chaincode.

$ ./deployChaincode.sh erc20Token
Enter fullscreen mode Exit fullscreen mode

Image description

Como hemos desplegado nuestro chaincode exitosamente en la red, ahora podremos usar nuestro chaincode a través de la fábrica SDK dentro de la aplicación del nodo.

Registro del Administrador

$ cd api-server/
$ node registerAdmin.js 
Enter fullscreen mode Exit fullscreen mode

Registra Acuñar y Destinatario estos son usuarios normales

$ node registerMinter.js
$ node registerRecevier.js 
Enter fullscreen mode Exit fullscreen mode

Para confirmar el administrador y el ID del usuario, revisa la carpeta de la cartera donde encontrarás el certificado creado con la extensión .id

Admin - admin.id, Minter - Rama.id, Receiver - Sita.id

Image description

Ahora invocaremos el chaincode a través de un script de Acuñar

En este script, estaremos buscando el nombre del Token y luego acuñaremos 18000 tokens y obtendremos el balance, luego transferiremos 9000 tokens Alpha a Sita.

$ cd api-server
$ npm install
$ node invokeByMinter.js 
Enter fullscreen mode Exit fullscreen mode

Image description

Perfecto, Rama ha acuñado exitosamente 18000 tokens y transferido 9000 Tokens Alpha a Sita, habiendo actualizado el balance de Rama.

Ejecuta el script Destinatario para revisar el balance de Sita

Ahora, revisaremos si sita recibió los 9000 tokens exitosamente o no.

$ node invokeByRecevier.js
Enter fullscreen mode Exit fullscreen mode

Image description

Perfecto, tenemos el balance actualizado exitosamente. Ahora, podemos concluir que nuestra aplicación trabaja correctamente.

Pasos para detener la red

No te olvides de apagar la red, esto es muy importante para evadir errores en la siguiente ejecución para proteger a tu sistema para que no consuma mucha memoria.

Regresa a la carpeta token-erc20

$ cd token-erc-20
$ ./networkDown.sh 
Enter fullscreen mode Exit fullscreen mode

Image description

Espero que hayas aprendido algo nuevo e interesante. Espero que este artículo te ayude de alguna forma. Puedes contactarme para cualquier ayuda en Linkedin o podemos hablar más de la Blockchain aquí.

No te olvides de comentar y siéntete libre de comentar si te quedas atascado en alguna parte. Estoy feliz de responder.

Muchas gracias por leer este artículo. ¡Feliz aprendizaje!

Este artículo es una traducción de Akshay Kurhekar, 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_es en Twitter.

Discussion (0)