WEB3DEV

Cover image for Registros Ethereum, Mãos na massa com Ethers.js
Panegali
Panegali

Posted on • Atualizado em

Registros Ethereum, Mãos na massa com Ethers.js

Sumário
  1. Introdução
  2. Decodificando registros brutos
  3. Biblioteca de conveniência Ethers.js

Introdução

Ao desenvolver na Ethereum, um dos principais tipos de dados que você inevitavelmente encontrará são os eventos de contratos inteligentes. Os eventos fornecem uma maneira de um contrato inteligente se comunicar com os aplicativos de assinatura, seja com a finalidade de retornar valores de contrato ou para acionar uma função correspondente. Além disso, como todo evento é gravado na blockchain, ele também pode servir como um registro histórico (e armazenamento mais barato) de todos os eventos emitidos por um contrato.

Este artigo se concentra na decodificação dessa função de registro e assume algum conhecimento básico sobre eventos e registros da Ethereum. Como um refresco:

  • Os eventos são específicos do aplicativo, conforme definido pelo desenvolvedor do contrato inteligente. Por exemplo, os desenvolvedores da Larvalabs definem um evento PunkTransfer para notificar os aplicativos quando um Cryptopunk foi transferido. O evento é emitido quando PunkTransfer() é chamado.

event PunkTransfer(address indexed from, address indexed to, uint256 punkIndex);

  • Ao consultar os registros de um evento, os valores emitidos do evento são retornados como um DataHexString. Assim, precisaremos do contrato ABI para decodificar os dados (lembre-se de que o evento é específico da aplicação). Abaixo está a resposta bruta ao consultar o evento PunkTransfer, as especificações completas podem ser encontradas aqui.

Registro de eventos

  • A Ethereum utiliza filtros bloom para armazenar seções dos dados de registro, o que permite uma consulta e filtragem eficientes de registros sem a necessidade de baixar a blockchain completa. “Tópicos” são usados ​​para descrever o evento com o primeiro tópico geralmente consistindo no hash keccak256 da assinatura do evento (isto é, PunkTransfer(address,address,uint256)). Portanto, os tópicos permitem a recuperação rápida de registros específicos.

Função de HASH online

Observe que esse hash corresponde ao primeiro tópico na captura de tela acima

Para entender melhor, vamos passar por 2 formas de processamento dos registros, com e sem a ajuda da biblioteca de conveniência de contrato Ethers.js. Este guia estará consultando os eventos PunkTransfer que foram emitidos nos últimos 1000 blocos. Ao fazer isso, teremos uma melhor compreensão do que Ethers.js está fazendo por baixo do capô e como os eventos são realmente registrados na Ethereum. O repositório para este tutorial pode ser encontrado aqui.


Decodificando registros brutos

Para obter os registros brutos, usaremos o provedor Ethers.js para consultar a blockchain sem a funcionalidade adicional de auxiliares de contrato. Depois que seu provedor for configurado e o bloco mais recente for consultado, podemos obter os registros brutos utilizando provider.getLogs().

async function getLogs() {
    console.log(`Obtendo os eventos do PunkTransfer...`);

    const cryptopunkContractAddress: string = '0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB'; 

    const eventSignature: string = 'PunkTransfer(address,address,uint256)';
    const eventTopic: string = ethers.utils.id(eventSignature); // Obter a string hexadecimal de dados

    rawLogs = await provider.getLogs({
        address: cryptopunkContractAddress,
        topics: [eventTopic],
        fromBlock: currentBlock - 10000, 
        toBlock: currentBlock
    });
}
Enter fullscreen mode Exit fullscreen mode

getLogs() espera que um filtro seja passado como um parâmetro. Nesse caso, passamos o endereço do contrato CryptoPunks, tópico para filtrar, bem como o intervalo do bloco de filtro. A lista completa de opções de filtro pode ser encontrada nos documentos oficiais do ether.

Observe que obtivemos o tópico fazendo o hash de eventSignature com ethers.utils.id, que retorna o hash keccak256. Isso aponta para o fato de que os eventos são armazenados como hashes na blockchain para reduzir os requisitos de armazenamento, pois todos os hashes têm comprimento fixo. Além disso, como os hashes são fáceis de calcular com base em uma entrada, mas proibitivamente difíceis de fazer engenharia reversa por meio de uma saída, isso também fornece um certo nível de privacidade da transação.

Dessa forma, sem conhecer o contrato do qual o registro se originou, não poderíamos saber muito sobre o evento nem o tópico pelo qual filtrar. Para referência, um exemplo de resposta bruta do evento PunkTransfer é fornecido abaixo:

Registros do contrato

Somente na captura de tela acima, não podemos obter nenhum dado útil específico do aplicativo. No entanto, como estamos cientes do contexto do evento, também podemos decodificá-lo usando o ABI ( Application Binary Interface ).

