الحذر - الغاز! أو كيف فعلنا العقود الذكية غير الغازية


لا تزال Blockchain والعقود الذكية موضوعًا ساخنًا بين المطورين والفنيين. هناك الكثير من الأبحاث والمناقشات حول مستقبلهم وأين يتحرك كل شيء وإلى أين سيقود. لدينا في Waves Platform وجهة نظرنا الخاصة بشأن ماهية العقود الذكية ، وسأخبرك في هذا المقال كيف فعلناها ، وما هي المشاكل التي واجهناها ولماذا ليست مثل العقود الذكية لمشاريع blockchain الأخرى (أولاً وقبل كل شيء) Ethereum).


هذه المقالة هي أيضًا دليل لأولئك الذين يرغبون في فهم كيفية عمل العقود الذكية في شبكة Waves ، ومحاولة كتابة العقد الخاص بك والتعرف على الأدوات التي يمتلكها المطورون بالفعل.


كيف وصلنا إلى هذه الحياة؟


لقد سُئلنا كثيرًا متى حصلنا على عقود ذكية ، لأن المطورين أحبوا سهولة العمل مع الشبكة ، وسرعة الشبكة (بفضل Waves NG ) وانخفاض مستوى العمولات. ومع ذلك ، توفر العقود الذكية مساحة أكبر بكثير للخيال.


أصبحت العقود الذكية تحظى بشعبية كبيرة في السنوات القليلة الماضية بسبب انتشار blockchain. أولئك الذين واجهوا تقنية blockchain في عملهم ، عند ذكر العقود الذكية ، عادة ما يفكرون في Ethereum و Solidity. ولكن هناك العديد من منصات blockchain ذات العقود الذكية ، ومعظمها يكرر ببساطة ما فعلته Ethereum (آلة افتراضية + لغة العقد الخاصة بها). قائمة مثيرة للاهتمام مع لغات ومناهج مختلفة في هذا المستودع .


ما هو العقد الذكي؟


بمعنى واسع ، العقد الذكي هو بروتوكول مصمم لدعم والتحقق من وفرض شروط المعاملة أو تنفيذ العقود بين الأطراف. تم طرح الفكرة لأول مرة في عام 1996 من قِبل Nick Szabo ، لكن العقود الذكية أصبحت شعبية فقط في السنوات القليلة الماضية.


من الناحية الفنية (التي تهمنا أكثر) ، فإن العقد الذكي هو خوارزمية (رمز) يتم تنفيذها ليس على أي خادم أو كمبيوتر واحد ، ولكن على العديد (أو جميع) العقد في شبكة blockchain ، أي لامركزية.


كيف يعمل؟


يعتبر النموذج الأول للعقد الذكي على blockchain بشكل صحيح Bitcoin Script - غير مكتمل بواسطة Turing ، وهي لغة قائمة على المكدس على شبكة Bitcoin. لا يوجد مفهوم حساب في Bitcoin ؛ بدلاً من ذلك ، هناك مدخلات ومخرجات. في Bitcoin ، عند إجراء معاملة (إنشاء مخرجات) ، من الضروري الإشارة إلى معاملة التلقي (إدخال). إذا كنت مهتمًا بالتفاصيل التقنية لجهاز Bitcoin ، نوصيك بقراءة هذه السلسلة من المقالات . نظرًا لعدم وجود حسابات في Bitcoin ، يحدد Bitcoin Script في هذه الحالة يمكن إنهاء خروج واحد أو آخر.


Ethereum يقدم المزيد من الميزات ، كما يوفر Solidity ، وهي لغة كاملة من تورينج تعمل في جهاز افتراضي داخل كل عقدة. مع القوة العظمى تأتي مسؤولية كبيرة ، مع مجموعة واسعة من الاحتمالات - عدد كبير إلى حد ما من القيود ، والتي سنتحدث عنها لاحقًا.


العقود الذكية الأمواج


كما كتبت أعلاه ، سُئلنا غالبًا عن العقود الذكية ، لكننا لم نرغب في القيام بـ "أعجبني على الهواء" أو "كما هو الحال في anyblockchainname" ، وهناك العديد من الأسباب لذلك . لذلك ، قمنا بتحليل الحالات الحالية للعقود وكيف يمكننا المساعدة في حل المشاكل الحقيقية بمساعدتهم.


