اختبار Ethereum العقود الذكية على الذهاب: وداعا ، جافا سكريبت

صورة
أود أن أشكر زملائي: سيرجي نيميش وميخائيل بوبسيف وإفغيني بابيتش وإيجور تيتارينكو على المشاورات والتعليقات والاختبارات. أود أيضًا أن أقول شكراً لفريق PolySwarm على تطوير النسخة الأصلية من Perigord.

هذه ترجمة لمقالتي الإنجليزية المتوسطة الأولى المنشورة


لقد كان الاختبار دائمًا جزءًا لا يتجزأ من تطوير البرامج ، على الرغم من أنه ليس الأكثر متعة. عندما يتعلق الأمر بالعقود الذكية ، يلزم إجراء اختبارات صارمة مع إيلاء اهتمام استثنائي بالتفاصيل سيكون من المستحيل إصلاح الأخطاء بعد النشر على شبكة blockchain. على مدار السنوات الماضية ، أنشأ مجتمع Ethereum العديد من الأدوات لتطوير العقود الذكية. بعضها لم يصبح شائعًا ، على سبيل المثال ، Vyper - لهجة بيثون لكتابة العقود الذكية. البعض الآخر ، مثل Solidity ، أصبح معيارًا معترفًا به. توفر الوثائق الأكثر شمولاً حول اختبار العقود الذكية حتى الآن مجموعة من Truffle & Ganache. تحتوي كلتا هاتين الأداتين على وثائق جيدة ، وقد تم بالفعل تحديد العديد من الحالات في Stack Overflow والموارد المماثلة. ومع ذلك ، فإن هذا النهج له عيب واحد مهم: لكتابة الاختبارات ، تحتاج إلى استخدام Node.js.


مصائد جافا سكريبت


حتى إذا لم تكن من عشاق لغات البرمجة الثابتة المكتوبة وتحب جافا سكريبت ، ففكر في إنشاء خطأ مطبعي والبدء في مقارنة نتيجة دالة تُرجع سلسلة ذات قيمة منطقية باستخدام طريقة المساواة المهملة بدلاً منrictEqual.


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

إذا قام checkProposal بإرجاع السلسلة "نعم" أو "لا" ، فسوف تقوم دائمًا بتحويلها إلى صواب. تخفي الكتابة الديناميكية العديد من هذه الفخاخ ، وحتى المبرمجين المتمرسين يمكنهم ارتكاب مثل هذه الأخطاء عند العمل في مشروع كبير أو في فريق مع مطورين آخرين يمكنهم إجراء تغييرات على الكود وعدم الإبلاغ عنه.


الكتابة الثابتة في Go تساعد في منع مثل هذه الأخطاء. بالإضافة إلى ذلك ، فإن استخدام لغة Go بدلاً من Node.js للاختبار هو حلم أي مطور Go الذي يبدأ العمل بعقود ذكية.


كان فريقي يعمل على تطوير نظام استثمار قائم على عقود ذكية ذات بنية معقدة للغاية. يحتوي نظام العقود الذكي على أكثر من 2000 سطر من التعليمات البرمجية. نظرًا لأن الجزء الأكبر من الفريق كان مطورو Go ، فإن الاختبار على Go كان أفضل من Node.js.


أول بيئة لاختبار العقود الذكية على الذهاب


في عام 2017 ، قامت PolySwarm بتطوير Perigord ، وهي أداة مشابهة لـ Truffle ، باستخدام Go بدلاً من JavaScript. لسوء الحظ ، لم يعد هذا المشروع مدعومًا ، فهو يحتوي على برنامج تعليمي واحد فقط مع أمثلة بسيطة جدًا. بالإضافة إلى ذلك ، لا يدعم التكامل مع Ganache (كتلة سلسلة خاصة لتطوير Ethereum مع واجهة المستخدم الرسومية مريحة للغاية). قمنا بتحسين Perigord من خلال القضاء على الأخطاء وتقديم وظيفتين جديدتين: إنشاء محافظ من رمز ذاكري واستخدامها لاختبار والاتصال إلى blockchain Ganache. يمكنك قراءة الكود المصدري هنا .


يحتوي البرنامج التعليمي Perigord الأصلي على أبسط مثال على استدعاء عقد لتغيير قيمة واحدة. ومع ذلك ، في العالم الواقعي ، ستحتاج أيضًا إلى استدعاء عقد من محافظ مختلفة ، وإرسال واستقبال الأثير ، إلخ. الآن يمكنك القيام بكل هذا باستخدام Perigord المتقدم و Ganache القديم الجيد. ستجد أدناه إرشادات مفصلة حول تطوير واختبار العقود الذكية باستخدام Perigord & Ganache.


باستخدام متقدم Perigord: دليل كامل


لاستخدام Perigord ، يلزمك تثبيت Go 1.7+ و solc و abigen و Ganache. يرجى الاطلاع على الوثائق الخاصة بنظام التشغيل الخاص بك.


تثبيت Perigord على النحو التالي:


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

بعد ذلك ، يمكنك استخدام الأمر 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. 

سنقوم الآن بإنشاء عقد سوقي بسيط لإظهار خيارات الاختبار المتاحة.


لبدء مشروع ، أدخل ما يلي في المحطة:


 $ perigord init market 

سيظهر المشروع في src / folder في GOPATH. انقل المشروع إلى مجلد آخر وقم بتحديث مسارات الاستيراد إذا كنت تريد تغيير موقعه. دعونا نرى ما هو في السوق / المجلد.


 $ 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 

