Pular para o conteúdo principal

Testando um contrato inteligente simples com a biblioteca Waffle

contratos inteligentessolidityWaffletestando
Intermediário
Ewa Kowalska
26 de fevereiro de 2021
6 minutos de leitura minute read

Neste tutorial, você aprenderá como

  • Testar as mudanças do saldo da carteira
  • Testar a emissão de eventos com argumentos especificados
  • Assegurar que uma transação foi revertida

Suposições

  • Você pode criar um novo projeto JavaScript ou TypeScript
  • Você tem alguma experiência básica com testes em JavaScript
  • Você tem usado gerenciadores de pacotes como Yarn ou NPM
  • Você possui um conhecimento muito básico de contratos inteligentes e Solidity

Introdução

O tutorial demonstra a configuração do teste e a execução usando yarn, mas não há problema se você preferir npm - Eu fornecerei referências adequadas a documentação(opens in a new tab) oficial do Waffle.

Instalando Dependências

Adicione(opens in a new tab) as dependências do ethereum-waffle e typescript às dependências de desenvolvimento do seu projeto.

yarn add --dev ethereum-waffle ts-node typescript @types/jest

Exemplo de contrato inteligente

Durante o tutorial, nós trabalharemos em um exemplo de contrato inteligente simples - EtherSplitter. Não faz nada de mais, além de permitir que qualquer um envie somas em wei e divida-as igualmente entre dois destinatários predefinidos. A função split exige que a quantidade de wei seja par, caso contrário, ela será anulada. Para ambos os destinatários, ela realiza uma transferência em wei, seguido da emissão do evento Transferir.

Coloque o trecho de código EtherSplitter em src/EtherSplitter.sol.

1pragma solidity ^0.6.0;
2
3contract EtherSplitter {
4 address payable receiver1;
5 address payable receiver2;
6
7 event Transfer(address from, address to, uint256 amount);
8
9 constructor(address payable _address1, address payable _address2) public {
10 receiver1 = _address1;
11 receiver2 = _address2;
12 }
13
14 function split() public payable {
15 require(msg.value % 2 == 0, 'Uneven wei amount not allowed');
16 receiver1.transfer(msg.value / 2);
17 emit Transfer(msg.sender, receiver1, msg.value / 2);
18 receiver2.transfer(msg.value / 2);
19 emit Transfer(msg.sender, receiver2, msg.value / 2);
20 }
21}
Exibir tudo
Copiar

Compilar o contrato

Para compilar(opens in a new tab) o contrato, adicione a seguinte entrada ao arquivo package.json:

1"scripts": {
2 "build": "waffle"
3 }
Copiar

Em seguida, crie o arquivo de configuração do Waffle, no diretório raiz do projeto - waffle.json - e então cole a seguinte configuração lá:

1{
2 "compilerType": "solcjs",
3 "compilerVersion": "0.6.2",
4 "sourceDirectory": "./contracts",
5 "outputDirectory": "./build"
6}
Copiar

Execute yarn build. Como resultado, o diretório build aparecerá com o contrato compilado, EtherSplitter, no formato JSON.

Teste de configuração

Testar com Waffle requer usar os matchers (comparadores) Chai e Mocha, então você precisa adicionar(opens in a new tab) ao seu projeto. Atualize seu arquivo package.json e adicione a entrada test na parte de scripts:

1"scripts": {
2 "build": "waffle",
3 "test": "export NODE_ENV=test && mocha -r ts-node/register 'test/**/*.test.ts'"
4 }
Copiar

Se você quiser executar(opens in a new tab) seus testes, basta executar yarn test.

Testando

Agora crie o diretório test e crie o novo arquivo test\EtherSplitter.test.ts. Copie o trecho de código abaixo e cole-o em nosso arquivo de teste.

1import { expect, use } from "chai"
2import { Contract } from "ethers"
3import { deployContract, MockProvider, solidity } from "ethereum-waffle"
4import EtherSplitter from "../build/EtherSplitter.json"
5
6use(solidity)
7
8describe("Ether Splitter", () => {
9 const [sender, receiver1, receiver2] = new MockProvider().getWallets()
10 let splitter: Contract
11
12 beforeEach(async () => {
13 splitter = await deployContract(sender, EtherSplitter, [
14 receiver1.address,
15 receiver2.address,
16 ])
17 })
18
19 // add the tests here
20})
Exibir tudo