بعد تحليل سيناريوهات الاستخدام ، اكتشفنا أن هناك فئتين كبيرتين من المهام التي يتم حلها عادة باستخدام العقود الذكية:


  1. مهام بسيطة ومباشرة مثل multisig ، مقايضة ذرية أو الضمان.
  2. dApps ، التطبيقات اللامركزية الكاملة مع منطق المستخدم. لتكون أكثر دقة ، هذا هو الخلفية للتطبيقات اللامركزية. الأمثلة الأكثر لفتا هي Cryptokitties أو Bancor.

هناك أيضًا نوع ثالث من أكثر الرموز شعبية للعقود. على شبكة Ethereum ، على سبيل المثال ، الغالبية العظمى من عقود العمل هي رموز ERC20 القياسية. في Waves ، لإنشاء الرموز ، ليست هناك حاجة إلى إبرام عقود ذكية ، مثل هم جزء من blockchain نفسه ، وإصدار رمز مميز (مع القدرة على المتاجرة به على الفور في بورصة غير مركزية (DEX)) يكفي لإرسال معاملة واحدة من نوع المشكلة (معاملة الإصدار).


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


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


لنجعل الحسابات ذكية


تدريجيا ، توصلنا إلى مفهوم الحسابات الذكية ، والتي صممت لحل المهام البسيطة في المقام الأول. تشبه فكرتهم برنامج Bitcoin Script: يمكن إضافة قواعد إضافية إلى الحساب الذي يحدد صلاحية المعاملة الصادرة. المتطلبات الرئيسية للحسابات الذكية هي:


  1. السلامة القصوى. كل شهر تقريبًا ، يمكنك العثور على أخبار تفيد بوجود ثغرة أخرى في عقود طراز Ethereum. أردنا تجنب هذا.
  2. لا حاجة للغاز ، بحيث يتم إصلاح العمولة. للقيام بذلك ، يجب أن يتم تنفيذ البرنامج النصي في فترة زمنية يمكن التنبؤ بها ولها حدود حجم صارمة إلى حد ما.

قبل الشروع في التفاصيل الفنية لتنفيذ العقود وكتابتها ، نوجز بعض خصائص سلسلة Waves blockchain التي ستكون مهمة لمزيد من الفهم:


  1. لدى Waves blockchain حاليًا 13 نوعًا مختلفًا من المعاملات.


  1. في blockchain Waves ، ليس المدخلات والمخرجات (مثل في Bitcoin) ، ولكن الحسابات (مثل ، على سبيل المثال ، في Nxt). يتم تنفيذ المعاملة نيابة عن حساب واحد محدد.
  2. افتراضيًا ، يتم تحديد صحة معاملة ما بواسطة الحالة الحالية للكتلة البرمجية وصلاحية التوقيع نيابةً عن إرسال المعاملة. يبدو تمثيل JSON للمعاملة بسيطًا بدرجة كافية:


نظرًا لأن لدينا بالفعل أنواع مختلفة من المعاملات في blockchain ، فقد قررنا ألا نجعل كيانًا منفصلاً كحساب ذكي ، ولكننا نضيف معاملة جديدة تحول حسابًا عاديًا إلى حساب ذكي. يمكن أن يصبح أي حساب حسابًا ذكيًا مع تغيير قواعد التحقق من صحة المعاملة ، ولهذا ، يجب على الحساب ببساطة إرسال معاملة من النوع SetScriptTransaction ، والتي تحتوي على العقد المترجم.


في حالة الحساب الذكي ، يكون العقد بمثابة قاعدة تحقق لكل معاملة صادرة.


وماذا عن الغاز؟


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


  1. يجب تشغيل البرنامج النصي بشكل أسرع من 20 عملية تحقق من التوقيع ، مما يعني أن عمليات التحقق من الحساب الذكي لن تكون أبطأ من 20 مرة مقارنة بالحساب العادي. يجب ألا يتجاوز حجم البرنامج النصي 8 كيلو بايت.
  2. لجعله مربحًا للعاملين في المناجم الوفاء بالعقود الذكية ، وضعنا حدًا أدنى للعمولة الإضافية للحسابات الذكية بمبلغ 0.004 WAVES. الحد الأدنى للعمولة في شبكة Waves للمعاملة هو 0.001 WAVES ، في حالة الحساب الذكي - 0.005 WAVES.

لغة العقود الذكية


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


