WEB3DEV

Cover image for Testando contratos inteligentes localmente utilizando Geth
Paulo Gio
Paulo Gio

Posted on • Atualizado em

Testando contratos inteligentes localmente utilizando Geth

Este tutorial irá guiá-lo através da implantação e teste de seu contrato inteligente em uma blockchain privada e local que você criou usando Geth.

Por que publicaríamos um contrato inteligente em uma cadeia local privada? Principalmente para fins de teste. É assim que podemos testar os contratos localmente sem publicá-los em uma rede mais larga para que todos vejam. E fazer tudo isso é gratuito, pois podemos alocar tanto Ether quanto quisermos às contas que possuímos por meio de nosso arquivo Genesis.

Se você achar este post útil, eu o encorajo a seguir minha conta no Twitter, onde eu posto conteúdo baseado em Ethereum e blockchain, tutoriais e explicações de baixo nível.

Testando contratos inteligentes localmente utilizando Geth https://t.co/P0vxqY6Znt - Brandon Arvanaghi (@arvanaghi) 15 de fevereiro de 2018

Pré-requisitos

1) Instale o Solidity

Solidity é uma linguagem de alto nível orientada a contratos para implementação de contratos inteligentes. Aqui está a explicação de como você pode instalá-lo.

2) Você precisará configurar uma blockchain Ethereum privada usando Geth.

Todo este tutorial é baseado na conclusão bem-sucedida do item 2.

Contrato a compilar

Usaremos o contrato Greeter.sol da ethereum.org. Ele envia uma mensagem e você pode alterar essa mensagem se for o proprietário do contrato.

// Greeter.sol
contract mortal {
    /* Defina o proprietário da variável do tipo "address" */
    address owner;

    /* Esta função é executada na inicialização e define o proprietário do contrato */
    function mortal() { owner = msg.sender; }

    /* Função para recuperar os fundos do contrato */
    function kill() { if (msg.sender == owner) selfdestruct(owner); }
}

contract greeter is mortal {
    /* Definir variável "greeting" do tipo "string" */
    string greeting;

    /* Isso é executado quando o contrato é executado */
    function greeter(string _greeting) public {
        greeting = _greeting;
    }

    /* Função principal */
    function greet() constant returns (string) {
        return greeting;
    }
}
Enter fullscreen mode Exit fullscreen mode

Uma vez que você tem este código em um arquivo Greeter.sol, faça o seguinte em uma janela do Terminal:

arvanaghi> solc -o target --bin --abi Greeter.sol
Enter fullscreen mode Exit fullscreen mode

Conforme explicado no site ethereum.org, isso gera um diretório target contendo:

  • Greeter.abi
  • Greeter.bin
  • Mortal.abi
  • Mortal.bin

Observando o código do contrato greeter, vemos

"contract greeter is mortal {"
Enter fullscreen mode Exit fullscreen mode

Isso significa que o contrato greeter herda todas as funções e variáveis do contrato mortal, portanto, o Greeter.abi e o Greeter.bin resultantes contêm todo o código de que precisamos. Você também pode descartar Mortal.abi e Mortal.bin, pois seus dados já estão contidos nos arquivos Greeter.

Greeter.bin

O arquivo Greeter.bin contém o contrato Greeter compilado. Se, a partir do Terminal, você fizer cat Greeter.bin, você verá vários valores hexadecimais. O conteúdo deste arquivo é o que é colocado na blockchain Ethereum. Todo o código executável necessário para a Máquina Virtual Ethereum (Ethereum Virtual Machine) interpretar existe neste arquivo.

Greeter.abi

ABI significa Interface Binária de Aplicação (Application Binary Interface). É uma maneira de interagir com a saída hexadecimal do Greeter.bin (que são os dados binários armazenados na blockchain) de forma legível.

Se você fizer cat Greeter.abi do Terminal, verá algo legível por humanos, já que, por convenção, clientes e compiladores da Ethereum usam JSON para representar os dados.

