Menguji Kontrak Cerdas Ethereum on Go: Selamat Tinggal, JavaScript

gambar
Saya ingin mengucapkan terima kasih kepada rekan-rekan saya: Sergey Nemesh, Mikhail Popsuyev, Evgeny Babich dan Igor Titarenko atas konsultasi, umpan balik, dan pengujian. Saya juga ingin mengucapkan terima kasih kepada tim PolySwarm karena telah mengembangkan versi asli Perigord.

Ini adalah terjemahan dari artikel Bahasa Inggris Medium pertama saya yang dipublikasikan


Pengujian selalu menjadi bagian integral dari pengembangan perangkat lunak, meskipun bukan yang paling menyenangkan. Ketika datang ke kontrak pintar, pengujian ketat diperlukan dengan perhatian khusus terhadap detail, seperti kesalahan tidak akan mungkin diperbaiki setelah penerapan ke jaringan blockchain. Selama beberapa tahun terakhir, komunitas Ethereum telah menciptakan banyak alat untuk mengembangkan kontrak pintar. Beberapa dari mereka tidak menjadi populer, misalnya, Vyper - dialek Python untuk menulis kontrak pintar. Lainnya, seperti Solidity, telah menjadi standar yang diakui. Dokumentasi paling luas tentang pengujian kontrak pintar hingga saat ini menyediakan banyak Truffle & Ganache. Kedua alat ini memiliki dokumentasi yang baik, banyak kasus telah diputuskan tentang Stack Overflow dan sumber daya serupa. Namun, pendekatan ini memiliki satu kelemahan penting: untuk menulis tes, Anda harus menggunakan Node.js.


Perangkap JavaScript


Bahkan jika Anda bukan penggemar bahasa pemrograman yang diketik statis dan menyukai JavaScript, pertimbangkan untuk membuat kesalahan ketik dan mulai membandingkan hasil fungsi yang mengembalikan string dengan nilai boolean menggunakan metode sama yang tidak digunakan alih-alih strictEqual.


let proposalExists = await voting.checkProposal(); assert.equal(proposalExists, true, 'Proposal should exist'); 

Jika checkProposal mengembalikan string "ya" atau "tidak", Anda akan selalu mengubahnya menjadi true. Pengetikan dinamis menyembunyikan banyak perangkap ini, dan bahkan pemrogram berpengalaman dapat membuat kesalahan seperti itu ketika bekerja pada proyek besar atau dalam tim dengan pengembang lain yang dapat membuat perubahan pada kode dan tidak melaporkannya.


Pengetikan statis Go membantu mencegah kesalahan semacam itu. Selain itu, penggunaan bahasa Go alih-alih Node.js untuk pengujian adalah impian setiap pengembang-Go yang mulai bekerja dengan kontrak pintar.


Tim saya sedang mengembangkan sistem investasi berdasarkan kontrak pintar dengan arsitektur yang sangat kompleks. Sistem kontrak pintar berisi lebih dari 2.000 baris kode. Karena sebagian besar tim adalah pengembang Go, pengujian di Go lebih disukai daripada Node.js.


Lingkungan pertama untuk menguji kontrak pintar di Go


Pada 2017, PolySwarm mengembangkan Perigord , alat yang mirip dengan Truffle, menggunakan Go, bukan JavaScript. Sayangnya, proyek ini tidak lagi didukung, hanya memiliki satu tutorial dengan contoh yang sangat sederhana. Selain itu, tidak mendukung integrasi dengan Ganache (blockchain pribadi untuk mengembangkan Ethereum dengan GUI yang sangat nyaman). Kami meningkatkan Perigord dengan menghilangkan bug dan memperkenalkan dua fungsi baru: menghasilkan dompet dari kode mnemonic dan menggunakannya untuk menguji dan terhubung ke blockchain Ganache. Anda dapat membaca kode sumber di sini .