دعونا نحاول أن نتخيل ما يجب أن تكون اللغة المثالية للعقود الذكية. في رأيي ، يجب أن تجبر أي لغة برمجة على كتابة كود "صحيح" وآمن ، أي من الناحية المثالية يجب أن يكون هناك طريقة واحدة صحيحة. نعم ، إذا أردت ، يمكنك كتابة التعليمات البرمجية غير القابلة للقراءة وغير المدعومة بالكامل بأي لغة ، ولكن هذا يجب أن يكون أكثر صعوبة من كتابتها بشكل صحيح (مرحبًا PHP و JavaScript). في الوقت نفسه ، يجب أن تكون اللغة مريحة للتطوير. نظرًا لأن اللغة تعمل على جميع نقاط الشبكة ، فمن الضروري أن تكون فعالة قدر الإمكان - التنفيذ البطيء يمكن أن يوفر الكثير من الموارد. أود أيضًا أن يكون لدي نظام كتابة قوي في اللغة ، ويفضل أن يكون جبريًا ، لأنه يساعد على وصف العقد بأكبر قدر ممكن من الوضوح والاقتراب من حلم "Code is law". إذا قمنا بإضفاء الطابع الرسمي على متطلباتنا أكثر من ذلك بقليل ، فسنحصل على معلمات اللغة التالية:


  1. أن تكون مكتوبة بدقة وبشكل ثابت. الكتابة القوية تلغي تلقائيًا العديد من الأخطاء المحتملة للمبرمجين.
  2. لديك نظام كتابة قوي يجعل من الصعب عليك إطلاق النار على قدمك.
  3. كن كسولًا حتى لا تضيع دورات المعالج الثمينة.
  4. لديك وظائف محددة في المكتبة القياسية للعمل مع blockchain ، على سبيل المثال ، التجزئة. في الوقت نفسه ، لا ينبغي زيادة تحميل مكتبة اللغات القياسية ، لأنه يجب أن يكون هناك دائمًا طريقة واحدة صحيحة.
  5. ليس لديك استثناءات في وقت التشغيل.

بلغتنا RIDE ، حاولنا أخذ هذه الميزات المهمة في الاعتبار ، وبما أننا نطور الكثير على Scala ومثل البرمجة الوظيفية ، فإن اللغة في بعض النواحي تشبه Scala و F #.


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


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


 match tx { case t:TransferTransaction => t.recepient case t:MassTransferTransaction => t.transfers case _ => throw } 

إذا كنت مهتمًا بمعرفة كيفية ترتيب العمل باستخدام رمز RIDE ، فراجع الورقة البيضاء ، التي تصف جميع مراحل العمل بعقد: التحليل ، التجميع ، إلغاء التسلسل ، حساب تعقيد النص والتنفيذ. يتم تنفيذ المرحلتين الأوليين - التحليل والتجميع خارج السلسلة ، فقط العقد المترجم في base64 يدخل في blockchain. تتم إزالة التسلسل وحساب التعقيد والتنفيذ على التسلسل ، وعدة مرات في مراحل مختلفة:


  1. عندما تتلقى معاملة وتضيفها إلى UTX ، وإلا فسيكون هناك موقف عندما يتم قبول المعاملة بواسطة العقدة blockchain ، على سبيل المثال ، من خلال واجهة برمجة تطبيقات REST ، ولكنها لن تدخل في الحجب.
  2. عند تكوين كتلة ، تقوم عقدة التعدين بالتحقق من صحة المعاملات ويكون البرنامج النصي مطلوبًا.
  3. عند استلام العقد غير التعدينية للكتلة والتحقق من صحة المعاملات الواردة فيها.

يصبح كل تحسين في التعامل مع العقود قيمة ، لأنه يتم تنفيذه عدة مرات على العديد من عقد الشبكة. تعمل Now Waves العقد بهدوء على الأجهزة الافتراضية مقابل 15 دولارًا في DigitalOcean ، على الرغم من الزيادة في أعباء العمل بعد إصدار الحسابات الذكية.


ما هي النتيجة؟


الآن دعونا نرى ما حصلنا عليه في الأمواج. سنكتب أول عقد خاص بنا ، فليكن عقدًا متعدد الاستخدامات قياسيًا متعدد الاستخدامات. لكتابة عقد ، يمكنك استخدام IDE عبر الإنترنت (ضبط اللغة - موضوع لمقال منفصل). إنشاء عقد فارغ جديد (جديد → عقد فارغ).


