Testen von Ethereum Smart Contracts on Go: Auf Wiedersehen, JavaScript

Bild
Ich möchte meinen Kollegen Sergey Nemesh, Mikhail Popsuyev, Evgeny Babich und Igor Titarenko für Konsultationen, Feedback und Tests danken. Ich möchte mich auch beim PolySwarm-Team für die Entwicklung der Originalversion von Perigord bedanken.

Dies ist eine Übersetzung meines ersten veröffentlichten mittelenglischen Artikels


Das Testen war schon immer ein wesentlicher Bestandteil der Softwareentwicklung, wenn auch nicht der unterhaltsamste. Wenn es um intelligente Verträge geht, sind strenge Tests mit außergewöhnlicher Liebe zum Detail erforderlich Fehler können nach der Bereitstellung im Blockchain-Netzwerk nicht mehr behoben werden. In den letzten Jahren hat die Ethereum-Community viele Tools für die Entwicklung intelligenter Verträge entwickelt. Einige von ihnen wurden nicht populär, zum Beispiel Vyper - ein Python-Dialekt zum Schreiben intelligenter Verträge. Andere, wie z. B. Solidity, sind zu einem anerkannten Standard geworden. Die bislang umfangreichste Dokumentation zum Testen intelligenter Verträge enthält eine Reihe von Trüffeln und Ganache. Beide Tools verfügen über eine gute Dokumentation. In vielen Fällen wurde bereits über Stapelüberlauf und ähnliche Ressourcen entschieden. Dieser Ansatz hat jedoch einen wichtigen Nachteil: Um Tests zu schreiben, müssen Sie Node.js verwenden.


JavaScript-Fallen


Auch wenn Sie kein Fan von statisch typisierten Programmiersprachen sind und JavaScript lieben, sollten Sie einen Tippfehler machen und das Ergebnis einer Funktion vergleichen, die eine Zeichenfolge mit einem booleschen Wert zurĂĽckgibt, indem Sie die veraltete Equal-Methode anstelle von strictEqual verwenden.


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

Wenn checkProposal die Zeichenfolge "yes" oder "no" zurückgibt, konvertieren Sie sie immer in "true". Durch dynamisches Tippen werden viele dieser Fallen ausgeblendet, und selbst erfahrene Programmierer können solche Fehler machen, wenn sie an einem großen Projekt oder in einem Team mit anderen Entwicklern arbeiten, die Änderungen am Code vornehmen und ihn nicht melden können.


Die statische Eingabe von Go hilft, solche Fehler zu vermeiden. Darüber hinaus ist die Verwendung der Go-Sprache anstelle von Node.js zum Testen der Traum eines jeden Go-Entwicklers, der mit intelligenten Verträgen arbeitet.


Mein Team entwickelte ein Investitionssystem, das auf intelligenten Verträgen mit einer sehr komplexen Architektur basiert. Das intelligente Vertragssystem enthielt mehr als 2.000 Codezeilen. Da der Großteil des Teams aus Go-Entwicklern bestand, war das Testen auf Go Node.js vorzuziehen.


Die erste Umgebung zum Testen intelligenter Verträge auf Go


Im Jahr 2017 entwickelte PolySwarm Perigord , ein Truffle-ähnliches Tool, das Go anstelle von JavaScript verwendet. Leider wird dieses Projekt nicht mehr unterstützt, es gibt nur ein Tutorial mit sehr einfachen Beispielen. Darüber hinaus wird die Integration mit Ganache (einer privaten Blockchain zur Entwicklung von Ethereum mit einer sehr praktischen Benutzeroberfläche) nicht unterstützt. Wir haben Perigord verbessert, indem wir Fehler beseitigt und zwei neue Funktionen eingeführt haben: Generieren von Brieftaschen aus dem Mnemonik-Code und Verwenden dieser zum Testen und Herstellen einer Verbindung mit der Ganache-Blockchain. Den Quellcode können Sie hier lesen.


Das ursprüngliche Perigord-Tutorial enthält nur das einfachste Beispiel für das Aufrufen eines Vertrags zum Ändern eines einzelnen Werts. In der realen Welt müssen Sie jedoch auch einen Vertrag aus verschiedenen Geldbörsen anrufen, Ether senden und empfangen usw. Jetzt können Sie all dies mit dem fortgeschrittenen Perigord und dem guten alten Ganache tun. Im Folgenden finden Sie detaillierte Anleitungen zum Entwickeln und Testen intelligenter Verträge mit Perigord & Ganache.


Verwenden von Advanced Perigord: Eine vollständige Anleitung


Um Perigord verwenden zu können, müssen Sie Go 1.7+, solc, abigen und Ganache installieren. Weitere Informationen finden Sie in der Dokumentation zu Ihrem Betriebssystem.


Installieren Sie Perigord wie folgt:


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

Danach können Sie den Befehl perigord verwenden:


 $ 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. 

Wir werden jetzt einen einfachen Market Smart-Vertrag erstellen, um die verfĂĽgbaren Testoptionen zu demonstrieren.


Geben Sie Folgendes in das Terminal ein, um ein Projekt zu starten:


 $ perigord init market 

Das Projekt wird im Ordner src / in GOPATH angezeigt. Verschieben Sie das Projekt in einen anderen Ordner und aktualisieren Sie die Importpfade, wenn Sie den Speicherort ändern möchten. Mal sehen, was sich im Markt / Ordner befindet.


 $ 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 

Sehr ähnlich dem in Truffle erstellten Projekt, nicht wahr? Aber es geht alles los! Mal sehen, was in der Konfigurationsdatei perigord.yaml enthalten ist.


 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 