Tutorial Perigord asli hanya berisi contoh paling sederhana dari memohon kontrak untuk mengubah nilai tunggal. Namun, di dunia nyata, Anda juga perlu menelepon kontrak dari dompet yang berbeda, mengirim dan menerima Eter, dll. Sekarang Anda dapat melakukan semua ini dengan menggunakan Perigord canggih dan Ganache tua yang baik. Di bawah ini Anda akan menemukan panduan terperinci tentang pengembangan dan pengujian kontrak pintar menggunakan Perigord & Ganache.


Menggunakan Advanced Perigord: Panduan Lengkap


Untuk menggunakan Perigord, Anda harus menginstal Go 1.7+, solc, abigen, dan Ganache. Silakan lihat dokumentasi untuk sistem operasi Anda.


Instal Perigord sebagai berikut:


 $ go get gitlab.com/go-truffle/enhanced-perigord $ go build 

Setelah itu, Anda dapat menggunakan perintah perigord:


 $ perigord A golang development environment for Ethereum Usage: perigord [command] Available Commands: add Add a new contract or test to the project build (alias for compile) compile Compile contract source files deploy (alias for migrate) generate (alias for compile) help Help about any command init Initialize new Ethereum project with example contracts and tests migrate Run migrations to deploy contracts test Run go and solidity tests Flags: -h, --help help for perigord Use "perigord [command] --help" for more information about a command. 

Kami sekarang akan membuat kontrak cerdas Pasar sederhana untuk menunjukkan opsi pengujian yang tersedia.


Untuk memulai proyek, masukkan yang berikut ke terminal:


 $ perigord init market 

Proyek akan muncul di folder src / di GOPATH. Pindahkan proyek ke folder lain dan perbarui jalur impor jika Anda ingin mengubah lokasinya. Mari kita lihat apa yang ada di market / folder.


 $ tree . β”œβ”€β”€ contracts β”‚ └── Foo.sol β”œβ”€β”€ generate.go β”œβ”€β”€ main.go β”œβ”€β”€ migrations β”‚ └── 1_Migrations.go β”œβ”€β”€ perigord.yaml β”œβ”€β”€ stub β”‚ β”œβ”€β”€ README.md β”‚ └── main.go β”œβ”€β”€ stub_test.go └── tests └── Foo.go 

Sangat mirip dengan proyek yang dibuat di Truffle, bukan? Tapi semuanya siap! Mari kita lihat apa yang ada di file konfigurasi perigord.yaml.


 networks: dev: url: /tmp/geth_private_testnet/geth.ipc keystore: /tmp/geth_private_testnet/keystore passphrase: blah mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat num_accounts: 10 

Untuk pengujian, Anda dapat menggunakan jaringan geth pribadi dan file dompet, dan terhubung ke Ganache. Opsi-opsi ini saling eksklusif. Kami mengambil mnemonics default, menghasilkan 10 akun dan terhubung ke Ganache. Ganti kode dalam perigord.yaml dengan:


 networks: dev: url: HTTP://127.0.0.1:7545 mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat num_accounts: 10 

HTTP http://127.0.0.1:7545 - alamat standar server Ganache RPC. Harap dicatat bahwa Anda dapat membuat sejumlah akun untuk pengujian, tetapi hanya akun yang dibuat di Ganache (GUI) yang akan berisi dana.


Kami akan membuat kontrak yang disebut Market.sol. Dia dapat menyimpan catatan pasangan alamat, yang salah satunya mengirim dana ke akun kontrak, dan yang lain memiliki hak untuk menerima dana ketika pemilik kontrak memberikan izin untuk transaksi semacam itu. Misalnya, dua peserta tidak saling percaya, tetapi mempercayai pemilik kontrak, yang memutuskan apakah suatu kondisi terpenuhi. Contoh ini mengimplementasikan beberapa fungsi dasar untuk tujuan demonstrasi.


Tambahkan kontak ke proyek:


 $ perigord add contract Market 

Postfix .sol akan ditambahkan secara otomatis. Anda juga dapat menambahkan kontrak lain atau menghapus kontrak sampel Foo.sol. Saat Anda bekerja di GOPATH, Anda dapat menggunakan kontrak impor untuk membuat struktur yang kompleks. Kami akan memiliki tiga file Soliditas: kontrak Pasar utama, kontrak tambahan Dimiliki dan Migrasi, dan pustaka SafeMath. Anda dapat menemukan kode sumber di sini .


