Ana içeriğe geç

Waffle: Dinamik taklit ve sözleşme çağrılarını test etme

waffleakıllı sözleşmelerkatılıktesttaklit
Orta düzey
Daniel Izdebski
14 Kasım 2020
6 dakikalık okuma minute read

Bu öğretici ne ile ilgili?

Bu eğitimde şunları nasıl yapacağınızı öğreneceksiniz:

  • dinamik taklit kullanımı
  • akıllı sözleşmeler arasındaki test etkileşimleri

Varsayımlar:

  • Solidity'de basit bir akıllı sözleşmenin nasıl yazılacağını zaten biliyorsunuz
  • JavaScript ve TypeScript'e aşinasınız
  • başka Waffle öğreticilerini tamamladınız veya bu konuda bir iki şey biliyorsunuz

Dinamik taklit

Dinamik taklit neden yararlıdır? Şey, entegrasyon testleri yerine birim testleri yazmamıza izin veriyor. Bu ne demek? Bu, akıllı sözleşmelerin bağımlılıkları hakkında endişelenmemize gerek olmadığı anlamına gelir, böylece hepsini tamamen ayrı ayrı test edebiliriz. Size tam olarak nasıl yapabileceğinizi göstermeme izin verin.

1. Proje

Başlamadan önce basit bir node.js projesi hazırlamamız gerekiyor:

mkdir dynamic-mocking
cd dynamic-mocking
mkdir contracts src
yarn init
# or if you're using npm
npm init

Typescript ve test bağımlılıkları ekleyerek başlayalım - mocha ve chai:

yarn add --dev @types/chai @types/mocha chai mocha ts-node typescript
# or if you're using npm
npm install @types/chai @types/mocha chai mocha ts-node typescript --save-dev

Şimdi Waffle ve ethers ekleyelim:

yarn add --dev ethereum-waffle ethers
# or if you're using npm
npm install ethereum-waffle ethers --save-dev

Proje yapınız şimdi şöyle görünmeli:

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

2. Akıllı sözleşme

Dinamik taklit etmeye başlamak için bağımlılıkları olan akıllı bir sözleşmeye ihtiyacımız var. Kaygılanmayın, bunu size anlatacağım!

İşte tek amacı zengin olup olmadığımızı kontrol etmek olan Solidity ile yazılmış basit bir akıllı sözleşme. Yeterli token'ımız olup olmadığını kontrol etmek için ERC20 token'ını kullanır. Onu ./contracts/AmIRichAlready.sol içine koyun.

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}
Tümünü göster
Kopyala

Dinamik taklit kullanmak istediğimiz için tüm ERC20'ye ihtiyacımız yok, bu yüzden IERC20 arayüzünü sadece bir fonksiyonda kullanıyoruz.

Bu sözleşmeyi yapma zamanı! Bunun için Waffle kullanacağız. İlk olarak, derleme seçeneklerini belirten basit bir waffle.json yapılandırma dosyası oluşturacağız.

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

Artık Waffle ile sözleşme yapmaya hazırız:

npx waffle

Kolay, değil mi? build/ klasöründe sözleşmeye ve arayüze karşılık gelen iki dosya belirdi. Onları daha sonra test için kullanacağız.

3. Test

Gerçek test için AmIRichAlready.test.ts adında bir dosya oluşturalım. Her şeyden önce, ithalatı halletmemiz gerekiyor. Onlara daha sonra ihtiyaç duyacağız:

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

JS bağımlılıkları dışında, yerleşik sözleşmemizi ve arayüzümüzü içe aktarmamız gerekiyor:

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

Waffle test için chai kullanır. Ancak kullanmadan önce, Waffle'ın eşleyicilerini chai'nin kendisine enjekte etmemiz gerekiyor:

1use(solidity)

Her testten önce sözleşmenin durumunu sıfırlayacak beforeEach() fonksiyonunu uygulamamız gerekiyor. Önce orada neye ihtiyacımız olduğunu düşünelim. Bir sözleşmeyi dağıtmak için iki şeye ihtiyacımız var: Bir cüzdan ve onu AmIRichAlready sözleşmesi için bir argüman olarak iletmek üzere konuşlandırılmış bir ERC20 sözleşmesi.

İlk olarak bir cüzdan oluşturuyoruz:

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

O zaman bir ERC20 sözleşmesi dağıtmamız gerekiyor. İşin zor yanı şu: Elimizde sadece bir arayüz var. Waffle'ın bizi kurtarmaya geldiği kısım burası. Waffle'ın sihirli deployMockContract() fonksiyonu sadece arayüzün abi'sini kullanarak bir sözleşme oluşturur:

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