Zum Testen können Sie sowohl das private Geth-Netzwerk als auch Brieftaschendateien verwenden und eine Verbindung zu Ganache herstellen. Diese Optionen schließen sich gegenseitig aus. Wir verwenden die Standard-Mnemonik, generieren 10 Konten und stellen eine Verbindung zu Ganache her. Ersetzen Sie den Code in perigord.yaml durch:


 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 - die Standardadresse des Ganache RPC-Servers. Bitte beachten Sie, dass Sie eine beliebige Anzahl von Konten zum Testen erstellen können, aber nur in der Ganache (GUI) generierte Konten Gelder enthalten.


Wir werden einen Vertrag namens Market.sol erstellen. Er kann Aufzeichnungen ĂĽber Adresspaare fĂĽhren, von denen einer Geld auf das Vertragskonto sendet und der andere das Recht hat, Geld zu erhalten, wenn der VertragseigentĂĽmer die Erlaubnis fĂĽr eine solche Transaktion erteilt. Beispielsweise vertrauen sich zwei Teilnehmer nicht, sondern dem Vertragsinhaber, der entscheidet, ob eine bestimmte Bedingung erfĂĽllt ist. Das Beispiel implementiert mehrere Grundfunktionen zu Demonstrationszwecken.


FĂĽgen Sie dem Projekt einen Kontakt hinzu:


 $ perigord add contract Market 

Das Postfix .sol wird automatisch hinzugefügt. Sie können auch andere Verträge hinzufügen oder den Beispielvertrag Foo.sol löschen. Während Sie bei GOPATH arbeiten, können Sie Importverträge verwenden, um komplexe Strukturen zu erstellen. Wir werden drei Solidity-Dateien haben: den Hauptmarktvertrag, die Ownable- und Migrations-Zusatzverträge und die SafeMath-Bibliothek. Den Quellcode finden Sie hier .


Jetzt hat das Projekt folgende Struktur:


 . ├── 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 

Generieren Sie EVM-Bytecode-, ABI- und Go-Bindungen:


 $ perigord build 

Fügen Sie die Migrationen aller Verträge hinzu, die Sie bereitstellen möchten. Weil Wir stellen nur Market.sol bereit. Wir benötigen nur eine neue Migration:


 $ perigord add migration Market 

Unser Vertrag enthält keinen Konstruktor, der Parameter akzeptiert. Wenn Sie Parameter an den Konstruktor übergeben müssen, fügen Sie sie der Funktion Deploy {NewContract} in der Migrationsdatei hinzu:


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

Löschen Sie die Beispieldatei Foo.go und fügen Sie eine Testdatei für unseren Vertrag hinzu:


 $ perigord add test Market 

Um deterministische Geldbörsen zu verwenden, müssen wir die Mnemonik aus der Konfigurationsdatei lesen:


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

Die folgende Hilfsfunktion wird verwendet, um die Netzwerkadresse abzurufen:


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

Eine weitere Hilfsfunktion, die wir benötigen, ist sendETH. Wir werden sie verwenden, um Ether von einer der generierten Geldbörsen (angezeigt durch den Index) an eine beliebige Ethereum-Adresse zu übertragen:


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

Die folgenden zwei Funktionen werden zum Ändern eines Vertragsaufrufs verwendet:


 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]) } 

Testverfahren


Für einen Anruf erstellen wir einen contractSessionActual für einen bestimmten Vertrag. Weil Der Vertrag hat einen Eigentümer. Wir können seine Adresse abrufen und prüfen, ob er mit dem Standard-Null-Ganache-Konto übereinstimmt. Wir werden dies wie folgt tun (wir werden die Fehlerbehandlung weglassen, um Platz zu sparen):


 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 

Die nächste nützliche Funktion ist das Ändern der Brieftasche, die den Vertrag verursacht:


 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) 

Weil Eine der Hauptfunktionen beim Testen ist das Ändern des Anrufvertrags. Lassen Sie uns eine Zahlung im Namen des Absenders leisten:


 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 

Der vollständige Testcode ist hier .


Ă–ffnen Sie nun stub_test.go und stellen Sie sicher, dass alle Importe auf Ihr aktuelles Projekt verweisen. In unserem Fall ist es:


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

FĂĽhren Sie die Tests aus:


 $ perigord test 

Wenn alles richtig gemacht wurde, gibt es nach dem Ende des Tests ein ähnliches Ergebnis:


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

Wenn Sie Probleme haben, laden Sie die Quelldateien herunter und wiederholen Sie die in diesem Handbuch beschriebenen Schritte.


AbschlieĂźend


Perigord ist ein zuverlässiges Testwerkzeug, das in Ihrer Lieblingssprache geschrieben ist. Er erstellt dieselbe Projektstruktur wie Truffle und hat dieselben Teams, sodass Sie nicht neu lernen müssen. Durch statische Typisierung und eine eindeutige Funktionssignatur können Sie schnell Debugging entwickeln und durchführen sowie sich erheblich gegen Tippfehler in den Argumenten schützen. In Perigord können Sie ein vorhandenes Projekt problemlos nach Truffle migrieren (Sie müssen lediglich die Vertragsdateien kopieren und in den entsprechenden Ordner einfügen und Tests hinzufügen) und ein vollständig neues Projekt mit in Go geschriebenen Tests starten.


Ich hoffe, dass die vom PolySwarm-Team begonnene und von Inn4Science fortgesetzte Arbeit fĂĽr die Go-Community nĂĽtzlich und frei von Test- und Debugging-Stunden mit weniger praktischen Tools ist.

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


All Articles