Ajută la actualizarea acestei pagini

🌏

Există o nouă versiune a acestei pagini, dar acum este doar în engleză. Ajută-ne să traducem cea mai recentă versiune.

Waffle: Simularea dinamică și testarea apelurilor de contracte

Despre ce este acest tutorial?

În acest tutorial vei învăța cum:

  • să utilizezi simularea dinamică
  • să testezi interacțiunile dintre contractele inteligente

Ipoteze:

  • știi deja cum să scrii un simplu contract inteligent în Solidity
  • te descurci în JavaScript și TypeScript
  • ai terminat alte tutoriale Waffle sau știi un lucru sau două despre ele

Simulare dinamică

De ce este utilă simularea dinamică? Ei bine, ne permite să scriem teste unitare în loc de teste de integrare. Ce înseamnă aceasta? Înseamnă că nu trebuie să ne facem griji cu privire la dependențele contractelor inteligente, astfel că le putem testa pe toate în mod izolat. Permite-mi să-ți arăt exact cum o poți face.

1. Proiect

Înainte de a începe, trebuie să pregătim un proiect simplu node.js:

$ mkdir dynamic-mocking
$ cd dynamic-mocking
$ mkdir contracts src
$ yarn init
# sau dacă utilizezi npm
$ npm init

Să începem cu adăugarea de typescript și dependențe de testare - mocha & chai:

$ yarn add --dev @types/chai @types/mocha chai mocha ts-node typescript
# sau dacă utilizezi npm
$ npm install @types/chai @types/mocha chai mocha ts-node typescript --save-dev

Acum să adăugăm Waffle și eter:

$ yarn add --dev ethereum-waffle ethers
# sau dacă utilizezi npm
$ npm install ethereum-waffle ethers --save-dev

Structura proiectului tău ar trebui să arate astfel:

1.
2├── contracts
3├── package.json
4└── test
5

2. Contracte inteligente

Pentru a începe simularea dinamică, avem nevoie de un contract inteligent cu dependențe. Nu-ți face griji, ne-am ocupat noi!

Iată un contract inteligent simplu scris în Solidity al cărui unic scop este să verificăm dacă suntem bogați. Acesta folosește tokenul ERC20 pentru a verifica dacă avem suficiente tokenuri. Să spunem că avem ./contracts/AmIRichAlready.sol.

1pragma solidity ^0.6.2;
2
3interface IERC20 {
4 function balanceOf(address account) external view returns (uint256);
5}
6
7contract AmIRichAlready {
8 IERC20 private tokenContract;
9 uint public richness = 1000000 * 10 ** 18;
10
11 constructor (IERC20 _tokenContract) public {
12 tokenContract = _tokenContract;
13 }
14
15 function check() public view returns (bool) {
16 uint balance = tokenContract.balanceOf(msg.sender);
17 return balance > richness;
18 }
19}
20
Afișează tot
📋 Copiere

Deoarece vrem să folosim simularea dinamică, nu avem nevoie de întregul ERC20, de aceea folosim interfața IERC20 cu o singură funcție.

Este timpul să construim acest contract! Pentru aceasta vom folosi Waffle. În primul rând, vom crea un fișier simplu de configurare Waffle.json care specifică opțiunile de compilare.

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

Acum suntem gata să construim contractul cu Waffle:

$ npx waffle

Simplu, nu? În directorul build/ au apărut două fișiere corespunzătoare contractului și interfeței. Le vom folosi mai târziu pentru testare.

3. Testare

Să creăm un fișier numit AmIRichAlready.test.ts pentru actualul test. În primul rând, trebuie să ne ocupăm de importuri. Vom avea nevoie de ele pentru mai târziu:

1import { expect, use } from "chai"
2import { Contract, utils, Wallet } from "ethers"
3import {
4 deployContract,
5 deployMockContract,
6 MockProvider,
7 solidity,
8} from "ethereum-waffle"
9

Cu excepția dependențelor JS, trebuie să importăm contractul și interfața construită:

1import IERC20 from "../build/IERC20.json"
2import AmIRichAlready from "../build/AmIRichAlready.json"
3

Waffle folosește chai pentru testare. Cu toate acestea, înainte de a-l putea folosi, trebuie să injectăm validatori-matcher Waffle chiar în chai:

1use(solidity)
2

Trebuie să implementăm funcția beforeEach() care va reseta starea contractului înainte de fiecare test. Să ne gândim mai întâi la ce avem nevoie acolo. Pentru a implementa un contract avem nevoie de două lucruri: (1) un portofel și (2) un contract ERC20 implementat pentru a-l transmite ca argument pentru contractul AmIRichAlready.

În primul rând creăm un portofel:

1const [wallet] = new MockProvider().getWallets()
2

Apoi, trebuie să implementăm un contract ERC20. Iată partea dificilă - avem doar o interfață. Aceasta este partea în care Waffle ne salvează. Waffle are o funcție magică deployMockContract () care creează un contract folosind doar abi-ul interfeței:

1const mockERC20 = await deployMockContract(wallet, IERC20.abi)
2

Acum, având portofelul și ERC20 implementate, putem continua cu implementarea contractului AmIRichAlready:

1const contract = await deployContract(wallet, AmIRichAlready, [
2 mockERC20.address,
3])
4

Cu toate acestea, funcția noastră beforeEach() este terminată. Până acum fișierul tău AmIRichAlready.test.ts ar trebui să arate astfel:

