عقود Ethereum Oracle: ميزات Solidity Code

نشرت: 2022-03-11

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

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

يمكن العثور على الكود الكامل في هذه المرحلة هنا: https://github.com/jrkosinski/oracle-example/tree/part2-step1

الإيثيريوم والصلابة

Solidity ليست لغة تطوير العقود الذكية الوحيدة المتاحة ، لكنني أعتقد أنها آمنة بما يكفي للقول إنها الأكثر شيوعًا والأكثر شيوعًا بشكل عام ، لعقود Ethereum الذكية. بالتأكيد هو الشخص الذي لديه الدعم والمعلومات الأكثر شيوعًا ، في وقت كتابة هذا التقرير.

رسم تخطيطي لميزات Ethereum Solidity الحاسمة

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

نسخة صلابة

هذا هو السطر الأول من كل قصيدة كود Solidity:

 pragma solidity ^0.4.17;

ستختلف أرقام الإصدارات التي تراها ، لأن Solidity ، لا تزال في شبابها ، تتغير وتتطور بسرعة. الإصدار 0.4.17 هو الإصدار الذي استخدمته في الأمثلة الخاصة بي ؛ أحدث إصدار وقت هذا المنشور هو 0.4.25.

قد يكون أحدث إصدار في هذا الوقت الذي تقرأ فيه هذا شيئًا مختلفًا تمامًا. توجد العديد من الميزات الرائعة في الأعمال (أو على الأقل المخطط لها) لـ Solidity ، والتي سنناقشها حاليًا.

فيما يلي نظرة عامة على إصدارات Solidity المختلفة.

نصيحة للمحترفين: يمكنك أيضًا تحديد مجموعة من الإصدارات (على الرغم من أنني لا أرى ذلك يتم كثيرًا) ، مثل:

 pragma solidity >=0.4.16 <0.6.0;

ميزات لغة برمجة Solidity

تتمتع Solidity بالعديد من الميزات اللغوية المألوفة لدى معظم المبرمجين الحديثين بالإضافة إلى بعض الميزات المميزة (بالنسبة لي على الأقل) غير العادية. يُقال إنه مستوحى من C ++ و Python و JavaScript - وكلها مألوفة جيدًا بالنسبة لي شخصيًا ، ومع ذلك تبدو Solidity متميزة تمامًا عن أي من هذه اللغات.

عقد

