Testando um contrato inteligente simples com a biblioteca Waffle
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;23contract EtherSplitter {4 address payable receiver1;5 address payable receiver2;67 event Transfer(address from, address to, uint256 amount);89 constructor(address payable _address1, address payable _address2) public {10 receiver1 = _address1;11 receiver2 = _address2;12 }1314 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 tudoCopiar
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"56use(solidity)78describe("Ether Splitter", () => {9 const [sender, receiver1, receiver2] = new MockProvider().getWallets()10 let splitter: Contract1112 beforeEach(async () => {13 splitter = await deployContract(sender, EtherSplitter, [14 receiver1.address,15 receiver2.address,16 ])17 })1819 // add the tests here20})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 255 )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})67it("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