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.
Top comments (0)