بادئ ذي بدء ، سوف نعلن عن المفاتيح العامة لـ Alice و Bob و Cooper ، والتي ستتحكم في الحساب. ستحتاج 2 من 3 توقيعات:


 let alicePubKey = base58'B1Yz7fH1bJ2gVDjyJnuyKNTdMFARkKEpV' let bobPubKey = base58'7hghYeWtiekfebgAcuCg9ai2NXbRreNzc' let cooperPubKey = base58'BVqYXrapgJP9atQccdBPAgJPwHDKkh6A8' 

تصف الوثائق وظيفة sigVerify ، والتي تتيح لك التحقق من توقيع المعاملة:



الوسائط إلى الدالة هي نص المعاملة والتوقيع الذي تم التحقق منه والمفتاح العمومي. يتوفر كائن tx في العقد في النطاق العام ، حيث يتم تخزين معلومات المعاملة. يحتوي هذا الكائن على حقل tx.bodyBytes يحتوي على وحدات البايت للمعاملة التي يتم إرسالها. هناك أيضًا مجموعة من tx.proofs ، التي تخزن التوقيعات ، والتي يمكن أن تصل إلى 8. ومن الجدير بالذكر أنه في الواقع ، لا يمكنك إرسال التواقيع فقط إلى tx.proofs ، ولكن أي معلومات أخرى يمكن استخدامها بواسطة العقد.


يمكننا التأكد من تقديم جميع التوقيعات وأنها في الترتيب الصحيح باستخدام 3 خطوط بسيطة:


 let aliceSigned = if(sigVerify(tx.bodyBytes, tx.proofs[0], alicePubKey )) then 1 else 0 let bobSigned = if(sigVerify(tx.bodyBytes, tx.proofs[1], bobPubKey )) then 1 else 0 let cooperSigned = if(sigVerify(tx.bodyBytes, tx.proofs[2], cooperPubKey )) then 1 else 0 

حسنًا ، ستكون الخطوة الأخيرة هي التحقق من إرسال توقيعين على الأقل.


 aliceSigned + bobSigned + cooperSigned >= 2 

يشبه العقد متعدد العقود بالكامل من أصل ثلاثة:


 #    let alicePubKey = base58'B1Yz7fH1bJ2gVDjyJnuyKNTdMFARkKEpV' let bobPubKey = base58'7hghYeWtiekfebgAcuCg9ai2NXbRreNzc' let cooperPubKey = base58'BVqYXrapgJP9atQccdBPAgJPwHDKkh6A8' #       let aliceSigned = if(sigVerify(tx.bodyBytes, tx.proofs[0], alicePubKey )) then 1 else 0 let bobSigned = if(sigVerify(tx.bodyBytes, tx.proofs[1], bobPubKey )) then 1 else 0 let cooperSigned = if(sigVerify(tx.bodyBytes, tx.proofs[2], cooperPubKey )) then 1 else 0 # ,      2   aliceSigned + bobSigned + cooperSigned >= 2 

يرجى ملاحظة: لا توجد كلمات رئيسية مثل return في الكود ، لأن السطر الأخير الذي تم تنفيذه يعتبر نتيجة البرنامج النصي ، ولهذا السبب يجب أن يُرجع دائمًا true أو false