arvanaghi> cat Greeter.abi
[{"constant":false,"inputs":[],"name":"kill","outputs":[],"payable":false ... ]
Enter fullscreen mode Exit fullscreen mode

A ABI produzida para o contrato precisa ter todos os detalhes relevantes sobre cada função, variável e seus atributos. Se houver uma incompatibilidade no que a ABI diz sobre uma função ou variável com o que o código de contrato compilado diz (por exemplo, sua ABI diz que uma função aceita uma string como argumento, mas o código compilado espera um uint), então essa interação que você faz com o contrato publicado através da ABI irá falhar.

O código do contrato compilado é a verdade. A ABI é uma abstração para facilitar a sua interação com ele.

Escrevendo um contrato para sua cadeia privada

Certifique-se de ter configurado uma blockchain Ethereum privada usando Geth. As etapas a seguir ocorrem no console em execução no Nó 1 deste post.

Armazenando o bytecode

Quando publicamos o contrato em nossa blockchain privada, na verdade estamos publicando o bytecode compilado (o conteúdo do arquivo Greeter.bin), pois é isso que a EVM sabe interpretar. Vamos criar uma variável em nosso console Geth para armazenar o conteúdo do arquivo Greeter.bin.

Como os dados em Greeter.bin são codificados em hexadecimal, devemos adicionar manualmente “0x” ao início da variável que criamos para informar à EVM que estamos fornecendo os códigos em valores hexadecimais. Copie e cole o conteúdo de Greeter.bin em uma nova variável greeterHex da seguinte forma:

// Do console Geth do Nó 1
> greeterHex = "0x<copy and paste the contents of Greeter.bin here>"
Enter fullscreen mode Exit fullscreen mode

Você pode testar se a variável armazenou os dados do contrato corretamente digitando greeterHex no console novamente.

Armazenando a ABI

O bytecode que acabamos de armazenar em uma variável é o código que realmente viverá na blockchain. É também como todos os comandos que enviamos para o contrato serão interpretados. Por sermos humanos e não robôs, usamos a ABI para facilitar nossas vidas. Especificamente, sem uma ABI, teríamos que interagir com as funções do contrato inteligente conhecendo a codificação hexadecimal dessas funções e o que elas significam.

Vamos armazenar o conteúdo de greeter.abi em uma variável assim (saída truncada):

// Do console Geth do Nó 1
> greeterAbi = [{"constant":false,"inputs":[],"name":"kill","outputs":[] ... ]
Enter fullscreen mode Exit fullscreen mode

Se formatado corretamente, o Geth interpretará os dados que você forneceu como JSON e os apresentará de volta para você:

Neste momento, o Geth sabe apenas que os dados que você forneceu eram JSON e estavam formatados corretamente. Ele não sabe tratá-los como uma ABI para um contrato que você publicará em breve.

Para deixar o Geth ciente de que o JSON em nossa variável deve ser tratado como uma ABI, fazemos o seguinte:

// Do console Geth do Nó 1
> greeterInterface = eth.contract(greeterAbi)
Enter fullscreen mode Exit fullscreen mode

O que eth.contract() faz é tratar o JSON passado para ele como uma ABI para um contrato. Isso significa que, se agora fizéssemos o greeterInterface.greet() (após ter publicado o contrato na blockchain), o Geth saberia que o greeterInterface é um meio de criar o bytecode correspondente necessário para chamar a função greet() no contrato.

Publicação do contrato

Agora que temos nossas variáveis definidas, é hora de publicar nosso contrato em nossa blockchain local.

Aqui está o comando para publicar o contrato, ligeiramente modificado do que vemos no tutorial de contrato do Geth. Mais documentações podem ser encontradas aqui.

var greeterTx = greeterInterface.new(
  "Esta é a saudação com a qual iremos instanciar o contrato. Olá!",
  {
    from: eth.accounts[0],
    data: greeterHex,
    gas: 1000000
  }
)
Enter fullscreen mode Exit fullscreen mode

Se você receber um erro durante esta etapa sobre a necessidade de desbloquear sua conta, digite personal.unlockAccount(eth.accounts[0]) e insira sua senha.

Depois de concluir esta etapa, você deve obter algo assim:

INFO [MM-DD|HH:MM:SS] Submitted contract creation              fullhash=0x830e6922af10081455de296683d959dde01588473326db251116ab72c99ec426
contract=0xA94C943B7b1Fe87E0b061109411Cde360454453C
undefined
Enter fullscreen mode Exit fullscreen mode

Você enviou o contrato para a rede. Como ainda não o mineramos, não é utilizável, porque ainda está no pool de transações nos sistemas dos mineradores. No entanto, podemos vê-lo no pool de transações de qualquer nó para o qual acabamos de transmitir.

Um ponto importante: embora o contrato ainda não tenha sido minerado, o endereço que você vê acima será exatamente o mesmo quando estiver na blockchain. O endereço do contrato é gerado em parte pelo endereço que o criou, assim como um nonce. Assim, quando o contrato estiver realmente na blockchain, qualquer nó que queira interagir com o contrato estará usando esse mesmo endereço que o Geth acabou de nos fornecer.

O valor fullhash acima é o hash da transação, ou como podemos rastrear nossa transação nos nós de todos. Usar algo como Etherscan e alimentá-lo com esse valor de hash permitirá que você veja o status de sua transação.

Podemos armazenar esse hash da transação em uma variável sem ter que copiar e colar fazendo o seguinte a partir do Nó 1:

> greeterTxHash = greeterTx.transactionHash
Enter fullscreen mode Exit fullscreen mode

Como mencionei, essa transação também existe no Nó 2 (desde que a publicamos), mas ainda não existe nas blockchains dos Nós. Ela está no pool de transações para ambos.

Execute o seguinte em qualquer nó para ver a transação “pendente”:

> txpool.status
Enter fullscreen mode Exit fullscreen mode

Você verá 1 transação “pendente” nos pools de ambos os Nós. Para uma visualização mais detalhada, insira txpool sozinho.

Se quiséssemos ver um recibo de transação, ele ainda não existiria:

> eth.getTransactionReceipt(greeterTxHash)
null
Enter fullscreen mode Exit fullscreen mode

Um recibo de transação é criado assim que a transação for realmente minerada em um bloco válido e aceita pela rede.

Minerando o contrato

A partir de qualquer um dos nós, execute miner.start() e, depois de alguns segundos, execute miner.stop().

Agora, nosso contrato deve ser publicado. Tente o mesmo comando eth.getTransactionReceipt(greeterTxHash) novamente e você obterá um recibo.

Como lembrete, o endereço do contrato publicado será o mesmo que o Geth nos deu quando criamos o contrato em primeiro lugar. Outra maneira de recuperar esse endereço é executando o seguinte comando do Nó 1:

publicadoGreeterAddr = eth.getTransactionReceipt(greeterTxHash).contractAddress
Enter fullscreen mode Exit fullscreen mode

Agora, usamos a interface que criamos anteriormente da ABI e a vinculamos ao contrato publicado:

>greeterInterface.at(publishedGreeterAddr).greet()
"Esta é a saudação com a qual iremos instanciar o contrato. Olá!"
Enter fullscreen mode Exit fullscreen mode

Para vê-lo funcionando no Nó 2, você precisará do endereço do contrato publicado e sua ABI. De uma só vez, você pode fazer tudo assim:

// Do console Geth do Nó 2
> greeterAbi = <cole o arquivo greeter.abi aqui>
> greeterInterface = eth.contract(greeterAbi)
> publishedGreeterAddr = <cole o endereço armazenado no GreeterAddr publicado do  1 aqui>
> greeter = greeterInterface.at(publishedGreeterAddr)
> greeter.greet()
"Esta é a saudação com a qual iremos instanciar o contrato. Olá!"
Enter fullscreen mode Exit fullscreen mode

Novamente, a ABI pode ser vinculada a qualquer contrato em seu nó local, mas o bytecode resultante que você enviar para esse contrato na blockchain será malformado e, portanto, rejeitado, a menos que você realmente tenha a ABI correta para o contrato com o qual deseja interagir.

Este artigo foi escrito por Brandon Arvanaghi, e traduzido por Paulinho Giovannini. Encontre o artigo original aqui.

Top comments (0)