WEB3DEV

Cover image for Contratos Inteligentes Ethereum. Como se Comunicar Com Eles? (Especificação ABI, Web3, Solidity)
Panegali
Panegali

Posted on

Contratos Inteligentes Ethereum. Como se Comunicar Com Eles? (Especificação ABI, Web3, Solidity)

Os contratos inteligentes são implantados na blockchain, eles são pedaços de código que contêm alguma lógica e são executados pela EVM, transformando a blockchain ethereum em uma espécie de computador mundial distribuído.

Contratos inteligentes podem ser invocados por usuários/programas off-chain que enviam transações para a blockchain ou eles também podem se comunicar entre eles invocando os métodos uns dos outros (seguindo uma invocação off-chain, os contratos inteligentes não podem “acionar” eles mesmos).

As interações de contratos inteligentes devem seguir as especificações da ABI, que é o conjunto de regras e definições que padronizam a comunicação de contratos inteligentes no ecossistema ethereum.

Neste blog apresentarei, de forma simplificada, como os dados devem ser enviados a blockchain para acionar um contrato inteligente de acordo com as Especificações da ABI. Em seguida, falarei sobre as diferentes possibilidades de invocar funções de contratos inteligentes de aplicativos off-chain e on-chain.

2

Introdução

Antes de começarmos, vou simplesmente esclarecer quais ferramentas usarei neste blog.

Para entidades off-chain, usarei a biblioteca javascript web3 (web3.js) porque ela encapsula o protocolo JSON-RPC, que é o protocolo real usado para se comunicar com a blockchain. Qualquer outra biblioteca que possa ser usada para o mesmo propósito seguiria os mesmos conceitos (ethers.js, …), mas a sintaxe provavelmente será diferente.

2

Para contratos inteligentes on-chain, usarei a linguagem solidity, mas qualquer outra linguagem de programação compatível com EVM também funcionaria.

Especificação ABI

A Especificação Abi indica como construir a string de bytes que deve ser enviada como “dados” em uma “transação” ao invocar uma função de contrato inteligente. A string de bytes contém 2 partes principais:

  • Seletor de função: primeiros 4 bytes. Eles indicam a função exata do contrato inteligente que está sendo invocado. O seletor de função é obtido calculando o hash ( keccak256 ) da assinatura da função (nome da função seguido por seus tipos de dados de parâmetros de entrada “ func1(bool,uint256,address)” ) e então simplesmente extraindo seus primeiros 4 bytes. Pode haver algumas colisões, pois estamos usando apenas 4 bytes, mas é altamente improvável…
  • Argumentos codificados: a partir do 5º byte, devemos adicionar os argumentos codificados que estamos passando como parâmetros de entrada na mesma ordem em que são especificados na assinatura da função. Existem dois tipos de argumentos, estáticos (tipos de dados de valor como bool, unit256, …) e dinâmicos (tipos de dados de referência como array, …) . Os argumentos estáticos ocupam 32 bytes (na posição indicada pela assinatura do método) e contêm o valor do argumento (preenchido com 0s, se necessário). Argumentos Dinâmicos são codificados de uma maneira diferente. Os 32 bytes reservados a eles (na posição em que são indicados pela assinatura do método) indicam a posição em que o valor do argumento está realmente incluído (como um deslocamento de bytes a contar desde o início da parte codificada do argumento). Na posição indicada, os primeiros 32 bytes indicam o comprimento do argumento (quantos valores ele contém) e, em seguida, os valores reais são listados.

Exemplo 1

  • Função: baz(uint32 val, bool check) retorna bool
  • Assinatura da função: baz(uint32,bool)
  • Invocação: baz(69, true)

3

Bytes laranja = seletor de função. Primeiros 4 bytes de keccak256(“baz(uint32,bool)”).

Bytes azuis = primeiro parâmetro codificado, é um parâmetro estático com valor “69” (0x….45).

Bytes vermelhos = segundo parâmetro codificado, é um parâmetro estático com valor “true” (0x….01).

Exemplo 2

  • Função: sam(bytes name, bool check, uint256[] ids)
  • Assinatura da função: sam(bytes,bool,uint256[])
  • Invocação: sam(“dave”, true, [1,2,3])

4