في المقارنة ، يبدو عقد Ethereum المشترك للتوقيع المتعدد أكثر تعقيدًا . حتى الأشكال البسيطة نسبياً تبدو كالتالي:


  pragma solidity ^0.4.22; contract SimpleMultiSig { uint public nonce; // (only) mutable state uint public threshold; // immutable state mapping (address => bool) isOwner; // immutable state address[] public ownersArr; // immutable state // Note that owners_ must be strictly increasing, in order to prevent duplicates constructor(uint threshold_, address[] owners_) public { require(owners_.length <= 10 && threshold_ <= owners_.length && threshold_ >= 0); address lastAdd = address(0); for (uint i = 0; i < owners_.length; i++) { require(owners_[i] > lastAdd); isOwner[owners_[i]] = true; lastAdd = owners_[i]; } ownersArr = owners_; threshold = threshold_; } // Note that address recovered from signatures must be strictly increasing, in order to prevent duplicates function execute(uint8[] sigV, bytes32[] sigR, bytes32[] sigS, address destination, uint value, bytes data) public { require(sigR.length == threshold); require(sigR.length == sigS.length && sigR.length == sigV.length); // Follows ERC191 signature scheme: https://github.com/ethereum/EIPs/issues/191 bytes32 txHash = keccak256(byte(0x19), byte(0), this, destination, value, data, nonce); address lastAdd = address(0); // cannot have address(0) as an owner for (uint i = 0; i < threshold; i++) { address recovered = ecrecover(txHash, sigV[i], sigR[i], sigS[i]); require(recovered > lastAdd && isOwner[recovered]); lastAdd = recovered; } // If we make it here all signatures are accounted for. // The address.call() syntax is no longer recommended, see: // https://github.com/ethereum/solidity/issues/2884 nonce = nonce + 1; bool success = false; assembly { success := call(gas, destination, value, add(data, 0x20), mload(data), 0, 0) } require(success); } function () payable public {} } 

يحتوي IDE على وحدة تحكم مدمجة تسمح لك بترجمة العقد على الفور ونشره وإنشاء المعاملات ومعرفة نتيجة التنفيذ. وإذا كنت ترغب في العمل بجدية مع العقود ، فإنني أوصي بالنظر في المكتبات للغات مختلفة والمكون الإضافي لـ Visual Studio Code .


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


النظام أقوى من اللغة


يحتوي Waves blockchain على أنواع بيانات خاصة لتخزين البيانات - معاملات البيانات . إنهم يعملون كتخزين رئيسي ذي قيمة مرتبطة بحساب ، وهذا يعني ، إلى حد ما ، حالة الحساب.



يمكن أن يحتوي تاريخ المعاملة على سلاسل وأرقام وقيم منطقية وصفائف بايت تصل إلى 32 كيلوبايت لكل مفتاح. مثال على العمل مع Data Transactions ، والذي يسمح لك بإرسال معاملة فقط إذا كان تخزين القيمة الرئيسية للحساب يحتوي بالفعل على الرقم 42 على مفتاح key :


 let keyName = "key" match (tx) { case tx:DataTransaction => let x = extract(getInteger(tx.sender, keyName)) x == 42 case _ => false } 

بفضل "Data Transaction" ، أصبحت الحسابات الذكية أداة قوية للغاية تتيح لك العمل مع oracles وإدارة الحالة ووصف السلوك بشكل ملائم.


توضح هذه المقالة كيف يمكنك تطبيق NFT (الرموز غير fungible) باستخدام "معاملات البيانات" والعقد الذكي الذي يتحكم في الحالة. نتيجة لذلك ، سيتضمن نمط الحساب إدخالات النموذج:


 +------------+-----------------------------------------------+ | Token Name | Owner Publc Key | +------------+-----------------------------------------------+ | "Token #1" | "6iQaHazE9NVAJfAjMpHifDXMfr1euWcy8fmW6rNcdhr" | | "Token #2" | "3tNLxyJnyxLzDkMkqiZmUjRqXe1UuwFeSyQ14GRYnGL" | | "Token #3" | "3wH7rENpbS78uohErXHq77yKzQwRyKBYhzCR9nKU17q" | | "Token #4" | "6iQaHazE9NVAJfAjMpHifDXMfr1euWcy8fmW6rNcdhr" | | "Token #5" | "6iQaHazE9NVAJfAjMpHifDXMfr1euWcy8fmW6rNcdhr" | +------------+-----------------------------------------------+ 

يبدو عقد NFT بحد ذاته بسيطًا للغاية:


 match tx { case dt: DataTransaction => let oldOwner = extract(getString(dt.sender, dt.data[0].key)) let newOwner = getBinary(dt.data, 0) size(dt.data) == 1 && sigVerify(dt.bodyBytes, dt.proofs[0], fromBase58String(oldOwner)) case _ => false } 

ما التالي؟


مزيد من التطوير للعقود الذكية الخاصة بـ Waves هي Ride4DApps ، مما سيسمح بعقد عقود الاتصال على حسابات أخرى ، ولغة (أو نظام Turing كاملة) تتيح لك حل جميع أنواع المهام ، وتشغيل مهام أخرى ، إلخ.


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


حسنًا ، في النهاية سأقدم مرة أخرى قائمة بما سيكون ضروريًا لبدء العمل بعقود ذكية على شبكة Waves.


  1. الوثائق
  2. IDE مع وحدة التحكم
  3. ورقة بيضاء لمعظم الفضوليين

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


All Articles