ملف .sol هو الوحدة الأساسية للتعليمات البرمجية. في BoxingOracle.sol ، لاحظ السطر التاسع:

 contract BoxingOracle is Ownable {

نظرًا لأن الفصل هو الوحدة الأساسية للمنطق في اللغات الموجهة للكائنات ، فإن العقد هو الوحدة الأساسية للمنطق في Solidity. يكفي تبسيط الأمر في الوقت الحالي للقول إن العقد هو "فئة" من Solidity (بالنسبة للمبرمجين الموجهين للكائنات ، هذه قفزة سهلة).

ميراث

تدعم عقود Solidity الميراث بشكل كامل ، وتعمل كما تتوقع ؛ لا يتم توريث أعضاء العقد الخاص ، في حين أن الأعضاء المحميون والعامة هم. يتم دعم التحميل الزائد وتعدد الأشكال كما تتوقع.

 contract BoxingOracle is Ownable {

في البيان أعلاه ، تشير الكلمة الأساسية "is" إلى الميراث. BoxingOracle يرث من Ownable. الوراثة المتعددة مدعومة أيضًا في Solidity. تتم الإشارة إلى الوراثة المتعددة بقائمة من أسماء الفئات مفصولة بفواصل ، مثل:

 contract Child is ParentA, ParentB, ParentC { …

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

Enums

Enums مدعومة في Solidity:

 enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg, cancelled) Decided //index of participant who is the winner }

كما تتوقع (لا تختلف عن اللغات المألوفة) ، يتم تعيين قيمة عدد صحيح لكل قيمة تعداد ، بدءًا من 0. كما هو مذكور في مستندات Solidity ، يمكن تحويل قيم التعداد إلى جميع أنواع الأعداد الصحيحة (على سبيل المثال ، uint ، uint16 ، uint32 ، إلخ) ، لكن التحويل الضمني غير مسموح به. مما يعني أنه يجب إرسالها بشكل صريح (إلى uint ، على سبيل المثال).

مستندات Solidity: Enums Enums Tutorial

الهياكل

الهياكل هي طريقة أخرى ، مثل التعدادات ، لإنشاء نوع بيانات محدد من قبل المستخدم. الهياكل مألوفة لجميع المبرمجين الأساسيين في C / C ++ والأشخاص القدامى مثلي. مثال على الهيكل ، من السطر 17 من BoxingOracle.sol:

 //defines a match along with its outcome struct Match { bytes32 id; string name; string participants; uint8 participantCount; uint date; MatchOutcome outcome; int8 winner; }

ملاحظة لجميع مبرمجي لغة سي القدامى: إن بنية "التعبئة" في Solidity شيء ، ولكن هناك بعض القواعد والمحاذير. لا تفترض بالضرورة أنه يعمل بنفس الطريقة في C ؛ تحقق من المستندات وكن على دراية بموقفك ، للتأكد مما إذا كان التعبئة سيساعدك في حالة معينة أم لا.

التعبئة هيكل الصلابة

بمجرد إنشائها ، يمكن معالجة الهياكل في التعليمات البرمجية الخاصة بك على أنها أنواع بيانات أصلية. فيما يلي مثال على بناء جملة "إنشاء مثيل" لنوع البنية الذي تم إنشاؤه أعلاه:

 Match match = Match(id, "A vs. B", "A|B", 2, block.timestamp, MatchOutcome.Pending, 1);

أنواع البيانات في الصلابة

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

أنواع البيانات في Ethereum Solidity

أنواع بيانات الصلابة

قيمة منطقية

يتم دعم الأنواع المنطقية تحت الاسم bool والقيم true أو false

أنواع رقمية

يتم دعم أنواع الأعداد الصحيحة ، الموقعة وغير الموقعة ، من int8 / uint8 إلى int256 / uint256 (وهي أعداد صحيحة 8 بت إلى أعداد صحيحة 256 بت ، على التوالي). نوع uint هو اختصار لـ uint256 (وبالمثل int هو اختصار لـ int256).

والجدير بالذكر أن أنواع الفاصلة العائمة غير مدعومة. لما لا؟ حسنًا ، لسبب واحد ، عند التعامل مع القيم النقدية ، من المعروف جيدًا أن متغيرات النقطة العائمة فكرة سيئة (بشكل عام بالطبع) ، لأن القيمة يمكن أن تضيع في الهواء. يُشار إلى قيم الأثير بوحدة wei ، وهي 1 / 1،000،000،000،000،000،000 من جزء من الأثير ، ويجب أن تكون هذه الدقة كافية لجميع الأغراض ؛ لا يمكنك تقسيم الأثير إلى أجزاء أصغر.

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

https://hackernoon.com/a-note-on-numbers-in-ethereum-and-javascript-3e6ac3b2fad9

ملاحظة: في معظم الحالات ، من الأفضل استخدام uint فقط ، لأن تقليل حجم المتغير (إلى uint32 ، على سبيل المثال) ، يمكن أن يؤدي في الواقع إلى زيادة تكاليف الغاز بدلاً من تقليلها كما قد تتوقع. كقاعدة عامة ، استخدم uint ما لم تكن متأكدًا من أن لديك سببًا جيدًا للقيام بخلاف ذلك.

أنواع السلاسل

نوع بيانات السلسلة في Solidity موضوع مضحك ؛ قد تحصل على آراء مختلفة بناءً على من تتحدث إليه. هناك نوع بيانات سلسلة في Solidity ، هذه حقيقة. رأيي ، الذي ربما يشاركه معظم الناس ، هو أنه لا يوفر الكثير من الوظائف. تحليل السلسلة ، والتسلسل ، والاستبدال ، والقص ، وحتى حساب طول السلسلة: لا توجد أي من الأشياء التي تتوقعها من نوع السلسلة ، وبالتالي فهي مسؤوليتك (إذا كنت بحاجة إليها). يستخدم بعض الأشخاص بايت 32 بدلاً من السلسلة ؛ يمكن أن يتم ذلك أيضًا.

مقال ممتع عن سلاسل Solidity

رأيي: قد يكون من الممتع كتابة نوع السلسلة الخاصة بك ونشرها للاستخدام العام.

نوع العنوان

ربما يكون فريدًا بالنسبة إلى Solidity ، فلدينا نوع بيانات العنوان ، خاصةً لمحفظة Ethereum أو عناوين العقد. إنها قيمة 20 بايت مخصصة لتخزين عناوين بهذا الحجم المعين. بالإضافة إلى ذلك ، يحتوي على أعضاء نوع خصيصًا لعناوين من هذا النوع.

 address internal boxingOracleAddr = 0x145ca3e014aaf5dca488057592ee45305d9b3a22;

أنواع بيانات العنوان

أنواع التاريخ والوقت

لا يوجد نوع أصلي للتاريخ أو التاريخ والوقت في Solidity ، في حد ذاته ، كما هو الحال في JavaScript ، على سبيل المثال. (أوه لا - يبدو Solidity أسوأ وأسوأ مع كل فقرة !؟) تتم معالجة التواريخ محليًا على أنها طوابع زمنية من النوع uint (uint256). يتم التعامل معها بشكل عام كطوابع زمنية على غرار Unix ، بالثواني بدلاً من المللي ثانية ، حيث أن الطابع الزمني للكتلة هو طابع زمني على غرار Unix. في الحالات التي تجد فيها نفسك بحاجة إلى تواريخ يمكن قراءتها لأسباب مختلفة ، فهناك مكتبات مفتوحة المصدر متاحة. قد تلاحظ أنني استخدمت واحدة في BoxingOracle: DateLib.sol. يحتوي OpenZeppelin أيضًا على أدوات مساعدة للتاريخ بالإضافة إلى العديد من الأنواع الأخرى من مكتبات المرافق العامة (سنصل إلى ميزة مكتبة Solidity قريبًا).

نصيحة احترافية: يعد OpenZeppelin مصدرًا جيدًا (ولكنه بالطبع ليس المصدر الجيد الوحيد) لكل من المعرفة والكود العام المكتوب مسبقًا والذي قد يساعدك على بناء عقودك.

التعيينات

لاحظ أن السطر 11 من BoxingOracle.sol يعرّف شيئًا يسمى التعيين :

 mapping(bytes32 => uint) matchIdToIndex;

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

مثال على إضافة إلى الخريطة ، من السطر 71 من BoxingOracle.sol:

 matchIdToIndex[id] = newIndex+1

مثال على القراءة من الخرائط ، من السطر 51 من BoxingOracle.sol:

 uint index = matchIdToIndex[_matchId];

يمكن أيضًا إزالة العناصر من التعيين. لم يتم استخدامه في هذا المشروع ، لكنه سيبدو كما يلي:

 delete matchIdToIndex[_matchId];

إرجاع القيم

كما لاحظت ، قد يكون لدى Solidity بعض التشابه السطحي مع Javascript ، لكنها لا ترث الكثير من رخاوة JavaScript في الأنواع والتعريفات. يجب تحديد رمز العقد بطريقة صارمة ومقيدة إلى حد ما (وربما يكون هذا أمرًا جيدًا ، مع الأخذ في الاعتبار حالة الاستخدام). مع وضع ذلك في الاعتبار ، ضع في اعتبارك تعريف الوظيفة من السطر 40 من BoxingOracle.sol

 function _getMatchIndex(bytes32 _matchId) private view returns (uint) { ... }

حسنًا ، لنقم أولاً بإلقاء نظرة عامة سريعة على ما هو موجود هنا. function تحددها كدالة. _getMatchIndex هو اسم الوظيفة (الشرطة السفلية هي اصطلاح يشير إلى عضو خاص - سنناقش ذلك لاحقًا). يستغرق وسيطة واحدة ، تسمى _matchId (هذه المرة يتم استخدام اصطلاح الشرطة السفلية للإشارة إلى وسيطات الدالة) من النوع bytes32 . تجعل الكلمة الأساسية private العضو في الواقع خاصًا في النطاق ، ويخبر view المترجم أن هذه الوظيفة لا تعدل أي بيانات على blockchain ، وأخيرًا: ~~~ إرجاع الصلابة (uint) ~~~

يشير هذا إلى أن الوظيفة ترجع uint (الدالة التي ترجع الفراغ لن تحتوي ببساطة على عبارة returns هنا). لماذا uint بين قوسين؟ ذلك لأن دوال Solidity يمكنها إرجاع المجموعات وغالبًا ما تقوم بذلك.

لننظر الآن في التعريف التالي من السطر 166:

 function getMostRecentMatch(bool _pending) public view returns ( bytes32 id, string name, string participants, uint8 participantCount, uint date, MatchOutcome outcome, int8 winner) { ... }

تحقق من شرط العودة على هذا واحد! تعيد واحد ، اثنان ... سبعة أشياء مختلفة. حسنًا ، تُرجع هذه الدالة هذه الأشياء في صورة مجموعة. لماذا ا؟ أثناء التطوير ، ستجد نفسك غالبًا بحاجة إلى إرجاع بنية (إذا كانت JavaScript ، فأنت تريد إرجاع كائن JSON ، على الأرجح). حسنًا ، حتى كتابة هذه السطور (على الرغم من أن هذا قد يتغير في المستقبل) ، لا تدعم Solidity إعادة الهياكل من الوظائف العامة. لذا عليك إرجاع المجموعات بدلاً من ذلك. إذا كنت من مستخدمي لغة Python ، فقد تكون مرتاحًا لاستخدام tuples بالفعل. العديد من اللغات لا تدعمها حقًا ، على الأقل ليس بهذه الطريقة.

انظر السطر 159 للحصول على مثال لإرجاع مجموعة كقيمة مرتجعة:

 return (_matchId, "", "", 0, 0, MatchOutcome.Pending, -1);

وكيف نقبل القيمة المعادة لشيء كهذا؟ يمكننا أن نفعل مثل ذلك:

 var (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false);

بدلاً من ذلك ، يمكنك التصريح عن المتغيرات بوضوح مسبقًا ، وأنواعها الصحيحة:

 //declare the variables bytes32 id; string name; ... etc... int8 winner; //assign their values (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false);

والآن أعلنا عن 7 متغيرات لتحتفظ بقيم الإرجاع السبعة ، والتي يمكننا استخدامها الآن. بخلاف ذلك ، بافتراض أننا أردنا قيمة واحدة أو اثنتين فقط ، يمكننا القول:

 //declare the variables bytes32 id; uint date; //assign their values (id,,,,date,,) = getMostRecentMatch(false);

انظر ماذا فعلنا هنا؟ لقد حصلنا فقط على الاثنين اللذين كنا مهتمين بهما. تحقق من كل تلك الفاصلات. علينا أن نحسبهم بعناية!

الواردات

الخطان 3 و 4 من BoxingOracle.sol مستوردان:

 import "./Ownable.sol"; import "./DateLib.sol";

كما تتوقع على الأرجح ، فهذه التعريفات تستورد من ملفات التعليمات البرمجية الموجودة في نفس مجلد مشروع العقود مثل BoxingOracle.sol.

الصفات التعريفية

لاحظ أن تعريفات الوظائف لها مجموعة من المعدلات المرفقة. أولاً ، هناك رؤية: رؤية خاصة وعامة وداخلية وخارجية للوظيفة.

علاوة على ذلك ، سترى الكلمات الرئيسية pure view . تشير هذه إلى المترجم إلى نوع التغييرات التي ستقوم بها الوظيفة ، إن وجدت. هذا مهم لأن مثل هذا الشيء هو عامل في تكلفة الغاز النهائية لتشغيل الوظيفة. انظر هنا للحصول على شرح: Solidity Docs.

أخيرًا ، ما أريد حقًا مناقشته هو مُعدِّلات مخصصة. ألق نظرة على السطر 61 من BoxingOracle.sol:

 function addMatch(string _name, string _participants, uint8 _participantCount, uint _date) onlyOwner public returns (bytes32) {

لاحظ معدِّل onlyOwner مباشرةً قبل الكلمة الأساسية "العامة". وهذا يدل على أن صاحب العقد فقط هو من يمكنه استدعاء هذه الطريقة! على الرغم من أهمية هذه الميزة ، إلا أنها ليست سمة أصلية لـ Solidity (على الرغم من أنها قد تكون كذلك في المستقبل). في الواقع ، onlyOwner هو مثال على مُعدِّل مخصص نصنعه بأنفسنا ونستخدمه. لنلقي نظرة.

أولاً ، تم تعريف المعدل في الملف Ownable.sol ، والذي يمكنك أن ترى أننا قمنا باستيراده في السطر 3 من BoxingOracle.sol:

 import "./Ownable.sol"

لاحظ أنه من أجل الاستفادة من المعدل ، جعلنا BoxingOracle يرث من Ownable . داخل Ownable.sol ، في السطر 25 ، يمكننا العثور على تعريف المعدل داخل عقد "Ownable":

 modifier onlyOwner() { require(msg.sender == owner); _; }

(هذا العقد المملوك ، بالمناسبة ، مأخوذ من أحد عقود OpenZeppelin العامة.)

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

 require(msg.sender == owner);

يمكننا القول أنه يعني:

 if (msg.send != owner) throw an exception;

وفي الواقع ، في Solidity 0.4.22 والإصدارات الأحدث ، يمكننا إضافة رسالة خطأ إلى عبارة تتطلب:

 require(msg.sender == owner, "Error: this function is callable by the owner of the contract, only");

أخيرًا ، في الخط الغريب المظهر:

 _;

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

هناك ، بالطبع ، المزيد من الأشياء التي يمكنك القيام بها باستخدام المُعدِّلات. تحقق من المستندات: Docs.

مكتبات التضامن

هناك سمة لغوية في Solidity تُعرف باسم المكتبة . لدينا مثال في مشروعنا في DateLib.sol.

تنفيذ مكتبة التضامن!

هذه مكتبة للتعامل بشكل أسهل مع أنواع التواريخ. تم استيراده إلى BoxingOracle في السطر 4:

 import "./DateLib.sol";

ويتم استخدامه في السطر 13:

 using DateLib for DateLib.DateTime;

DateLib.DateTime عبارة عن بنية مستحقة من عقد DateLib (تم الكشف عنها كعضو ؛ انظر السطر 4 من DateLib.sol) ونحن نعلن هنا أننا "نستخدم" مكتبة DateLib لنوع بيانات معين. لذا فإن الأساليب والعمليات المعلنة في تلك المكتبة ستنطبق على نوع البيانات الذي قلنا أنه ينبغي. هذه هي الطريقة التي يتم بها استخدام المكتبة في Solidity.

للحصول على مثال أوضح ، تحقق من بعض مكتبات OpenZeppelin للأرقام ، مثل SafeMath. يمكن تطبيقها على أنواع بيانات صلابة أصلية (رقمية) (بينما قمنا هنا بتطبيق مكتبة على نوع بيانات مخصص) ، وتستخدم على نطاق واسع.

واجهات

كما هو الحال في اللغات السائدة الموجهة للكائنات ، يتم دعم الواجهات. يتم تعريف الواجهات في Solidity على أنها عقود ، ولكن يتم حذف الهيئات الوظيفية للوظائف. للحصول على مثال لتعريف الواجهة ، راجع OracleInterface.sol. في هذا المثال ، يتم استخدام الواجهة كبديل لعقد أوراكل ، والذي يوجد محتواه في عقد منفصل بعنوان منفصل.

اصطلاحات التسمية

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

ملخص المشروع

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

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

يمكن ذكر حالة العمل للمثال على النحو التالي:

  • يريد المستخدم المراهنة بأحجام مختلفة على مباريات الملاكمة ، ودفع المال (الأثير) للرهانات وجمع المكاسب عندما يفوز.
  • يقوم المستخدم بهذه الرهانات من خلال عقد ذكي. (في حالة الاستخدام الواقعي ، سيكون هذا تطبيق DApp كاملًا بواجهة ويب 3 ؛ لكننا نقوم فقط بفحص جانب العقود.)
  • عقد ذكي منفصل - أوراكل - يحتفظ به طرف ثالث. وتتمثل مهمتها في الحفاظ على قائمة مباريات الملاكمة مع حالاتها الحالية (معلقة ، قيد التقدم ، منتهية ، وما إلى ذلك) ، وإذا انتهى ، الفائز.
  • يحصل العقد الرئيسي على قوائم بالمطابقات المعلقة من أوراكل ويقدمها للمستخدمين كمطابقات "قابلة للمضاربة".
  • العقد الأساسي يقبل الرهانات حتى بداية المباراة.
  • بعد تحديد المباراة ، يقسم العقد الرئيسي المكاسب والخسائر وفقًا لخوارزمية بسيطة ، ويأخذ جزءًا لنفسه ، ويدفع المكاسب عند الطلب (يخسر الخاسرون حصتهم بالكامل).

قواعد الرهان:

  • هناك حد أدنى محدد للرهان (محدد في وي).
  • لا يوجد حد أقصى للرهان. يمكن للمستخدمين المراهنة على أي مبلغ يرغبون فيه فوق الحد الأدنى.
  • يمكن للمستخدمين المراهنة حتى يحين الوقت الذي تصبح فيه المباراة "قيد التقدم".

خوارزمية قسمة المكاسب:

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

BoxingOracle: عقد أوراكل

الوظائف الرئيسية المقدمة

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

للمستخدمين:

  • قائمة بجميع المباريات
  • قائمة المباريات المعلقة
  • احصل على تفاصيل مباراة محددة
  • احصل على حالة ونتائج مباراة محددة

للمالك:

  • أدخل المباراة
  • تغيير حالة المباراة
  • مجموعة نتيجة المباراة

رسم توضيحي لعناصر وصول المستخدم والمالك

قصة المستخدم:

  • الإعلان عن مباراة ملاكمة جديدة وتأكيدها في 9 مايو.
  • أنا ، المشرف على العقد (ربما أكون شبكة رياضية معروفة أو منفذًا جديدًا) ، أضف المباراة القادمة إلى بيانات oracle على blockchain ، مع الحالة "معلقة". يمكن لأي شخص أو أي عقد الآن الاستعلام عن هذه البيانات واستخدامها كيفما شاء.
  • عندما تبدأ المباراة ، قمت بتعيين حالة تلك المباراة على "قيد التقدم".
  • عندما تنتهي المباراة ، قمت بتعيين حالة المباراة على "مكتملة" وقمت بتعديل بيانات المباراة للإشارة إلى الفائز.

مراجعة كود أوراكل

تستند هذه المراجعة بالكامل على BoxingOracle.sol ؛ تشير أرقام الأسطر إلى هذا الملف.

في السطر 10 و 11 ، نعلن عن مكان تخزين المباريات لدينا:

 Match[] matches; mapping(bytes32 => uint) matchIdToIndex;

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

في السطر 17 ، تم تحديد وشرح هيكل المطابقة الخاص بنا:

 //defines a match along with its outcome struct Match { bytes32 id; //unique id string name; //human-friendly name (eg, Jones vs. Holloway) string participants; //a delimited string of participant names uint8 participantCount; //number of participants (always 2 for boxing matches!) uint date; //GMT timestamp of date of contest MatchOutcome outcome; //the outcome (if decided) int8 winner; //index of the participant who is the winner } //possible match outcomes enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg, cancelled) Decided //index of participant who is the winner }

السطر 61: وظيفة addMatch مخصصة للاستخدام من قبل مالك العقد فقط ؛ يسمح بإضافة تطابق جديد للبيانات المخزنة.

السطر 80: وظيفة الإعلان عن declareOutcome تسمح لمالك العقد بتعيين المباراة على أنها "مقررة" ، وتعيين المشارك الذي فاز.

الأسطر 102-166: جميع الوظائف التالية قابلة للاستدعاء من قبل الجمهور. هذه هي البيانات للقراءة فقط والمتاحة للجمهور بشكل عام:

  • تُرجع الدالة getPendingMatches قائمة بمعرفات جميع التطابقات التي تكون حالتها الحالية "معلقة".
  • تقوم الدالة getAllMatches بإرجاع قائمة بمعرفات كافة التطابقات.
  • تقوم الدالة getMatch بإرجاع التفاصيل الكاملة لمطابقة واحدة ، محددة بواسطة المعرف.

تعلن الأسطر 193-204 عن الوظائف المخصصة بشكل أساسي للاختبار وتصحيح الأخطاء والتشخيص.

  • اختبار الوظيفة testConnection فقط أننا قادرون على الاتصال بالعقد.
  • تقوم الوظيفة getAddress بإرجاع عنوان هذا العقد.
  • تضيف الوظيفة addTestData مجموعة من المطابقات الاختبارية إلى قائمة التطابقات.

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

رهان الملاكمة: عقد العميل

من المهم تحديد مسؤولية عقد العميل (عقد الرهان) وما هو غير مسؤول عنه. عقد العميل غير مسؤول عن الاحتفاظ بقوائم مباريات الملاكمة الحقيقية أو إعلان نتائجها. نحن "نثق" (نعم أعلم ، هناك تلك الكلمة الحساسة - أه أوه - سنناقش هذا في الجزء 3) أوراكل لتلك الخدمة. عقد العميل مسؤول عن قبول الرهانات. إنها مسؤولة عن الخوارزمية التي تقسم المكاسب وتحولها إلى حسابات الفائزين بناءً على نتيجة المباراة (كما وردت من أوراكل).

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

الوظائف الرئيسية المقدمة

  • قائمة بجميع المباريات المعلقة
  • احصل على تفاصيل مباراة محددة
  • احصل على حالة ونتائج مباراة محددة
  • وضع رهان
  • طلب / استلام المكاسب

مراجعة كود العميل

تستند هذه المراجعة بالكامل على BoxingBets.sol ؛ تشير أرقام الأسطر إلى هذا الملف.

يحدد السطران 12 و 13 ، الأسطر الأولى من الكود في العقد ، بعض التعيينات التي سنخزن فيها بيانات العقد.

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

 mapping(address => bytes32[]) private userToBets;

يقوم السطر 13 بتعيين معرف المطابقة الفريد إلى قائمة مثيلات الرهان. بهذا ، يمكننا ، لأي مباراة ، الحصول على قائمة بجميع الرهانات التي تم إجراؤها لتلك المباراة.

 mapping(bytes32 => Bet[]) private matchToBets;

يرتبط السطران 17 و 18 بالاتصال بـ oracle الخاص بنا. أولاً ، في متغير boxingOracleAddr ، نقوم بتخزين عنوان عقد أوراكل (يتم ضبطه على صفر افتراضيًا). يمكننا تثبيت عنوان oracle بشكل ثابت ، ولكن بعد ذلك لن نتمكن من تغييره مطلقًا. (عدم القدرة على تغيير عنوان أوراكل قد يكون شيئًا جيدًا أو سيئًا - يمكننا مناقشة ذلك في الجزء 3). يُنشئ السطر التالي مثيلاً لواجهة oracle (التي تم تعريفها في OracleInterface.sol) ويخزنها في متغير.

 //boxing results oracle address internal boxingOracleAddr = 0; OracleInterface internal boxingOracle = OracleInterface(boxingOracleAddr);

إذا قفزت إلى السطر 58 ، فسترى وظيفة setOracleAddress ، والتي يمكن من خلالها تغيير عنوان oracle هذا ، وفيها يتم إعادة إنشاء مثيل boxingOracle بعنوان جديد.

يحدد السطر 21 الحد الأدنى لحجم الرهان لدينا ، في وي. هذه بالطبع كمية صغيرة جدًا ، فقط 0.000001 إيثر.

 uint internal minimumBet = 1000000000000;

في السطر 58 و 66 على التوالي ، لدينا setOracleAddress و getOracleAddress . يحتوي setOracleAddress على مُعدِّل المالك الوحيد لأن مالك العقد هو الوحيد الذي يمكنه تبديل أوراكل onlyOwner (ربما ليست فكرة جيدة ، لكننا سنشرحها في الجزء 3). من ناحية أخرى ، يمكن استدعاء دالة getOracleAddress ؛ يمكن لأي شخص رؤية ما يتم استخدام أوراكل.

 function setOracleAddress(address _oracleAddress) external onlyOwner returns (bool) {... function getOracleAddress() external view returns (address) { ....

في السطر 72 و 79 ، لدينا وظائف getBettableMatches و getMatch ، على التوالي. لاحظ أن هؤلاء يقومون ببساطة بإعادة توجيه المكالمات إلى أوراكل ، وإرجاع النتيجة.

 function getBettableMatches() public view returns (bytes32[]) {... function getMatch(bytes32 _matchId) public view returns ( ....

وظيفة placeBet مهمة جدًا (السطر 108).

 function placeBet(bytes32 _matchId, uint8 _chosenWinner) public payable { ...

السمة اللافتة للنظر في هذا هو معدل payable ؛ لقد كنا مشغولين جدًا في مناقشة ميزات اللغة العامة التي لم نتطرق بعد إلى الميزة المركزية المهمة المتمثلة في القدرة على إرسال الأموال مع مكالمات الوظائف! هذا هو الأساس - إنها وظيفة يمكنها قبول مبلغ من المال مع أي حجج وبيانات أخرى يتم إرسالها.

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

 require(msg.value >= minimumBet, "Bet amount must be >= minimum bet");

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

 address(this).transfer(msg.value);

أخيرًا ، في السطر 136 ، لدينا وظيفة مساعد اختبار / تصحيح الأخطاء التي ستساعدنا على معرفة ما إذا كان العقد مرتبطًا بأداة أوراكل صالحة أم لا:

 function testOracleConnection() public view returns (bool) { return boxingOracle.testConnection(); }

تغليف

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

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

مزيد من الخطوات الاختيارية

أود بشدة أن أشجع القراء الذين يرغبون في معرفة المزيد ، لأخذ هذا الرمز واللعب به. نفِّذ ميزات جديدة. أصلح أي أخطاء. تنفيذ ميزات غير مطبقة (مثل واجهة الدفع). اختبر استدعاءات الوظائف. قم بتعديلها وإعادة الاختبار لمعرفة ما سيحدث. أضف واجهة web3 الأمامية. إضافة وسيلة لإزالة التطابقات أو تعديل نتائجها (في حالة حدوث خطأ). ماذا عن المباريات الملغاة؟ تنفيذ الوحي الثاني. بالطبع ، العقد مجاني لاستخدام العديد من الأوراكل كما يحلو له ، ولكن ما هي المشاكل التي يسببها ذلك؟ استمتع بها هذه طريقة رائعة للتعلم ، وعندما تفعل ذلك بهذه الطريقة (وتستمتع بها) ، فمن المؤكد أنك ستحتفظ بالمزيد مما تعلمته.

عينة قائمة غير شاملة بالأشياء التي يجب تجربتها:

  • قم بتشغيل كل من العقد و oracle في testnet المحلية (في الكمأة ، كما هو موضح في الجزء 1) واستدعاء جميع الوظائف القابلة للاستدعاء وجميع وظائف الاختبار.
  • أضف وظيفة لحساب المكاسب ودفعها عند الانتهاء من المباراة.
  • إضافة وظيفة لاسترداد جميع الرهانات في حالة التعادل.
  • أضف ميزة لطلب استرداد أو إلغاء رهان قبل بدء المباراة.
  • أضف ميزة للسماح بإلغاء المطابقات في بعض الأحيان (في هذه الحالة سيحتاج الجميع إلى استرداد الأموال).
  • قم بتطبيق ميزة لضمان أن الوسيطة التي كانت موجودة عندما وضع المستخدم رهانًا هي نفس الوسيطة التي سيتم استخدامها لتحديد نتيجة تلك المباراة.
  • قم بتنفيذ أوراكل آخر (ثاني) ، والذي يحتوي على بعض الميزات المختلفة المرتبطة به ، أو ربما يخدم رياضة أخرى غير الملاكمة (لاحظ أن عدد المشاركين والقائمة تسمح بأنواع مختلفة من الألعاب الرياضية ، لذلك فنحن لسنا مقيدين في الواقع بالملاكمة فقط) .
  • نفِّذ getMostRecentMatch بحيث تُرجع بالفعل أحدث مطابقة مضافة أو المطابقة الأقرب إلى التاريخ الحالي من حيث وقت حدوثها.
  • تطبيق معالجة الاستثناءات.

بمجرد أن تكون على دراية بآليات العلاقة بين العقد و oracle ، في الجزء 3 من هذه السلسلة المكونة من ثلاثة أجزاء ، سنناقش بعض القضايا الإستراتيجية والتصميمية والفلسفية التي أثارها هذا المثال.