Bytes laranja = seletor de função. Primeiros 4 bytes de keccak256(“sam(bytes,bool,uint256[])”).

Bytes azuis = primeiro parâmetro codificado, é um parâmetro dinâmico, começamos indicando sua localização (byte 0x60). Então, no local 0x60, o primeiro byte indica o comprimento (0x…..04 = 4 bytes, pois o tipo de dados é bytes), o segundo byte indica o valor real do parâmetro “dave” (0x646176650………0).

Bytes vermelhos = segundo parâmetro codificado, é um parâmetro estático com valor “true” (0x….01).

Bytes verdes = terceiro parâmetro codificado, é um parâmetro dinâmico, começamos indicando sua localização (byte 0xa0). Em seguida, na posição 0xa0, o primeiro byte indica o comprimento (0x….03 = 3 palavras, pois é um array uint256), depois os três bytes indicam os valores ”1”, “2”, “3” (0x….. 01, 0x….02, 0x……03).

Comunicação off-chain para on-chain

Você tem um aplicativo front-end ou back-end que precisa interagir com algum contrato inteligente ethereum. Estarei usando a biblioteca web3.js para javascript que lidará com o protocolo JSON-RPC e também gerará a string de bytes compatível com a especificação abi que deve ser enviada a blockchain.

Existem dois cenários possíveis, você tem o contrato inteligente JSON ABI ou não.

Com Contrato Inteligente JSON ABI

O contrato inteligente JSON ABI é um arquivo JSON gerado pelo compilador Solidity quando você cria seus contratos inteligentes. O compilador na verdade gera dois documentos:

  • ByteCode: Os opcodes (operações EVM) que serão implantados na blockchain e os opcodes da função “construtor” (se existir) que serão executados apenas uma vez, ao implantar o contrato inteligente, todos em formato de bytes.
  • O JSON ABI: Um array json com a lista de funções públicas e externas, eventos e erros relacionados aos seus contratos inteligentes. Cada função, evento e erro é um objeto json dentro do array e contém todas as informações necessárias para que entidades off-chain interajam com eles.

5

Os objetos JSON ABI contém as seguintes informações

Objetos de função:

  • Tipo: indica o tipo de função, as opções são “ function ” (para funções regulares), “ receive ”, “ fallback ” e “ constructor ” (para as funções especiais da ethereum).
  • Nome: Nome da função.
  • Entradas: Matriz de objetos contendo para cada argumento de entrada de função seu nome, tipo e componente.
  • Saídas: Assim como a entrada, mas para os argumentos de saída da função.
  • Mutabilidade de estado: a função de mutabilidade, as opções são “ view ” (só lê da blockchain), “ puro ” (nem escreve nem lê da blockchain), “ nonpayable ” (não pode receber ether) e “ payable ” (pode receber ether).

Objetos de evento :

  • Tipo: sempre “ event ”.
  • Nome: Nome do evento.
  • Entradas: Matriz de objetos contendo para cada parâmetro de evento seu nome, tipo, componente e indexado (verdadeiro ou falso).
  • Anonymous: verdadeiro se o evento foi declarado anônimo.

Objetos de erro :

  • Tipo: sempre “ error ”.
  • Nome: Nome do erro.
  • Entradas: Matriz de objetos contendo para cada parâmetro de erro seu nome, tipo e componente).

Para interagir com um contrato inteligente de seu aplicativo off-chain, primeiro você precisa importar o arquivo JSON Abi e, em seguida, instanciar um objeto fornecendo o JSON Abi e o endereço que aponta para o contrato inteligente. A partir desse momento você pode invocar os métodos do contrato diretamente como para qualquer outro objeto.

As invocações de contratos inteligentes serão feitas de forma assíncrona.

// Referencia o contrato inteligente
const SmartContract= require ("SmartContract");
// Recupera o JSON ABI e o endereça
const SmartContractAbi = SmartContract. abi ;
const SmartContractAddress = "0x......"
// Instancia um objeto que "encapsula" o contrato inteligente
const SmartContractObject = new web3.eth.Contract (SmartContractAbi, SmartContractAddress);
// Agora você está pronto para interagir com o contrato inteligente. Funções // invocações retornarão promises.
SmartContractObject.methods .func1(...). send ({from: ..., ...}).on (...);
SmartContractObject.methods .func2(...). call ({from: ...}).on (...);
Enter fullscreen mode Exit fullscreen mode