Sekarang proyek memiliki struktur berikut:


 . β”œβ”€β”€ contracts β”‚ β”œβ”€β”€ Market.sol β”‚ β”œβ”€β”€ Ownable.sol β”‚ └── SafeMath.sol β”œβ”€β”€ generate.go β”œβ”€β”€ main.go β”œβ”€β”€ migrations β”‚ └── 1_Migrations.go β”œβ”€β”€ perigord.yaml β”œβ”€β”€ stub β”‚ β”œβ”€β”€ README.md β”‚ └── main.go β”œβ”€β”€ stub_test.go └── tests └── Foo.go 

Hasilkan bytecode EVM, ABI dan Go bindings:


 $ perigord build 

Tambahkan migrasi semua kontrak yang akan Anda terapkan. Karena kami hanya menggunakan Market.sol, kami hanya perlu satu migrasi baru:


 $ perigord add migration Market 

Kontrak kami tidak mengandung konstruktor yang menerima parameter. Jika Anda perlu meneruskan parameter ke konstruktor, tambahkan ke fungsi Deploy {NewContract} dalam file migrasi:


 address, transaction, contract, err := bindings.Deploy{NewContract}(auth, network.Client(), β€œFOO”, β€œBAR”) 

Hapus file sampel Foo.go dan tambahkan file uji untuk kontrak kami:


 $ perigord add test Market 

Untuk menggunakan dompet deterministik, kita perlu membaca mnemonik dari file konfigurasi:


 func getMnemonic() string { viper.SetConfigFile("perigord.yaml") if err := viper.ReadInConfig(); err != nil { log.Fatal() } mnemonic := viper.GetStringMapString("networks.dev")["mnemonic"] return mnemonic } 

Fungsi pembantu berikut digunakan untuk mendapatkan alamat jaringan:


 func getNetworkAddress() string { viper.SetConfigFile("perigord.yaml") if err := viper.ReadInConfig(); err != nil { log.Fatal() } networkAddr := viper.GetStringMapString("networks.dev")["url"] return networkAddr } 

