Olá, bem-vindo ao artigo catchTheBlock sobre a implementação do token Erc20 na Hyperledger Fabric com uma lógica Chaincode excelente e empolgante, diferente da lógica padrão fornecida pela comunidade Hyperledger.
Antes de ir para o artigo detalhado, você deve ter uma ideia básica do que é a Hyperledger Fabric e sua arquitetura. Se você não sabe, também não se preocupe, vamos entrar em detalhes.
Então, agora, sem mais demora, vamos começar…
Vamos fazer uma rápida introdução à arquitetura da Hyperledger Fabric
######Visão geral da arquitetura da Hyperledger Fabric
No diagrama acima, você pode ver o aplicativo do usuário se comunicando com o contrato inteligente, também podemos chamá-lo de Chaincode e está conectado a uma rede privada incluindo 4 nós da rede.
Agora vamos verificar os detalhes da rede do HLF
1 canal (mychannel), 2 organizações (Org1 e Org2), cada Org tem 2 pares (peer0 e peer1) e 1 Chaincode (erc20token) e 1 serviço de pedidos.
Link do repositório do GitHub
Este é o repositório mais simplificado para iniciar qualquer projeto na Hyperledger Fabric, o código erc20token completo está neste link do repositório.
Visão geral do fluxo de trabalho do aplicativo erc20token
######Fluxo de trabalho do token erc20
Declaração do problema
Implementaremos esta função elencada do erc20token conforme listado abaixo. Todas são funções de um contrato inteligente.
- TokenName (Nome do token).
- Symbol (Símbolo).
- Decimals (Decimais).
- setOptions (Definir opções).
- Mint (Cunhar).
- GetBalance (Obter saldo).
- Transfer (Transferir).
Código de contrato inteligente (Chaincode)
/*
SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Contract } = require('fabric-contract-api');
const ClientIdentity = require('fabric-shim').ClientIdentity;
// chave - valor
// Nome do token ERC20: Aplha
// Definir nomes-chave para opções
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){
//exemplo de buffer: [ 23 45 57 79 89 90]
await ctx.stub.putState(nameKey, Buffer.from(name)) // para criar um estado no livro-razão
await ctx.stub.putState(symbolKey, Buffer.from(symbol)) // para criar um estado no livro-razão
await ctx.stub.putState(decimalsKey, Buffer.from(decimals)) // para criar um estado no livro-razão
return 'successo';
}
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) {
// validação da função do usuário
let cid = new ClientIdentity(ctx.stub)
const role = await cid.getAttributeValue('role'); // obtém a função do certificado do usuário registrado.
if(role !== 'Minter') {
return('O usuario nao esta autorizado a cunhar tokens....!')
}
// validação do valor
const amountInt = parseInt(amount);
if(amountInt < 0){
return('O valor não deve ser zero....!');
}
// incremento do saldo do cunhador
const minter = await cid.getAttributeValue('userId'); // obter o userId do certificado do usuário 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()));
// incremento do fornecimento total
let totalSupply;
const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey); // buscar o valor do fornecimento de tokens no livro-razão
if(!totalSupplyBytes || totalSupplyBytes.length === 0 ){
totalSupply = 0;
console.log('Inicializar o tokenSupply..!');
}else {
totalSupply = parseInt(totalSupplyBytes.toString());
}
totalSupply = totalSupply + amountInt; // adicionado tokenSupply com novo valor
await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); // chave-valor
return 'successo';
}
async getBalance(ctx) {
let balance;
let cid = new ClientIdentity(ctx.stub);
const userID = await cid.getAttributeValue('userId'); // obter o userId do certificado do usuário 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ém o userId do remetente a partir do certificado do usuário registrado.
// Converter valor de string para int
const valueInt = parseInt(value);
if (valueInt < 0) { // a transferência de 0 é permitida no ERC20, portanto, basta validar contra valores negativos
return('o valor da transferencia nao pode ser negativo');
}
// Recuperar o saldo atual do remetente
const fromBalanceKey = ctx.stub.createCompositeKey(balancePrefix, [from]);
const fromCurrentBalanceBytes = await ctx.stub.getState(fromBalanceKey);
if (!fromCurrentBalanceBytes || fromCurrentBalanceBytes.length === 0) {
return(`A conta do cliente ${from} nao tem saldo`);
}
const fromCurrentBalance = parseInt(fromCurrentBalanceBytes.toString());
// Verificar se o remetente tem tokens suficientes para gastar.
if (fromCurrentBalance < valueInt) {
return(`a conta do cliente ${from} tem fundos insuficientes.`);
}
// Recuperar o saldo atual do recebedor
const toBalanceKey = ctx.stub.createCompositeKey(balancePrefix, [to]);
const toCurrentBalanceBytes = await ctx.stub.getState(toBalanceKey);
let toCurrentBalance;
// Se o saldo atual do destinatário ainda não existir, nós o criaremos com um saldo atual de 0
if (!toCurrentBalanceBytes || toCurrentBalanceBytes.length === 0) {
toCurrentBalance = 0;
} else {
toCurrentBalance = parseInt(toCurrentBalanceBytes.toString());
}
// Atualizar o saldo
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;
A lógica do Chaincode é bem simples.
A lógica da função Mint:
Nesta função, vamos cunhar tokens (gerando tokens em suprimento total). Criamos um estado global para o suprimento total e o saldo do usuário sempre que chamamos a função Mint, o suprimento total é incrementado por valor e o saldo também é incrementado por valor. Somente usuários com uma função Minter (cunhador) podem chamar a função Mint.
async Mint(ctx, amount) {
// validação da função do usuário
let cid = new ClientIdentity(ctx.stub)
const role = await cid.getAttributeValue('funcao'); // obtém a função do certificado do usuário registrado.
if(role !== 'Minter') {
return('Usuario nao esta autorizado a cunhar tokens....!')
}
// validação do valor
const amountInt = parseInt(amount);
if(amountInt < 0){
return('O valor nao deve ser zero....!');
}
// saldo de incremento do minter
const minter = await cid.getAttributeValue('userId'); // obtém o userId do certificado do usuário 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()));
// incremento do fornecimento total
let totalSupply;
const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey); // buscar o valor do fornecimento de tokens no livro-razão
if(!totalSupplyBytes || totalSupplyBytes.length === 0 ){
totalSupply = 0;
console.log('Initialize the tokenSupply..!');
}else {
totalSupply = parseInt(totalSupplyBytes.toString());
}
totalSupply = totalSupply + amountInt; // Adicionado tokenSupply com novo valor
await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); // chave-valor
return 'successo';
}
No HLF, temos uma classe de identidade do cliente em fabric-SDK para gerenciar as operações de identidade.
Agora vamos dar uma olhada para executar este aplicativo.
Primeiro, precisamos criar uma rede com 2 Organizações e Ordenador.
Execute este script para criar a rede padrão como rede de teste. Vá para o diretório raiz do seu repositório clonado.
$ cd token-erc-20/
$ ./networkUp.sh
######A rede está ativa agora.
Isso criará a rede que você pode confirmar usando este comando docker para verificar todos os contêineres em execução.
$ docker ps -a
Você obterá uma saída semelhante a esta.
######Saída docker ps -a
Agora vamos implantar o Chaincode
Execute um script deployChaincode com o nome Chaincode.
$ ./deployChaincode.sh erc20Token
######Sucesso na implantação do Chaincode
Como implantamos com sucesso nosso Chaincode na rede, agora usaremos nosso Chaincode por meio do fabric-SDK dentro do aplicativo do nó.
Registrar administrador (Register Admin)
$ cd api-server/
$ node registerAdmin.js
Registrar o Minter (Register Minter) e Destinatário (Receiver) como um usuário normal
$ node registerMinter.js
$ node registerRecevier.js
Para confirmar o ID do administrador e do usuário, verifique a pasta da carteira e você encontrará o certificado criado com a extensão .id.
Admin — admin.id, Minter — Rama.id, Receptor — Sita.id.
######Certificado do usuário
Agora vamos invocar o Chaincode, através de um script do Minter.
Neste script, estamos buscando o nome do Token, cunhando 18.000 tokens e obtendo o saldo e, em seguida, transferindo 9.000 tokens Alpha para Sita.
$ cd api-server
$ npm install
$ node invokeByMinter.js
######Status do Minter
Perfeito, Rama cunhou com sucesso 18.000 tokens e transferiu 9.000 Tokens Alpha para Sita, obtendo o saldo atualizado do Rama.
Execute o script para o Receptor verificar o saldo de Sita.
Agora, verificaremos se a Sita recebeu 9.000 tokens com sucesso ou não.
$ node invokeByRecevier.js
######Status do Receptor
Perfeito, atualizamos o saldo com sucesso. Assim, podemos concluir nosso aplicativo funcionando corretamente.
Passos para parar a rede
Não se esqueça de desligar a rede, isso é muito importante para evitar erros na próxima execução e para proteger seu sistema de consumir muita memória.
Volte para a pasta token-erc-20.
$ cd token-erc-20
$ ./networkDown.sh

Espero que você aprenda algo novo e interessante, desejo que este artigo o ajude de alguma forma. Você pode entrar em contato comigo para obter ajuda no Linkedin ou podemos conversar mais sobre Blockchain lá.
Não se esqueça de demonstrar seu apoio, sinta-se à vontade para comentar se tiver dúvidas em algum ponto.
Muito obrigado por ler este artigo. Bom aprendizado...!
Artigo escrito por Akshay Kurhekar. Traduzido por Marcelo Panegali.