Sem contrato inteligente JSON ABI

Se você não tiver o JSON ABI, ainda poderá interagir com contratos inteligentes, mas será um pouco mais complicado e irritante.

Você mesmo terá que criar a transação blockchain a partir da definição do método (no formato json), dos parâmetros de entrada que deseja enviar e enviá-la diretamente para o endereço do contrato inteligente.

Você poderá enviar uma transação “send” (uma transação real que mudará o estado da blockchain) ou uma transação de “call” (não uma transação real da perspectiva da ethereum, pois apenas lerá dados).

As transações serão enviadas de forma assíncrona.

//  Define os dados da transação
const TransactionData = web3.eth.abi.encodeFunctionCall({
   name: 'myMethod',
   type: 'function',
   inputs: [{
       type: 'uint256',
       name: 'myNumber'
   },{
       type: 'string',
       name: 'myString'
   }]
}, ['2345675643', 'Hello!%']);
// Agora você pode enviar uma transação ou fazer uma chamada. Em ambos os
// casos você estará lidando com Promessas
web3.eth.sendTransaction({from: ..., to: ..., data: TransactionData, ...}).on(...);
web3.eth.call({from: ..., to: ..., data: TransactionData, ...}).on(...);
Enter fullscreen mode Exit fullscreen mode

Comunicação On-Chain para On-Chain

Você está implementando um contrato inteligente e gostaria de invocar a funcionalidade de outro contrato do seu código. Eu estarei usando a linguagem de programação Solidity que oferece algumas funções embutidas que geram a string de bytes compatível com a especificação abi.

Assim como na situação Off-chain para On-chain, existem dois cenários possíveis, você tem a interface de contrato inteligente ou não.

Com interface de contrato inteligente

Se você tiver a interface do contrato inteligente que deseja invocar, o solidity fará a maior parte do trabalho para você.

Você só precisa importar a interface para seu arquivo de contrato inteligente, instanciar um objeto do tipo interface passando o endereço do contrato inteligente e pronto. Agora você pode invocar os métodos do contrato como para qualquer outro objeto.

// Importa a interface e define o objeto do contrato usando a
// interface como um tipo de dado
import "IContract.sol";
IContract Contract;
// Instância o contrato com seu endereço
address contractAddress = 0x.......;
Contract = IContract(contractAddress);
// Invoca os métodos do contrato, conforme definidos pela interface
Contract.func1(....);
Enter fullscreen mode Exit fullscreen mode

Sem interface de contrato inteligente

Se você não tiver a interface do contrato, precisará criar a mensagem inteira.

Você precisará do endereço do contrato, a assinatura do método (nome do método e tipos de parâmetros de entrada separados por vírgulas) e os argumentos de entrada que deseja enviar (também separados por vírgulas).

// Endereço do contrato e assinatura da função
address contractAddress = 0x.......;
string memory Method = "func1(uint256,bool)";
// Define os dados em conformidade com a abi
bytes memory AbiData = abi.encodeWithSignature(Method, 345223, true);
// Envia a mensagem
(bool success, bytes memory data) = contractAddress.call(AbiData);
Enter fullscreen mode Exit fullscreen mode

Aviso

É importante observar que, independentemente da forma como você está interagindo com um contrato inteligente, se o endereço do contrato inteligente que estiver usando estiver errado, você ainda estará enviando a transação, não há verificação alguma. Se o contrato inteligente tiver uma função que corresponda à sua chamada, ele será executado, se não, a transação poderá falhar ou ter sucesso se o contrato inteligente tiver uma função “ fallback() ”… O ponto é que as consequências podem ser inesperadas e potencialmente indetectáveis, razão pela qual você deve ter certeza para qual contrato está enviando a transação, sempre verifique se o endereço do contrato é o correto.

Junte-se ao canal Coinmonks no Telegram e ao canal do Youtube para aprender sobre negociação e investimento em criptomoedas

Leia também


Artigo escrito por Alberto Molina e traduzido por Marcelo Panegali. O original pode ser encontrado aqui.

Oldest comments (0)