تشبه إلى حد كبير المشروع الذي تم إنشاؤه في Truffle ، أليس كذلك؟ ولكن كل شيء على الذهاب! دعونا نرى ما هو في ملف التكوين 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 

للاختبار ، يمكنك استخدام كل من شبكة geth الخاصة وملفات المحفظة ، والاتصال بـ Ganache. هذه الخيارات متبادلة. نأخذ فن الإستذكار الافتراضي ، وننشئ 10 حسابات ونتصل بـ Ganache. استبدل الكود في perigord.yaml بـ:


 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 - العنوان القياسي لخادم Ganache RPC. يرجى ملاحظة أنه يمكنك إنشاء أي عدد من الحسابات للاختبار ، ولكن فقط الحسابات التي تم إنشاؤها في Ganache (GUI) سوف تحتوي على أموال.


سنقوم بإنشاء عقد يسمى Market.sol. يمكنه الاحتفاظ بسجل لأزواج العناوين ، أحدها يرسل الأموال إلى حساب العقد ، والآخر لديه الحق في استلام الأموال عندما يمنح مالك العقد إذنًا لمثل هذه الصفقة. على سبيل المثال ، لا يثق اثنان من المشاركين في بعضهما البعض ، لكنهما يثقان في مالك العقد ، الذي يقرر ما إذا كان قد تم استيفاء شرط معين. يقوم المثال بتنفيذ عدة وظائف أساسية لأغراض العرض التوضيحي.


إضافة جهة اتصال إلى المشروع:


 $ perigord add contract Market 

ستتم إضافة postfix .sol تلقائيًا. يمكنك أيضًا إضافة عقود أخرى أو حذف نموذج العقد Foo.sol. أثناء عملك في GOPATH ، يمكنك استخدام عقود الاستيراد لإنشاء هياكل معقدة. سيكون لدينا ثلاثة ملفات Solidity: عقد السوق الرئيسي ، وعقود المساعدة الخاصة والهجرة ، ومكتبة SafeMath. يمكنك العثور على الكود المصدري هنا .


يحتوي المشروع الآن على البنية التالية:


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

قم بإنشاء روابط EVM bytecode و ABI و Go:


 $ perigord build 

أضف عمليات ترحيل جميع العقود التي ستنشرها. لأن نحن ننشر Market.sol فقط ، نحتاج إلى ترحيل جديد واحد فقط:


 $ perigord add migration Market 

لا يحتوي عقدنا على مُنشئ يقبل المعلمات. إذا كنت بحاجة إلى تمرير المعلمات إلى المُنشئ ، فأضفها إلى وظيفة Deploy {NewContract} في ملف الترحيل:


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

احذف نموذج الملف Foo.go وأضف ملف اختبار لعقدنا:


 $ perigord add test Market 

لاستخدام محافظ الحتمية ، نحتاج إلى قراءة فن الإستذكار من ملف التكوين:


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

يتم استخدام وظيفة المساعد التالية للحصول على عنوان الشبكة:


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

وظيفة المساعد الأخرى التي سنحتاج إليها هي sendETH ، وسوف نستخدمها لنقل الأثير من إحدى المحافظ التي تم إنشاؤها (المشار إليها بواسطة الفهرس) إلى أي عنوان 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) } } 

يتم استخدام الدالتين التاليتين لتعديل مكالمة العقد:


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

إجراء الاختبار


بالنسبة للمكالمة ، نقوم بإنشاء عقد الدورة الفعلية للحصول على عقد محدد. لأن يحتوي العقد على مالك ، ويمكننا الحصول على عنوانه والتحقق مما إذا كان يطابق الحساب الافتراضي صفر Ganache. سنفعل ذلك على النحو التالي (سنحذف معالجة الأخطاء لتوفير المساحة):


 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 

الميزة المفيدة التالية هي تغيير المحفظة التي تسبب العقد:


 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) 

لأن إحدى الوظائف الرئيسية المستخدمة في الاختبار هي تغيير عقد الاتصال ، فلنقوم بالدفع نيابة عن المرسل:


 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 

رمز الاختبار الكامل هنا .


افتح الآن stub_test.go وتأكد من أن جميع الواردات تشير إلى مشروعك الحالي. في حالتنا ، هو:


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

قم بإجراء الاختبارات:


 $ perigord test 

إذا تم كل شيء بشكل صحيح ، فبعد نهاية الاختبار ستكون هناك نتيجة مماثلة:


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

إذا كانت لديك أي مشاكل ، فقم بتنزيل الملفات المصدر وكرر الخطوات الموضحة في هذا الدليل.


في الختام


Perigord هي أداة اختبار موثوقة مكتوبة بلغتك المفضلة. يقوم بإنشاء نفس هيكل المشروع مثل Truffle ، ولديه نفس الفرق ، لذلك لن تحتاج إلى إعادة التعلم. تتيح لك الكتابة الثابتة وتوقيع الوظيفة غير الواضحة تطوير وتنفيذ تصحيح الأخطاء بشكل سريع ، فضلاً عن الحماية بشكل كبير من الأخطاء المطبعية في الوسائط. في Perigord ، يمكنك بسهولة ترحيل مشروع موجود إلى Truffle (كل ما عليك القيام به هو نسخ ملفات العقد ولصقها في المجلد المناسب وإضافة الاختبارات) ، وكذلك بدء مشروع جديد تمامًا مع اختبارات مكتوبة في Go.


آمل أن يكون العمل الذي بدأه فريق PolySwarm واستمرته Inn4Science مفيدًا لمجتمع Go وخاليًا من ساعات الاختبار والتصحيح باستخدام أدوات أقل ملاءمة.

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


All Articles