WEB3DEV

Cover image for Testandos Contratos Solidity Unitários na Ethereum com Go
Panegali
Panegali

Posted on • Atualizado em

Testandos Contratos Solidity Unitários na Ethereum com Go

Contratos inteligentes na blockchain Ethereum podem ser escritos na linguagem Solidity. Existem várias ferramentas e estruturas disponíveis para testar esses contratos inteligentes. Este artigo demonstra como testar estes contratos de maneira segura, sem sacrificar a simultaneidade.

Meu amigo me mostrou recentemente como escrever contratos inteligentes para aplicativos descentralizados (ou dapps de forma abreviada) usando a linguagem de programação Solidity para a blockchain Ethereum. Ele mencionou um projeto que estava desenvolvendo com o framework Truffle. É uma ferramenta muito popular pelo que tenho ouvido e visto, e é complementada por uma “confeitaria” de ferramentas relacionadas, com nomes deliciosos como Ganache e Drizzle. Exceto que uma vez que eu comecei a dar uma mordida real neles, eles não deixaram um sabor muito agradável. Outros frameworks de teste como Embark, dapple e Populus também existem em diferentes ecossistemas de bibliotecas. Mas todos eles são escritos em Python ou JavaScript.

Venho de um background Java / Scala e, mais recentemente, tenho desenvolvido muitos serviços escritos na linguagem de programação Go para software na Tesla. Portanto, segurança tipográfica e simultaneidade significam muito para mim. E por essas razões, Python e JavaScript estão entre as minhas linguagens de programação menos favoritas para escrever e manter código escalável. Passei algum tempo, alguns meses antes de escrever este artigo, vasculhando os repositórios de origem de implementações de blockchain como Bitcoin, LiteCoin, Ethereum, Ripple e Monero. E de todo o código que eu realmente olhei, a implementação do go-ethereum se destacou como uma estrela brilhante para mim. O código era fácil de ler, simples de construir e bem documentado. É efetivamente a implementação líder da Ethereum. Muitos dos exemplos que encontrei on-line para testar os contratos do Solidity foram baseados no kit de ferramentas web3.js para a especificação Ethereum JSON-RPC, o que foi ótimo para começar geth console, mas eu queria algo mais simples. E eu queria algo que interagisse diretamente no processo geth, em oposição a algo envolto em várias camadas de “penugem açucarada” ou algo que exigisse que eu testasse contratos em um nó ativo.

Simulador de Blockchain em go-ethereum

Fui à procura de bibliotecas de teste unitários Solidity escritas em Go e tive o prazer de encontrar uma página wiki sobre ligações Go para contratos Ethereum. Fiquei especialmente emocionado ao descobrir neste artigo que go-ethereum vem com um simulador de blockchain. Deixe-me repetir isso: a implementação Go da Ethereum vem com um simulador de blockchain! "Isso é muito ruim", pensei comigo mesmo.

Mas ao seguir os exemplos linha por linha no wiki, fiquei um pouco desanimado ao encontrar trechos de código que eram incompatíveis com a versão da fonte go-ethereum (master@d2fe83d) que eu havia clonado do Github. Agora, sinta-se à vontade para pular para a próxima seção se meus detalhes de solução de problemas dos exemplos do wiki o aborreceram, apenas saiba que eventualmente os fiz funcionar :) Mas para o leitor mais curioso, o código específico que quebrou com minha compilação foi:

sim := backends.NewSimulatedBackend(
       core.GenesisAccount{
               Address: auth.From,
               Balance: big.NewInt(10000000000),
       })
Enter fullscreen mode Exit fullscreen mode

que resulta no erro de compilação:

cannot use core.GenesisAccount literal (type core.GenesisAccount) as type core.GenesisAlloc in argument to backends.NewSimulatedBackend

É preciso amar os compiladores, certo? Ok, então o problema com esta compilação é que o manuseio da conta gênesis foi modificado no Geth 1.6.0 e o artigo de @karalabehttps://github.com/karalabe foi criado apenas alguns meses antes desse lançamento. O tipo de assinatura do método de construção NewSimulatedBackend agora se parece com isso:

type GenesisAlloc map[common.Address]GenesisAccount

func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend
Enter fullscreen mode Exit fullscreen mode

O que significa que a maneira correta de criar uma blockchain Ethereum simulada a partir de 1.6.0 é algo como:

gAlloc := map[common.Address]core.GenesisAccount{       auth.From: {Balance: big.NewInt(10000000000)},
}
sim := backends.NewSimulatedBackend(gAlloc)
Enter fullscreen mode Exit fullscreen mode

Tenho que amar esse tipo de inferência. Imagine tentar descobrir isso em Ruby, Python ou JavaScript. Não que isso não possa ser feito, apenas não seria bonito. Tudo bem, então isso faz com que as coisas funcionem com as ligações fornecidas na página wiki vinculada acima, que me fez começar com este pacote. Agora vamos escrever alguns testes com um exemplo mais simples e atualizado.

Teste Unitário: Hello World

Em primeiro lugar, eu encorajo todos os desenvolvedores de software a aprender um pouco de Go. Eu estava relutante alguns anos atrás em aprender mais uma linguagem, especialmente depois de ter investido muito em Java e Scala, e na época eu inicialmente rubriquei como apenas um modismo ordenado pelo Google. Mas quanto mais eu leio e escrevo em Go, mais fico impressionado com a qualidade do software que encontro escrito em Go, a complexidade de projetos Go bem-sucedidos (por exemplo, Docker, Kubernetes, etc.), a comunidade de desenvolvedores Go, sua taxa de adoção, a diversidade de suas bibliotecas e a simplicidade de seu uso. Vamos baixar, instalar e construir as seguintes dependências como pré-requisito para nosso contrato inteligente Hello World:

Se esta é a primeira vez que você usa o Go, você pode baixar e instalar qualquer uma das distribuições mais recentes na página inicial da golang. Os exemplos que preparei abaixo funcionam com o Go 1.9.2. Depois de instalar o Go, clone o código-fonte do go-ethereum e faça o checkout da tag mais recente (que no momento da escrita deste artigo é a versão v1.8.7) para que possamos construir a versão estável mais recente:

$ cd $GOPATH/src/github.com/ethereum
$ git clone https://github.com/ethereum/go-ethereum.git
$ cd go-ethereum
$ git checkout v1.8.7
Enter fullscreen mode Exit fullscreen mode

Em seguida, queremos instalar as ferramentas necessárias para compilar nossos arquivos Solidity. A maioria das pessoas que já trabalha com Solidity estará familiarizada com serviços da Web como Remix ou a API SolC para compilar e gerar ligações Ethereum a partir de arquivos *.sol. Para escrever nossos testes de unidade, queremos compilar e gerar essas ligações localmente. Para fazer isso, instalaremos a ferramenta abigen que acompanha o go-ethereum a partir de sua pasta de origem:

$ go install ./cmd/abigen

e também a ferramenta solc que você pode fazer seguindo as instruções em solidity.readthedocs.io - embora se você estiver desenvolvendo no Mac OS X, em vez do NPM, eu recomendo instalar o Solidity Compiler usando o Homebrew:

$ brew tap ethereum/ethereum

$ brew install solidity

Agora, em um diretório de trabalho de sua escolha, vamos escrever nosso contrato inteligente “Hello World” em um arquivo chamado helloworld.sol:

pragma solidity ^0.4.23;
contract helloworld {
   function say() public pure returns (string) {
       return 'hello etherworld';
   }
}
Enter fullscreen mode Exit fullscreen mode

Em seguida, geramos as ligações Go para este contrato simples usando o comando:

$ abigen --sol helloworld.sol --pkg main --out helloworld.go