1import { expect, use } from "chai"
2import { Contract, utils, Wallet } from "ethers"
3import {
4 deployContract,
5 deployMockContract,
6 MockProvider,
7 solidity,
8} from "ethereum-waffle"
9
10import IERC20 from "../build/IERC20.json"
11import AmIRichAlready from "../build/AmIRichAlready.json"
12
13use(solidity)
14
15describe("Am I Rich Already", () => {
16 let mockERC20: Contract
17 let contract: Contract
18 let wallet: Wallet
19
20 beforeEach(async () => {
21 ;[wallet] = new MockProvider().getWallets()
22 mockERC20 = await deployMockContract(wallet, IERC20.abi)
23 contract = await deployContract(wallet, AmIRichAlready, [mockERC20.address])
24 })
25})
26
Afișează tot

Să scriem primul test la contractul AmIRichAlready. Despre ce crezi că ar trebui să fie testul nostru? Da, ai dreptate! Ar trebui să verificăm dacă suntem deja bogați :)

Dar așteaptă o secundă. Cum va ști contractul nostru simulat ce valori să returneze? Nu am implementat nicio logică pentru funcția balanceOf(). Din nou, Waffle poate fi de ajutor aici. Contractul nostru simulat are câteva lucruri noi sofisticate acum:

1await mockERC20.mock.<nameOfMethod>.returns(<value>)
2await mockERC20.mock.<nameOfMethod>.withArgs(<arguments>).returns(<value>)
3

Cu aceste cunoștințe putem, în sfârșit, scrie primul nostru test:

1it("returnează fals dacă portofelul are mai puțin de 1000000 tokenuri", async () => {
2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))
3 expect(await contract.check()).to.be.equal(false)
4})
5

Să descompunem acest test în părți:

  1. Am stabilit contractul nostru fals ERC20 pentru a returna întotdeauna soldul de 999999 de tokenuri.
  2. Am verificat dacă metoda contract.check() returnează false.

Suntem gata să dăm drumul fiarei:

Un test care trece

Deci, testul funcționează, dar... mai este loc pentru îmbunătățiri. Funcția balanceOf() va returna întotdeauna 99999. Putem îmbunătăți acest lucru prin specificarea unui portofel pentru care funcția ar trebui să returneze ceva - la del ca un contract real:

1it("returnează fals dacă portofelul are mai puțin de 1000001 tokenuri", async () => {
2 await mockERC20.mock.balanceOf
3 .withArgs(wallet.address)
4 .returns(utils.parseEther("999999"))
5 expect(await contract.check()).to.be.equal(false)
6})
7

Până acum, am testat doar cazul în care nu suntem suficient de bogați. Să testăm opusul în schimb:

1it("returnează true dacă portofelul are cel puțin 1000001 tokenuri", async () => {
2 await mockERC20.mock.balanceOf
3 .withArgs(wallet.address)
4 .returns(utils.parseEther("1000001"))
5 expect(await contract.check()).to.be.equal(true)
6})
7

Rulează testele...

Două teste care trec

...și iată! Contractul nostru pare să funcționeze conform scopului :)

Testarea apelurilor de contracte

Să rezumăm ce am făcut până acum. Am testat funcționalitatea contractului AmIRichAlready și se pare că funcționează corect. Asta înseamnă că am terminat, nu? Nu chiar! Waffle ne permite să testăm și mai mult contractul. Dar cum anume? Ei bine, în arsenalul Waffle există doi validatori-matcher calledOnContract() și calledOnContractWith(). Ei ne vor permite să verificăm dacă nu cumva contractul nostru a apelat contractul simulat ERC20. Iată un test de bază cu unul dintre acești validatori-matcher:

1it("contractul nostru este verificat dacă a apelat balanceOf pe tokenul ERC20", async () => {
2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))
3 await contract.check()
4 expect("balanceOf").to.be.calledOnContract(mockERC20)
5})
6

Putem merge chiar mai departe și putem îmbunătăți acest test cu celălalt validator-matcher de care am povestit:

1it("contractul nostru este verificat dacă a apelat balanceOf cu un anumit portofel pe tokenul ERC20", async () => {
2 await mockERC20.mock.balanceOf
3 .withArgs(wallet.address)
4 .returns(utils.parseEther("999999"))
5 await contract.check()
6 expect("balanceOf").to.be.calledOnContractWith(mockERC20, [wallet.address])
7})
8

Să verificăm dacă testele sunt corecte:

Trei teste care trec

Super, toate testele sunt verzi.

Testarea apelurilor de contracte cu Waffle este foarte ușoară. Și aici e partea cea mai bună. Acești validatori-matcher funcționează cu contracte normale sau simulate! Aceasta deoarece Waffle înregistrează și filtrează apelurile EVM în loc să injecteze cod, ca în cazul bibliotecilor populare de testare pentru alte tehnologii.

Linia de sosire

Felicitări! Acum știi cum să folosești Waffle pentru a testa în mod dinamic apelurile de contracte și contractele simulate. Există mult mai multe caracteristici interesante de descoperit. Îți recomandăm să studiezi în detaliu documentația Waffle.

Documentația Waffle este disponibilă aici.

Codul sursă pentru acest tutorial poate fi găsit aici.

Tutoriale care te pot interesa, de asemenea:

Ultima editare: , Invalid DateTime
Edit page