async function processLogsWithInterface() {
    const abi: string = '[{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"punkIndex","type":"uint256"}],"name":"PunkTransfer","type":"event"}]';

    const intrfc = new ethers.utils.Interface(abi);

    rawLogs.forEach((log: any) => {
        console.log(`ANTES DE ANALISAR:`);
        console.debug(log);
        console.log(`\n`);

        console.log(`DEPOIS DE ANALISAR:`);
        let parsedLog = intrfc.parseLog(log);
        console.debug(parsedLog);
        console.log('************************************************');
    })
}
Enter fullscreen mode Exit fullscreen mode

A ABI fornecida especifica como interagir com o registro de eventos bruto. Ao instanciar um objeto Interface com a ABI, podemos decodificar o registro bruto com o parsedLog() que resulta no seguinte:

ABI de interação com os eventos

O registro bruto foi analisado com sucesso e podemos extrair os valores que foram emitidos pelo contrato inteligente por meio do evento PunkTransfer.


Biblioteca de conveniência Ethers.js

Como agora temos uma melhor compreensão de como os registros são tratados, podemos apreciar melhor a abstração que Ethers.js permite por meio do objeto Contract. Primeiro criamos o objeto de contrato CryptoPunk:

async function getContract() {
  console.log(`Obtenção do contrato CryptoPunk...`);

  const cryptopunkContractAddress: string = '0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB'; 
  const cryptopunkContractAbi: string = '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"punksOfferedForSale","outputs":[{"name":"isForSale","type":"bool"},{"name":"punkIndex","type":"uint256"},{"name":"seller","type":"address"},{"name":"minValue","type":"uint256"},{"name":"onlySellTo","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"enterBidForPunk","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"},{"name":"minPrice","type":"uint256"}],"name":"acceptBidForPunk","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"addresses","type":"address[]"},{"name":"indices","type":"uint256[]"}],"name":"setInitialOwners","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"withdraw","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"imageHash","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"nextPunkIndexToAssign","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"punkIndexToAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"standard","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"punkBids","outputs":[{"name":"hasBid","type":"bool"},{"name":"punkIndex","type":"uint256"},{"name":"bidder","type":"address"},{"name":"value","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"allInitialOwnersAssigned","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"allPunksAssigned","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"buyPunk","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"punkIndex","type":"uint256"}],"name":"transferPunk","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"withdrawBidForPunk","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"punkIndex","type":"uint256"}],"name":"setInitialOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"},{"name":"minSalePriceInWei","type":"uint256"},{"name":"toAddress","type":"address"}],"name":"offerPunkForSaleToAddress","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"punksRemainingToAssign","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"},{"name":"minSalePriceInWei","type":"uint256"}],"name":"offerPunkForSale","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"getPunk","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"pendingWithdrawals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"punkNoLongerForSale","outputs":[],"payable":false,"type":"function"},{"inputs":[],"payable":true,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"punkIndex","type":"uint256"}],"name":"Assign","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"punkIndex","type":"uint256"}],"name":"PunkTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"},{"indexed":false,"name":"minValue","type":"uint256"},{"indexed":true,"name":"toAddress","type":"address"}],"name":"PunkOffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"fromAddress","type":"address"}],"name":"PunkBidEntered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"fromAddress","type":"address"}],"name":"PunkBidWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"fromAddress","type":"address"},{"indexed":true,"name":"toAddress","type":"address"}],"name":"PunkBought","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"}],"name":"PunkNoLongerForSale","type":"event"}]';
  cryptopunkContract = new ethers.Contract(cryptopunkContractAddress, cryptopunkContractAbi, provider);
}
Enter fullscreen mode Exit fullscreen mode

Observe que, ao criar o contrato, também fornecemos o endereço do contrato e a ABI completa (que pode ser encontrada no Etherscan). Com a instância do contrato configurada, a obtenção dos registros pode ser feita de forma simples através de uma simples função queryFilter().

async function getEvents() {
  console.log(`Obtendo os eventos do PunkTransfer...`);

  let events = await cryptopunkContract.queryFilter('PunkTransfer', currentBlock - 10000, currentBlock);

  console.log(events);
}
Enter fullscreen mode Exit fullscreen mode

Este método nos permite evitar as complexidades de gerenciar os tópicos ao consultar um evento, pois precisamos apenas passar o nome do evento. O resultado final é o mesmo, mas é muito mais fácil extrair os dados do contrato inteligente:

Extração dos dados


Obrigado por ficar até o fim. Adoraria ouvir seus pensamentos/comentários, então deixe um comentário. Estou ativo no twitter @ AwKaiShin se você quiser receber informações mais digeríveis sobre informações relacionadas à cripto ou visite meu site pessoal se quiser meus serviços.

Artigo escrito por Aw Kai Shin e traduzido por Marcelo Panegali


Abrace a oportunidade de elevar sua jornada de desenvolvimento para um nível superior. O desenvolvimento em Ethereum é apenas o começo; os builds incríveis da WEB3DEV representam a chave de entrada para o emocionante cenário web3. 🚀🧑‍💻

Não perca tempo, 👉inscreva-se👈 agora mesmo e comece a desbravar o universo Blockchain!
 
Seja também WEB3DEV!

Latest comments (0)