A ferramenta abigen não apenas gerará ligações Go nativas para seu contrato Solidity, mas também invocará o comando solc para produzir uma string de interface binária de aplicativo (ABI) (rotulada como a constante HelloworldABI em helloworld.go), bem como o bytecode (rotulado HelloworldBin) que será executado em nossa máquina virtual simulada Ethereum. Anexei o resumo ao arquivo de saída que gerei com o comando acima para referência, mas sugiro que você tente o codegen para verificar se suas ferramentas estão configuradas corretamente em sua máquina de desenvolvimento local. Com esta ligação Go em mãos, agora podemos escrever um teste para nossa unidade Say() que simplesmente espera a string "hello etherworld" de saída desta função:

package main

import (
    "math/big"
    "testing"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/stretchr/testify/suite"
)

type HelloworldTestSuite struct {
    suite.Suite
    auth       *bind.TransactOpts
    address    common.Address
    gAlloc     core.GenesisAlloc
    sim        *backends.SimulatedBackend
    helloworld *Helloworld
}

func TestRunHelloworldSuite(t *testing.T) {
    suite.Run(t, new(HelloworldTestSuite))
}

func (s *HelloworldTestSuite) SetupTest() {
    key, _ := crypto.GenerateKey()
    s.auth = bind.NewKeyedTransactor(key)

    s.address = s.auth.From
    s.gAlloc = map[common.Address]core.GenesisAccount{
        s.address: {Balance: big.NewInt(10000000000)},
    }

    s.sim = backends.NewSimulatedBackend(s.gAlloc)

    _, _, hw, e := DeployHelloworld(s.auth, s.sim)
    s.helloworld = hw
    s.Nil(e)
    s.sim.Commit()
}

func (s *HelloworldTestSuite) TestSay() {
Enter fullscreen mode Exit fullscreen mode

Agora vamos em frente e executar nosso teste de unidade de contrato inteligente recém-criado usando:

$ go test -v helloworld*.go
=== RUN   TestRunHelloworldSuite
=== RUN   TestRunHelloworldSuite/TestSay
--- PASS: TestRunHelloworldSuite (0.00s)
--- PASS: TestRunHelloworldSuite/TestSay (0.00s)
PASS
ok   command-line-arguments 0.041s
Enter fullscreen mode Exit fullscreen mode

e voilá! todos os nossos testes de unidade de contrato inteligente passam sem ter que executar ou configurar uma rede privada, ou ter que baixar e instalar um complemento “açucarado”.

No código de teste acima, estou usando um pacote Go chamado testify que achei muito útil para organizar grandes coleções de testes de unidade semelhantes em suítes — por exemplo, cada contrato pode ter uma suíte para a qual os testes são escritos. Todo o estado de teste para reutilização entre as unidades é encapsulado no HelloworldTestSuite e, em seguida, conectado a SetupTest para ser configurado com um simulador de blockchain. Primeiro, criamos uma chave privada crypto.GenerateKey() que será usada para preencher uma função de assinante para autorizar transações em nosso backend Ethereum simulado. A função bind.NewKeyedTransactor retorna um *TransactOpts que aponta para um endereço a partir do qual podemos fazer transações. Usaremos este endereço para criar um bloco gênese com uma conta alocada com fundos e, em seguida, criar nossa blockchain simulada com essa conta por meio do mecanismo de manipulação de gênese modificado descrito anteriormente. Em seguida, implantamos nosso contrato “hello world” com DeployHelloworld onde s.auth aponta para todos os dados de autorização necessários para fazer transações da conta gerada e s.sim aponta para uma blockchain totalmente simulada apoiado por um banco de dados na memória. A transação para implantar este contrato permanece em estado “pendente” até que s.sim.Commit() seja enviada a blockchain, que disponibiliza o código do contrato no endereço fornecido. Finalmente, podemos invocar e testar a saída de nossa função s.helloworld.Say, que pode ser ajustada com opções de chamada de contrato não nulas.

E aí está: um contrato inteligente escrito em Solidity, testado em Go sem precisar acionar um nó ou conectar-se a uma rede.


Este artigo foi escrito por Nathan Murthy e traduzido por Marcelo Panegali. O artigo original pode ser encontrado aqui.

Top comments (0)