Algumas palavras antes de começarmos. O MockProvider vem com uma versão em mock (simulada de um objeto real) da blockchain. Ele também fornece o mock de carteiras que nos servirão para testar o contrato EtherSplitter. Podemos obter até dez carteiras chamando o método getWallets() no provedor. No exemplo, nós obtemos três carteiras - para o remetente e duas para os destinatários.

Em seguida, declaramos uma variável chamada 'splitter' - este é o nosso contrato mock EtherSplitter. Ele é criado antes de cada execução de um único teste pelo método deployContract. Este método simula a implantação de um contrato, da carteira passada como primeiro parâmetro (a carteira do remetente em nosso caso). O segundo parâmetro é a ABI e o bytecode do contrato testado — passamos para lá o arquivo json do contrato EtherSplitter compilado no diretório build. O terceiro parâmetro é uma matriz com os argumentos do construtor do contrato que, no nosso caso, são os dois endereços dos destinatários.

changeBalances

Primeiro, verificaremos se o método split realmente altera os saldos das carteiras dos destinatários. Se dividirmos 50 wei da conta do remetente, nós esperaríamos que os saldos de ambos os destinatários aumentassem em 25 wei. Nós usaremos o matcher changeBalances do Waffle:

1it("Changes accounts balances", async () => {
2 await expect(() => splitter.split({ value: 50 })).to.changeBalances(
3 [receiver1, receiver2],
4 [25, 25]
5 )
6})

Como o primeiro parâmetro do matcher, nós passamos um array de carteiras dos destinatários e, como segundo - um array de aumentos esperados nas contas correspondentes. Se nós quiséssemos verificar o saldo de uma carteira específica, também poderíamos usar o matcher changeBalance, que não requer a passagem de arrays, como no exemplo abaixo:

1it("Changes account balance", async () => {
2 await expect(() => splitter.split({ value: 50 })).to.changeBalance(
3 receiver1,
4 25
5 )
6})

Observe que, em ambos os casos de changeBalance e de changeBalances, transmitimos a função split como um retorno de chamada, pois o comparador precisa acessar o estado dos saldos antes e depois da chamada.

A seguir, testaremos se o evento Transfer foi emitido após cada transferência de wei. Vamos passar para outro comparador do Waffle:

Emit

1it("Emits event on the transfer to the first receiver", async () => {
2 await expect(splitter.split({ value: 50 }))
3 .to.emit(splitter, "Transfer")
4 .withArgs(sender.address, receiver1.address, 25)
5})
6
7it("Emits event on the transfer to the second receiver", async () => {
8 await expect(splitter.split({ value: 50 }))
9 .to.emit(splitter, "Transfer")
10 .withArgs(sender.address, receiver2.address, 25)
11})
Exibir tudo

O matcher emit nos permite verificar, se um contrato emitiu um evento ao chamar um método. Como parâmetros para o matcher emit, nós fornecemos o mock do contrato, que prevemos para emitir o evento, juntamente com o nome desse evento. Em nosso caso, o contrato simulado é o splitter e o nome do evento é Transfer. Nós também podemos verificar os valores precisos dos argumentos, com os quais o evento foi emitido - nós passamos tantos argumentos para o matcher withArgs, como espera a nossa declaração de evento. No caso do contrato EtherSplitter, passamos os endereços do remetente e do destinatário, juntamente com a quantia de wei transferida.

revertedWith

Como último exemplo, nós verificaremos se a transação foi revertida, em caso de número desigual de wei. Usaremos o matcher revertedWith:

1it("Reverts when Vei amount uneven", async () => {
2 await expect(splitter.split({ value: 51 })).to.be.revertedWith(
3 "Uneven wei amount not allowed"
4 )
5})

O teste, se aprovado, nos garantirá que a transação foi revertida de fato. No entanto, também deve haver uma correspondência exata entre as mensagens que passamos, na instrução require e a mensagem que esperamos em revertedWith. Se voltarmos ao código do contrato EtherSplitter, na declaração require para a quantidade wei, fornecemos a mensagem: 'Quantidade de wei desigual não permitida'. Isso corresponde à mensagem que esperamos em nosso teste. Se eles não fossem iguais, o teste falharia.

Parabéns!

Você acabou de dar seu primeiro grande passo para testar contratos inteligentes com Waffle! Caso esteja interessado em outros tutoriais do Waffle:

  • Testando ERC20 com Waffle
  • Waffle: simulações dinâmicas e testando chamadas de contrato
  • Waffle diga olá mundo tutorial com capacete de segurança e ethers

Última edição: @nhsz(opens in a new tab), 27 de fevereiro de 2024

Este tutorial foi útil?