Vai al contenuto principale

Testare semplici contratti intelligenti con la libreria Waffle

contratti intelligentisolidityWaffletest
Principiante
Ewa Kowalska
26 febbraio 2021
6 minuti letti minute read

In questo tutorial imparerai come

  • Testare le modifiche del saldo del portafoglio
  • Testare l'emissione di eventi con gli argomenti specificati
  • Imporre il ripristino di una transazione

Premesse

  • Sei in grado di creare un nuovo progetto JavaScript o TypeScript
  • Hai delle esperienze di base con i test in JavaScript
  • Hai utilizzato dei gestori di pacchetti come yarn o npm
  • Possiedi conoscenze molto essenziali dei contratti intelligenti e di Solidity

Primi passi

Il tutorial dimostra la configurazione di prova e opera utilizzando yarn, ma non ci sono problemi se preferisci npm: fornirò gli adeguati riferimenti alla documentazione(opens in a new tab) ufficiale di Waffle.

Installa dipendenze

Aggiungi(opens in a new tab) ethereum-waffle e le dipendenze di TypeScript alle dipendenze di sviluppo del tuo progetto.

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

Esempio di contratto intelligente

Durante il tutorial lavoreremo a un esempio di contratto intelligente semplice: EtherSplitter. Non fa molto, tranne che consentire a chiunque di inviare wei e dividerli uniformemente tra due destinatari predefiniti. La funzione di divisione richiede che il numero di wei sia pari, altrimenti si ripristinerà. Per entrambi i destinatari esegue un trasferimento di wei, seguito dall'emissione dell'evento Trasferimento.

Posiziona il frammento del codice di EtherSplitter in 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}
Mostra tutto
Copia

Compila il contratto

Per compilare(opens in a new tab) il contratto, aggiungi il seguente elemento al file package.json:

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

Poi, crea un file di configurazione di Waffle nella cartella di root del progetto, waffle.json, e incolla qui la seguente configurazione:

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

Esegui yarn build. Di conseguenza, la cartella build apparirà con il contratto compilato di EtherSplitter nel formato JSON.

Testare la configurazione

Testare con Waffle richiede l'utilizzo di abbinatori Chai e Mocha, quindi, devi aggiungerli(opens in a new tab) al tuo progetto. Aggiorna il tuo file package.json e aggiungi l'elemento test nella parte degli script:

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

Se desideri eseguire(opens in a new tab) i tuoi test, basta eseguire yarn test.

Test

Ora, crea la cartella test e crea il nuovo file test\EtherSplitter.test.ts. Copia il seguente frammento e incollalo sul nostro file di test.

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})
Mostra tutto

Solo due parole prima di iniziare. MockProvider offre una versione fittizia della blockchain. Inoltre, fornisce portafogli fittizi che ci serviranno per testare il contratto di EtherSplitter. Possiamo ottenere fino a dieci portafogli chiamando il metodo getWallets() sul fornitore. Nell'esempio, otteniamo tre portafogli: per il mittente e per i due destinatari.

Quindi, dichiariamo una variabile detta "splitter" (divisore), che è il nostro contratto fittizio EtherSplitter. È creato prima di ogni esecuzione di un singolo test dal metodo deployContract. Questo metodo simula la distribuzione di un contratto dal portafoglio passato come primo parametro (nel nostro caso, il portafoglio del mittente). Il secondo parametro è l'ABI e il bytecode del contratto testato; qui, passiamo il file json del contratto compilato EtherSplitter dalla cartella build. Il terzo parametro è un insieme con gli argomenti del costruttore del contratto che, nel nostro caso, sono gli indirizzi dei due destinatari.

changeBalances

Prima controlleremo se il metodo di divisione modifica effettivamente i saldi dei portafogli dei destinatari. Se dividiamo 50 wei dall'account del mittente, i saldi di entrambi i destinatari dovrebbero aumentare di 25 wei. Utilizzeremo l'abbinatore di Waffle "changeBalances:

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

Come primo parametro dell'abbinatore, passiamo un insieme di portafogli dei destinatari e, come secondo, un insieme degli aumenti previsti sugli account corrispondenti. Se desiderassimo verificare il saldo di un portafoglio specifico, potremmo anche utilizzare l'abbinatore changeBalance, che non richiede di passare insiemi, come nel seguente esempio:

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

Nota che in entrambi i casi di changeBalance e changeBalances, passiamo la funzione di divisione come richiamata, poiché l'abbinatore deve accedere allo stato dei saldi prima e dopo la chiamata.

Poi, testeremo se l'evento Trasferimento è stato emesso dopo ogni trasferimento di wei. Ci rivolgeremo a un altro abbinatore da Waffle:

Emetti

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})
Mostra tutto

L'abbinatore emit ci consente di verificare se un contratto ha emesso un evento alla chiamata di un metodo. Come parametri all'abbinatore emit, forniamo il contratto fittizio che prevediamo emetterà l'evento, insieme al nome di tale evento. Nel nostro caso, il contratto fittizio è splitter e il nome dell'evento è Trasferimento. Inoltre, possiamo verificare i valori precisi degli argomenti con cui è stato emesso l'evento; passiamo altrettanti argomenti all'abbinatore withArgs, come previsto dalla dichiarazione del nostro evento. Nel caso del contratto EtherSplitter, passiamo gli indirizzi del mittente e del destinatario insieme all'importo trasferito di wei.

revertedWith

Come ultimo esempio verificheremo se la transazione è stata ripristinata, nel caso di un numero dispari di wei. Utilizzeremo l'abbinatore 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})

Il test, se superato, ci assicurerà che la transazione è stata effettivamente ripristinata. Tuttavia, deve anche verificarsi una corrispondenza esatta tra i messaggi passati nella dichiarazione require e il messaggio previsto in revertedWith. Se torniamo al codice del contratto EtherSplitter, nella dichiarazione require per l'importo di wei, forniamo il messaggio: "Importo di wei dispari non consentito". Questo corrisponde al messaggio che ci aspettiamo nel nostro test. Se non fossero stati uguali, il test sarebbe fallito.

Congratulazioni!

Hai compiuto il tuo primo grande passo verso il test dei contratti intelligenti con Waffle! Potresti essere interessato ad altri tutorial di Waffle:

  • Testare ERC-20 con Waffle
  • Waffle: simulazioni dinamiche e test delle chiamate del contratto
  • Tutorial Waffle Hello world con hardhat ed ethers

Ultima modifica: @nhsz(opens in a new tab), 27 febbraio 2024

Questo tutorial è stato utile?