Fungsi pembantu lain yang kita perlukan adalah sendETH, kita akan menggunakannya untuk mentransfer Ether dari salah satu dompet yang dihasilkan (ditunjukkan oleh indeks) ke alamat Ethereum:


 func sendETH(s *MarketSuite, c *ethclient.Client, sender int, receiver common.Address, value *big.Int) { senderAcc := s.network.Accounts()[sender].Address nonce, err := c.PendingNonceAt(context.Background(), senderAcc) if err != nil { log.Fatal(err) } gasLimit := uint64(6721975) // in units gasPrice := big.NewInt(3700000000) wallet, err := hdwallet.NewFromMnemonic(getMnemonic()) toAddress := receiver var data []byte tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) chainID, err := c.NetworkID(context.Background()) if err != nil { log.Fatal(err) } privateKey, err := wallet.PrivateKey(s.network.Accounts()[sender]) signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) if err != nil { log.Fatal(err) } ts := types.Transactions{signedTx} rawTx := hex.EncodeToString(ts.GetRlp(0)) var trx *types.Transaction rawTxBytes, err := hex.DecodeString(rawTx) err = rlp.DecodeBytes(rawTxBytes, &trx) err = c.SendTransaction(context.Background(), trx) if err != nil { log.Fatal(err) } } 

Dua fungsi berikut digunakan untuk memodifikasi panggilan kontrak:


 func ensureAuth(auth bind.TransactOpts) *bind.TransactOpts { return &bind.TransactOpts{ auth.From, auth.Nonce, auth.Signer, auth.Value, auth.GasPrice, auth.GasLimit, auth.Context} } func changeAuth(s MarketSuite, account int) bind.TransactOpts { return *s.network.NewTransactor(s.network.Accounts()[account]) } 

Prosedur pengujian


Untuk panggilan, kami membuat kontrakSessionActual untuk kontrak tertentu. Karena kontrak memiliki pemilik, kita bisa mendapatkan alamatnya dan memeriksa apakah cocok dengan nol akun Ganache default. Kami akan melakukan ini sebagai berikut (kami akan menghilangkan penanganan kesalahan untuk menghemat ruang):


 contractSession := contract.Session("Market") c.Assert(contractSession, NotNil) contractSessionActual, ok := contractSession.(*bindings.MarketSession) c.Assert(ok, Equals, true) c.Assert(contractSessionActual, NotNil) owner, _ := contractSessionActual.Owner() account0 := s.network.Accounts()[0] c.Assert(owner.Hex(), Equals, account0.Address.Hex()) //Owner account is account 0 

Fitur berguna selanjutnya adalah mengganti dompet yang menyebabkan kontrak:


 ownerInd := 0 sender := 5 receiver := 6 senderAcc := s.network.Accounts()[sender].Address receiverAcc := s.network.Accounts()[receiver].Address //Call contract on behalf of its owner auth := changeAuth(*s, ownerInd) _, err = contractSessionActual.Contract.SetSenderReceiverPair(ensureAuth(auth), senderAcc, receiverAcc) 

Karena salah satu fungsi utama yang digunakan dalam pengujian adalah mengubah kontrak panggilan, mari lakukan pembayaran atas nama pengirim:


 auth = changeAuth(*s, sender) //Change auth fo senderAcc to make a deposit on behalf of the sender client, _ := ethclient.Dial(getNetworkAddress()) //Let's check the current balance balance, _ := client.BalanceAt(context.Background(), contract.AddressOf("Market"), nil) c.Assert(balance.Int64(), Equals, big.NewInt(0).Int64()) //Balance should be 0 //Let's transfer 3 ETH to the contract on behalf of the sender value := big.NewInt(3000000000000000000) // in wei (3 eth) contractReceiver := contract.AddressOf("Market") sendETH(s, client, sender, contractReceiver, value) balance2, _ := client.BalanceAt(context.Background(), contract.AddressOf("Market"), nil) c.Assert(balance2.Int64(), Equals, value.Int64()) //Balance should be 3 ETH 

Kode tes lengkap ada di sini .


Sekarang buka stub_test.go dan pastikan semua impor mengarah ke proyek Anda saat ini. Dalam kasus kami, itu adalah:


 import ( _ "market/migrations" _ "market/tests" "testing" . "gopkg.in/check.v1" ) 

Jalankan tes:


 $ perigord test 

Jika semuanya dilakukan dengan benar, maka setelah akhir pengujian akan ada hasil yang serupa:


 Running migration 2 Running migration 3 OK: 1 passed PASS ok market 0.657s 

Jika Anda memiliki masalah, unduh file sumber dan ulangi langkah-langkah yang dijelaskan dalam panduan ini.


Kesimpulannya


Perigord adalah alat pengujian andal yang ditulis dalam bahasa favorit Anda. Dia menciptakan struktur proyek yang sama dengan Truffle, dan memiliki tim yang sama, jadi Anda tidak perlu mempelajari kembali. Pengetikan statis dan tanda tangan fungsi yang tidak ambigu memungkinkan Anda untuk dengan cepat mengembangkan dan melakukan debugging, serta secara signifikan melindungi terhadap kesalahan pengetikan dalam argumen. Di Perigord, Anda dapat dengan mudah memigrasi proyek yang sudah ada ke Truffle (yang perlu Anda lakukan hanyalah menyalin dan menempelkan file kontrak ke folder yang sesuai dan menambahkan tes), dan juga memulai proyek yang sama sekali baru dengan tes yang ditulis dalam Go.


Saya berharap pekerjaan yang dimulai oleh tim PolySwarm dan dilanjutkan oleh Inn4Science akan bermanfaat bagi komunitas Go dan membebaskan waktu pengujian dan debugging menggunakan alat yang kurang nyaman.

Source: https://habr.com/ru/post/id445254/


All Articles