Şimdi hem cüzdan hem de dağıtılan ERC20 ile devam edip AmIRichAlready sözleşmesini uygulayabiliriz:

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

Bunların tamamı ile, beforeEach() fonksiyonumuz tamamlandı. Şimdiye dek AmIRichAlready.test.ts dosyanız şu şekilde gözükmeli:

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})
Tümünü göster

Hadi AmIRichAlready sözleşmesine ilk testi yazalım. Sizce testimiz ne hakkında olmalı? Evet, haklısınız! Zaten zengin olup olmadığımızı kontrol etmeliyiz :)

Ama bir saniye durun. Taklit sözleşmemiz hangi değerlerin döndürüleceğini nasıl bilecek? balanceOf() fonksiyonu için herhangi bir mantık eklemedik. Tekrardan, Waffle burada yardımcı olabilir. Sahte sözleşmemizde şimdi bazı yeni ilginç şeyler var:

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

Bu bilgiyle nihayet ilk testimizi yazabiliriz:

1it("returns false if the wallet has less than 1000000 tokens", async () => {
2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))
3 expect(await contract.check()).to.be.equal(false)
4})

Bu testi parçalara ayıralım:

  1. Taklit ERC20 sözleşmemizi her zaman 999999 token'lık bakiyeyi iade edecek şekilde ayarladık.
  2. contract.check() yönteminin false döndürüp döndürmediğini kontrol edin.

Canavarı başlatmaya hazırız:

Bir test geçişi

Yani, test işe yarıyor ama... biraz daha geliştirilebilir. balanceOf() fonksiyonu her zaman 99999 döndürür. Fonksiyonun bir şey döndürmesi gereken bir cüzdan belirterek onu iyileştirebiliriz: Tıpkı gerçek bir sözleşme gibi:

1it("returns false if the wallet has less than 1000001 tokens", 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})

Şimdiye kadar sadece yeterince zengin olmadığımız durumu test ettik. Bunun yerine zıttını test edelim:

1it("returns true if the wallet has at least 1000001 tokens", 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})

Testleri çalıştırırsınız...

İki test geçişi

...ve buradasınız! Sözleşmemiz istendiği gibi çalışıyor gibi görünüyor :)

Sözleşme çağrılarını test etme

Şimdiye kadar yaptıklarımı özetleyelim. AmIRichAlready sözleşmemizin işlevselliğini test ettik ve düzgün çalışıyor gibi görünüyor. Bu işimizin bittiği anlamına gelir, değil mi? Tam olarak değil! Waffle, sözleşmemizi daha da test etmemizi sağlıyor. Ama nasıl? Waffle'ın zulasıda calledOnContract() ve calledOnContractWith() eşleyicileri bulunmaktadır. Sözleşmemizin ERC20 taklit sözleşme olarak adlandırılıp adlandırılmadığını kontrol etmemizi sağlayacaklar. İşte bu eşleyicilerden biriyle yapılan temel bir test:

1it("checks if contract called balanceOf on the ERC20 token", async () => {
2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))
3 await contract.check()
4 expect("balanceOf").to.be.calledOnContract(mockERC20)
5})

Daha da ileri gidebilir ve size bahsettiğim diğer eşleyiciyle bu testi iyileştirebiliriz:

1it("checks if contract called balanceOf with certain wallet on the ERC20 token", 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})

Testlerin doğru olup olmadığını kontrol edelim:

Üç test geçişi

Müthiş, tüm testler yeşil ışık yakıyor.

Waffle ile sözleşme çağrılarını test etmek aşırı kolaydır. En güzel tarafı ise şu: Bu eşleyiciler hem normal hem de taklit sözleşmelerle çalışır! Bunun nedeni, Waffle'ın diğer teknolojiler için popüler test kütüphanelerinde olduğu gibi, kod enjekte etmek yerine EVM çağrılarını kaydetmesi ve filtrelemesidir.

Bitiş Çizgisi

Tebrikler! Artık sözleşme çağrılarını test etmek ve sözleşmeleri dinamik olarak taklit etmek için Waffle'ı nasıl kullanacağınızı biliyorsunuz. Keşfedilecek çok daha ilginç özellikler var. Waffle'ın belgelerine dalmanızı öneririm.

Waffle'ın belgeleri burada(opens in a new tab) mevcuttur.

Bu öğreticinin kaynak kodu burada(opens in a new tab) bulunabilir.

Ayrıca ilginizi çekebilecek öğreticiler:

Son düzenleme: @kaanmetu(opens in a new tab), 26 Şubat 2024

Bu rehber yararlı oldu mu?