ProHoster > بلوق > إدارة > حول كيفية كتابة عقد ذكي ونشره في شبكة Telegram المفتوحة (TON)
حول كيفية كتابة عقد ذكي ونشره في شبكة Telegram المفتوحة (TON)
حول كيفية كتابة ونشر عقد ذكي في TON
عن ماذا تتحدث هذه المقالة؟
سأتحدث في المقال عن كيفية مشاركتي في مسابقة Telegram blockchain الأولى (من اثنتين)، ولم أحصل على جائزة، وقررت تسجيل التجربة في المقالة حتى لا تغرق في غياهب النسيان، وربما تساعد شخص ما.
نظرًا لأنني لم أرغب في كتابة تعليمات برمجية مجردة، ولكن للقيام بشيء ناجح، فقد كتبت في المقالة عقدًا ذكيًا لليانصيب الفوري وموقعًا على الويب يعرض بيانات العقد الذكي مباشرة من TON دون استخدام التخزين الوسيط.
ستكون هذه المقالة مفيدة لأولئك الذين يرغبون في إبرام أول عقد ذكي لهم في TON، ولكنهم لا يعرفون من أين يبدأون.
وباستخدام مثال اليانصيب، سأنتقل من تهيئة البيئة إلى نشر عقد ذكي والتفاعل معه وكتابة موقع لتلقي البيانات ونشرها.
حول المشاركة في المسابقة
في أكتوبر الماضي، أعلنت Telegram عن مسابقة blockchain بلغات جديدة Fift и FunC. كان من الضروري اختيار كتابة أي من العقود الذكية الخمسة المقترحة. اعتقدت أنه سيكون من الجيد أن أفعل شيئًا خارجًا عن المألوف، أتعلم لغة وأقوم بشيء ما، حتى لو لم أضطر إلى كتابة أي شيء آخر في المستقبل. بالإضافة إلى أن الموضوع يتم الاستماع إليه باستمرار.
تجدر الإشارة إلى أنه ليس لدي أي خبرة في تطوير العقود الذكية.
لقد خططت للمشاركة حتى النهاية، طالما اتضح ثم أكتب مقالة مراجعة، لكنني فشلت على الفور في المقالة الأولى. أنا كتب المحفظة مع التوقيع المتعدد على FunC وكان يعمل بشكل عام. اتخذت كأساس العقد الذكي على Solidity.
في ذلك الوقت، اعتقدت أن هذا كان بالتأكيد كافيًا لأخذ مكان ما على الأقل للفوز بالجائزة. ونتيجة لذلك، أصبح حوالي 40 من أصل 60 مشاركاً فائزين ولم أكن من بينهم. بشكل عام، هذا ليس شيئا فظيعا، ولكن هناك شيء واحد توترني. في وقت إعلان النتائج لم يتم إجراء مراجعة مع اختبار لعقدي، سألت المشاركين في الدردشة إذا كان هناك أي شخص آخر ليس لديه ذلك، لم يكن هناك أي شخص.
على ما يبدو، بعد الاهتمام برسائلي، نشر الحكام تعليقًا بعد يومين وما زلت لا أفهم ذلك، لقد فاتتهم عقدي الذكي عن طريق الخطأ أثناء التحكيم، أو ببساطة اعتبروا أنه كان سيئًا للغاية لدرجة أنه لا يحتاج إلى تعليق. لقد طرحت سؤالا على الصفحة، ولكن لم أتلق ردا. على الرغم من أن الحكم ليس سرا، إلا أنني اعتبرت أنه من غير الضروري كتابة رسائل شخصية.
تم إنفاق الكثير من الوقت على الفهم، لذلك تقرر كتابة مقال. نظرًا لعدم وجود الكثير من المعلومات حتى الآن، ستساعد المقالة في توفير الوقت لجميع المهتمين.
مفهوم العقود الذكية في TON
قبل أن تكتب شيئا ما، تحتاج إلى معرفة أي جانب للتعامل مع هذا الشيء على الإطلاق. لذلك، سأخبرك الآن، ما هي الأجزاء التي يتكون منها النظام. بتعبير أدق، ما هي الأجزاء التي تحتاج إلى معرفتها لكتابة نوع من عقد العمل على الأقل.
سنركز على كتابة عقد ذكي والعمل معه TON Virtual Machine (TVM), Fift и FunCلذا فإن المقالة أشبه بوصف لتطوير برنامج تقليدي. لن نتناول هنا كيفية عمل المنصة نفسها.
عموما حول كيفية عمله TVM واللغة Fift هناك وثائق رسمية جيدة. أثناء مشاركتي في المسابقة والآن أثناء كتابة العقد الحالي، كنت أتوجه إليها كثيرًا.
اللغة الرئيسية التي تكتب بها العقود الذكية هي FunC. لا يوجد أي توثيق لها في الوقت الحالي، لذلك من أجل كتابة شيء ما، تحتاج إلى دراسة أمثلة على العقود الذكية من المستودع الرسمي وتنفيذ اللغة نفسها هناك، بالإضافة إلى أنه يمكنك مشاهدة أمثلة على العقود الذكية في المسابقتين السابقتين. الروابط في نهاية المقال.
لنفترض أننا كتبنا بالفعل عقدًا ذكيًا لـ FunC، بعد ذلك نقوم بتجميع الكود في مجمع Fift.
لا يزال يتعين نشر العقد الذكي المجمع. للقيام بذلك، تحتاج إلى كتابة وظيفة على Fift، والذي سيأخذ رمز العقد الذكي وبعض المعلمات الأخرى كمدخلات، وسيكون الإخراج ملفًا بالامتداد .boc (والتي تعني "كيس الخلايا")، واعتمادًا على كيفية كتابتها، يتم إنشاء مفتاح خاص وعنوان بناءً على رمز العقد الذكي. يمكن بالفعل إرسال Grams إلى عنوان عقد ذكي لم يتم نشره بعد.
لنشر العقد الذكي في TON المستلمة .boc سيلزم إرسال الملف إلى blockchain باستخدام عميل خفيف (المزيد حول ذلك أدناه). ولكن قبل النشر، تحتاج إلى نقل الجرام إلى العنوان الذي تم إنشاؤه، وإلا فلن يتم نشر العقد الذكي. بعد النشر، سيكون من الممكن التفاعل مع العقد الذكي عن طريق إرسال رسائل إليه من الخارج (على سبيل المثال، باستخدام عميل خفيف) أو من الداخل (على سبيل المثال، يرسل عقد ذكي رسالة إلى آخر داخل TON).
بعد أن نفهم كيفية نشر الكود، يصبح الأمر أسهل. نحن نعرف تقريبًا ما نريد كتابته وكيف سيعمل برنامجنا. وأثناء الكتابة، نبحث عن كيفية تنفيذ ذلك بالفعل في العقود الذكية الحالية، أو ننظر إلى كود التنفيذ Fift и FunC في المستودع الرسمي، أو ابحث في الوثائق الرسمية.
في كثير من الأحيان كنت أبحث عن كلمات رئيسية في دردشة Telegram حيث تجمع جميع المشاركين في المسابقة وموظفي Telegram، بما في ذلك، وحدث أنه خلال المسابقة اجتمع الجميع هناك وبدأوا في مناقشة Fift وFunC. الرابط في نهاية المقال.
حان الوقت للانتقال من النظرية إلى التطبيق.
إعداد البيئة للعمل مع TON
كل ما سيتم وصفه في المقالة قمت به على نظام MacOS وقمت بالتحقق منه مرة أخرى في Ubuntu 18.04 LTS النظيف على Docker.
أول شيء فعله هو التنزيل والتثبيت lite-client يمكنك من خلاله إرسال الطلبات إلى TON.
تصف التعليمات الموجودة على الموقع الرسمي عملية التثبيت بشيء من التفصيل وبشكل واضح وتغفل بعض التفاصيل. نتبع هنا التعليمات على طول الطريق، لتثبيت التبعيات المفقودة. لم أقم بتجميع كل مشروع بنفسي وقمت بتثبيته من مستودع Ubuntu الرسمي (على نظام MacOS الذي استخدمته brew).
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json
إذا كان البناء ناجحًا، فبعد الإطلاق، سترى سجل اتصال العميل الخفيف بالعقدة.
[ 1][t 2][1582054822.963129282][lite-client.h:201][!testnode] conn ready
[ 2][t 2][1582054823.085654020][lite-client.cpp:277][!testnode] server version is 1.1, capabilities 7
[ 3][t 2][1582054823.085725069][lite-client.cpp:286][!testnode] server time is 1582054823 (delta 0)
...
يمكنك تشغيل الأمر help ومعرفة ما هي الأوامر المتاحة.
help
دعونا ندرج الأوامر التي سنستخدمها في هذه المقالة.
list of available commands:
last Get last block and state info from server
sendfile <filename> Load a serialized message from <filename> and send it to server
getaccount <addr> [<block-id-ext>] Loads the most recent state of specified account; <addr> is in [<workchain>:]<hex-or-base64-addr> format
runmethod <addr> [<block-id-ext>] <method-id> <params>... Runs GET method <method-id> of account <addr> with specified parameters
last получает последний созданный блок с сервера.
sendfile <filename> отправляет в TON файл с сообщением, именно с помощью этой команды публикуется смарт-контракт и запрсосы к нему.
getaccount <addr> загружает текущее состояние смарт-контракта с указанным адресом.
runmethod <addr> [<block-id-ext>] <method-id> <params> запускает get-методы смартконтракта.
الآن نحن على استعداد لكتابة العقد نفسه.
تطبيق
فكرة
كما كتبت أعلاه، فإن العقد الذكي الذي نكتبه هو يانصيب.
علاوة على ذلك، فهذا ليس يانصيبًا تحتاج فيه إلى شراء تذكرة والانتظار لمدة ساعة أو يوم أو شهر، ولكنه يانصيب فوري ينقل فيه المستخدم إلى عنوان العقد N جرامًا، ويعود على الفور 2 * N جرام أو يخسر. سنجعل احتمالية الفوز حوالي 40%. إذا لم يكن هناك غرامات كافية للدفع، فسنعتبر المعاملة بمثابة تجديد.
علاوة على ذلك، من المهم أن يمكن رؤية الرهانات في الوقت الفعلي وبطريقة مريحة، حتى يتمكن المستخدم من فهم ما إذا كان قد فاز أم خسر على الفور. لذلك، تحتاج إلى إنشاء موقع ويب يعرض الأسعار والنتيجة مباشرة من TON.
كتابة عقد ذكي
للراحة، قمت بإبراز التعليمات البرمجية لـ FunC، ويمكن العثور على المكون الإضافي وتثبيته في بحث Visual Studio Code، إذا كنت تريد فجأة إضافة شيء ما، فقمت بنشر المكون الإضافي في المجال العام. أيضًا، قام شخص ما مسبقًا بإنشاء مكون إضافي للعمل مع Fift، ويمكنك أيضًا تثبيته والعثور عليه في VSC.
قم بإنشاء مستودع على الفور حيث سنلتزم بالنتائج المتوسطة.
لتسهيل حياتنا، سنكتب عقدًا ذكيًا ونختبره محليًا حتى يصبح جاهزًا. فقط بعد ذلك سوف نقوم بنشره في TON.
يحتوي العقد الذكي على طريقتين خارجيتين يمكن الوصول إليهما. أولاً، recv_external() يتم تنفيذ هذه الوظيفة عندما يأتي طلب العقد من العالم الخارجي، أي ليس من TON، على سبيل المثال، عندما نشكل بأنفسنا رسالة ونرسلها عبر عميل لايت. ثانية، recv_internal() يحدث هذا عندما يشير أي عقد داخل TON نفسها إلى عقدنا. في كلتا الحالتين، يمكنك تمرير المعلمات إلى الدالة.
لنبدأ بمثال بسيط سيعمل إذا تم نشره، لكنه لا يحتوي على أي حمل وظيفي.
هنا لا بد من شرح ما slice. جميع البيانات المخزنة في TON Blockchain عبارة عن مجموعة TVM cell أو ببساطة cell، يمكن لهذه الخلية تخزين ما يصل إلى 1023 بت من البيانات وما يصل إلى 4 إشارات إلى خلايا أخرى.
TVM cell slice أو slice هو جزء من الموجود cell يتم استخدامه لتحليله، وسوف يكون واضحا أكثر. الشيء الرئيسي بالنسبة لنا هو أن نتمكن من التحول إلى عقد ذكي slice واعتمادًا على نوع الرسالة، قم بمعالجة البيانات فيها recv_external() أو recv_internal().
impure - كلمة أساسية تشير إلى أن الوظيفة تغير بيانات العقد الذكي.
لقد قمنا بتجميع كود المجمع Fift في lottery-compiled.fif:
// lottery-compiled.fif
"Asm.fif" include
// automatically generated from `/Users/rajymbekkapisev/TON/ton/crypto/smartcont/stdlib.fc` `./lottery-code.fc`
PROGRAM{
DECLPROC recv_internal
DECLPROC recv_external
recv_internal PROC:<{
// in_msg
DROP //
}>
recv_external PROC:<{
// in_msg
DROP //
}>
}END>c
ويمكن تشغيله محليا، ولهذا سنقوم بإعداد البيئة.
لاحظ أن السطر الأول يتصل Asm.fif، هذا هو الكود المكتوب في مجمع Fift for Fift.
وبما أننا نريد تشغيل العقد الذكي واختباره، فسنقوم بإنشاء ملف محليًا lottery-test-suite.fif وانسخ الكود المترجم هناك، مع استبدال السطر الأخير فيه، والذي يكتب كود العقد الذكي إلى ثابت codeلتمريره بعد ذلك إلى الجهاز الظاهري:
"TonUtil.fif" include
"Asm.fif" include
PROGRAM{
DECLPROC recv_internal
DECLPROC recv_external
recv_internal PROC:<{
// in_msg
DROP //
}>
recv_external PROC:<{
// in_msg
DROP //
}>
}END>s constant code
بينما يبدو الأمر واضحًا، فلنضيف الآن الكود الذي سنستخدمه لتشغيل TVM إلى نفس الملف.
В c7 نكتب السياق، أي البيانات التي سيتم من خلالها إطلاق TVM (أو حالة الشبكة). حتى أثناء المنافسة، أظهر أحد المطورين كيفية الإنشاء c7 وأنا نسخت. في هذه المقالة، قد نحتاج إلى التغيير rand_seed وبما أن توليد رقم عشوائي يعتمد عليه ولا يتغير، فسيتم إرجاع نفس الرقم في كل مرة.
recv_internal и recv_external ستكون الثوابت ذات القيمة 0 و-1 مسؤولة عن استدعاء الوظائف المناسبة في العقد الذكي.
نحن الآن جاهزون لإنشاء الاختبار الأول لعقدنا الذكي الفارغ. للتوضيح، في الوقت الحالي، سنضيف جميع الاختبارات إلى نفس الملف. lottery-test-suite.fif.
لنقم بإنشاء متغير storage واكتب فارغة cellسيكون هذا بمثابة تخزين العقد الذكي.
message هذه هي الرسالة التي سنرسلها إلى جهة الاتصال الذكية من الخارج. دعونا نجعلها فارغة في الوقت الراهن.
الآن نحن بحاجة إلى إضافة وظيفة. دعونا نتعامل أولاً مع الرسائل التي تأتي من العالم الخارجي إليه recv_external()
يختار المطور بنفسه تنسيق الرسالة الذي يمكن أن يقبله العقد.
لكن عادة
أولاً، نريد حماية عقدنا من العالم الخارجي وجعله بحيث لا يتمكن سوى صاحب العقد من إرسال رسائل خارجية إليه.
ثانيًا، عندما نرسل رسالة صالحة إلى TON، نريد أن يحدث ذلك مرة واحدة بالضبط، وعندما يتم إرسال نفس الرسالة مرة أخرى، يرفضها العقد الذكي.
لذلك، في كل عقد تقريبًا، يتم حل هاتين المشكلتين، نظرًا لأن عقدنا يقبل الرسائل الخارجية، فنحن بحاجة أيضًا إلى الاهتمام بهذا الأمر.
سنفعل ذلك بترتيب عكسي. أولاً، نحل مشكلة التكرار، إذا كان العقد قد تلقى بالفعل مثل هذه الرسالة وقام بمعالجتها، فلن ينفذها مرة أخرى. وبعد ذلك سنحل المشكلة بحيث لا تتمكن سوى دائرة معينة من الأشخاص من إرسال رسائل إلى العقد الذكي.
هناك طرق مختلفة لحل مشكلة الرسائل المتكررة. سنفعل ذلك بهذه الطريقة. في العقد الذكي، نقوم بتهيئة عداد الرسائل المستلمة بقيمة أولية 0. وفي كل رسالة إلى العقد الذكي، سنضيف القيمة الحالية للعداد. إذا كانت قيمة العداد في الرسالة لا تتطابق مع القيمة الموجودة في العقد الذكي، فإننا لا نقوم بمعالجتها، وإذا حدث ذلك، فإننا نقوم بمعالجة وزيادة العداد في العقد الذكي بمقدار 1.
نعود إلى lottery-test-suite.fif وأضف الاختبار الثاني إليه. لنرسل رقمًا غير صالح، يجب أن يطرح الرمز استثناءً. على سبيل المثال، لنفترض أنه تم تخزين 166 في بيانات العقد، وسنرسل 165.
<b 166 32 u, b> storage !
<b 165 32 u, b> message !
message @
recv_external
code
storage @
c7
runvmctx
drop
exit_code !
."Exit code " exit_code @ . cr
exit_code @ 33 - abort"Test #2 Not passed"
هيا نركض.
~/TON/build/crypto/fift -s lottery-test-suite.fif
وسنرى أن الاختبار قد تم تنفيذه مع حدوث خطأ.
[ 1][t 0][1582283084.210902214][words.cpp:3046] lottery-test-suite.fif:67: abort": Test #2 Not passed
[ 1][t 0][1582283084.210941076][fift-main.cpp:196] Error interpreting file `lottery-test-suite.fif`: error interpreting included file `lottery-test-suite.fif` : lottery-test-suite.fif:67: abort": Test #2 Not passed
في هذه المرحلة lottery-test-suite.fif يجب أن تبدو رابط.
الآن دعونا نضيف المنطق المضاد إلى العقد الذكي lottery-code.fc.
() recv_internal(slice in_msg) impure {
;; TODO: implementation
}
() recv_external(slice in_msg) impure {
if (slice_empty?(in_msg)) {
return ();
}
int msg_seqno = in_msg~load_uint(32);
var ds = begin_parse(get_data());
int stored_seqno = ds~load_uint(32);
throw_unless(33, msg_seqno == stored_seqno);
}
В slice in_msg تكمن الرسالة التي نرسلها.
أول شيء نقوم به هو التحقق مما إذا كانت هناك بيانات في الرسالة، وإذا لم يكن الأمر كذلك، فإننا نخرج للتو.
بعد ذلك، نقوم بتحليل الرسالة. in_msg~load_uint(32) الأحمال رقم 165، 32 بت unsigned int من الرسالة المرسلة.
بعد ذلك، نقوم بتحميل 32 بت من مخزن العقد الذكي. نتحقق من أن الرقم الذي تم تحميله يطابق الرقم الذي تم تمريره، وإذا لم يكن كذلك، فإننا نطرح استثناءً. في حالتنا، نظرًا لأننا نمرر في حالة عدم تطابق، فيجب طرح استثناء.
انسخ الكود الناتج إلى lottery-test-suite.fif، ولا ننسى استبدال السطر الأخير.
التأكد من اجتياز الاختبار:
~/TON/build/crypto/fift -s lottery-test-suite.fif
هنا يمكنك رؤية الالتزام المقابل مع النتائج الحالية.
لاحظ أنه من غير المناسب نسخ كود العقد الذكي المترجم باستمرار إلى ملف الاختبار، لذلك سنكتب نصًا يكتب الكود إلى ثابت لنا، وسنقوم ببساطة بتوصيل الكود المترجم باختباراتنا باستخدام "include".
قم بإنشاء ملف في مجلد المشروع build.sh مع المحتوى التالي.
الآن، يكفي تشغيل البرنامج النصي الخاص بنا لتجميع العقد. لكن بالإضافة إلى ذلك، علينا كتابته في صورة ثابت code. لذلك سوف نقوم بإنشاء ملف جديد lotter-compiled-for-test.fif، والتي سوف نقوم بإدراجها في الملف lottery-test-suite.fif.
دعونا نضيف التعليمات البرمجية إلى البرنامج النصي sh الذي سيؤدي ببساطة إلى تكرار الملف المترجم فيه lotter-compiled-for-test.fif وتغيير السطر الأخير فيه.
# copy and change for test
cp lottery-compiled.fif lottery-compiled-for-test.fif
sed '$d' lottery-compiled-for-test.fif > test.fif
rm lottery-compiled-for-test.fif
mv test.fif lottery-compiled-for-test.fif
echo -n "}END>s constant code" >> lottery-compiled-for-test.fif
الآن، للتحقق، قم بتشغيل البرنامج النصي الناتج وسنقوم بإنشاء ملف lottery-compiled-for-test.fifوالتي سوف ندرجها في موقعنا lottery-test-suite.fif
В lottery-test-suite.fif قم بإزالة رمز العقد وأضف السطر "lottery-compiled-for-test.fif" include.
قم بإجراء الاختبارات لمعرفة ما إذا كانت ستنجح.
~/TON/build/crypto/fift -s lottery-test-suite.fif
عظيم، الآن لأتمتة إطلاق الاختبارات، فلنقم بإنشاء ملف test.sh، والذي سيتم تنفيذه أولاً build.shثم قم بإجراء الاختبارات.
دعنا نفعل test.sh وتشغيل للتأكد من نجاح الاختبارات.
chmod +x ./test.sh
./test.sh
نتحقق من تجميع العقد وتنفيذ الاختبارات.
عظيم، الآن عند بدء التشغيل test.sh سيتم تجميع الاختبارات وتشغيلها على الفور. هنا رابط ل ارتكب.
حسنًا، قبل أن نواصل، دعونا نفعل شيئًا آخر من أجل الراحة.
لنقم بإنشاء مجلد build حيث سنقوم بتخزين العقد المنسوخ واستنساخه مكتوبًا في ملف ثابت lottery-compiled.fif, lottery-compiled-for-test.fif. سنقوم أيضًا بإنشاء مجلد test أين سيتم تخزين ملف الاختبار lottery-test-suite.fif وربما ملفات الدعم الأخرى. رابط إلى التغييرات ذات الصلة.
دعونا نواصل تطوير العقد الذكي.
يجب أن يكون التالي اختبارًا للتحقق من استلام الرسالة وتحديث العداد في المتجر عندما نرسل الرقم الصحيح. لكننا سنفعل ذلك لاحقًا.
الآن دعونا نفكر في بنية البيانات والبيانات التي يجب تخزينها في العقد الذكي.
سأصف كل ما لدينا.
`seqno` 32-х битное целое положительное число счетчик.
`pubkey` 256-ти битное целое положительное число публичный ключ, с помощью которого, мы будем проверять подпись отправленного извне сообщения, о чем ниже.
`order_seqno` 32-х битное целое положительное число хранит счетчик количества ставок.
`number_of_wins` 32-х битное целое положительное число хранит количество побед.
`incoming_amount` тип данных Gram (первые 4 бита отвечает за длину), хранит общее количество грамов, которые были отправлены на контртакт.
`outgoing_amount` общее количество грамов, которое было отправлено победителям.
`owner_wc` номер воркчейна, 32-х битное (в некоторых местах написано, что 8-ми битное) целое число. В данный момент всего два -1 и 0.
`owner_account_id` 256-ти битное целое положительное число, адрес контракта в текущем воркчейне.
`orders` переменная типа словарь, хранит последние двадцать ставок.
والخطوة التالية هي كتابة وظيفتين. دعنا نتصل بالأول pack_state()، والتي ستحزم البيانات للتخزين اللاحق في مخزن العقد الذكي. والثاني سوف نتصل به unpack_state() سوف يقرأ ويعيد البيانات من التخزين.
_ pack_state(int seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) inline_ref {
return begin_cell()
.store_uint(seqno, 32)
.store_uint(pubkey, 256)
.store_uint(order_seqno, 32)
.store_uint(number_of_wins, 32)
.store_grams(incoming_amount)
.store_grams(outgoing_amount)
.store_int(owner_wc, 32)
.store_uint(owner_account_id, 256)
.store_dict(orders)
.end_cell();
}
_ unpack_state() inline_ref {
var ds = begin_parse(get_data());
var unpacked = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_uint(32), ds~load_grams(), ds~load_grams(), ds~load_int(32), ds~load_uint(256), ds~load_dict());
ds.end_parse();
return unpacked;
}
نضيف هاتين الوظيفتين في بداية العقد الذكي. اتضح مثله نتيجة وسيطة.
لحفظ البيانات، سوف تحتاج إلى استدعاء الوظيفة المضمنة set_data() وسوف يكتب البيانات من pack_state() في تخزين العقد الذكي.
والآن بعد أن أصبح لدينا وظائف ملائمة لكتابة البيانات وقراءتها، يمكننا المضي قدمًا.
نحتاج إلى التحقق من أن الرسالة الواردة موقعة من مالك العقد (أو مستخدم آخر لديه حق الوصول إلى المفتاح الخاص).
عندما ننشر عقدًا ذكيًا، يمكننا تهيئته بالبيانات التي نحتاجها في المتجر، والتي سيتم حفظها للاستخدام المستقبلي. سنكتب المفتاح العام هناك حتى نتمكن من التحقق من أن توقيع الرسالة الواردة تم بواسطة المفتاح الخاص المقابل.
قبل المتابعة، دعونا ننشئ مفتاحًا خاصًا ونكتبه إليه test/keys/owner.pk. للقيام بذلك، قم بتشغيل Fift في الوضع التفاعلي وقم بتشغيل أربعة أوامر.
`newkeypair` генерация публичного и приватного ключа и запись их в стек.
`drop` удаления из стека верхнего элемента (в данном случае публичный ключ)
`.s` просто посмотреть что лежит в стеке в данный момент
`"owner.pk" B>file` запись приватного ключа в файл с именем `owner.pk`.
`bye` завершает работу с Fift.
لنقم بإنشاء مجلد keys داخل مجلد test واكتب المفتاح الخاص هناك.
mkdir test/keys
cd test/keys
~/TON/build/crypto/fift -i
newkeypair
ok
.s
BYTES:128DB222CEB6CF5722021C3F21D4DF391CE6D5F70C874097E28D06FCE9FD6917 BYTES:DD0A81AAF5C07AAAA0C7772BB274E494E93BB0123AA1B29ECE7D42AE45184128
drop
ok
"owner.pk" B>file
ok
bye
نرى الملف في المجلد الحالي owner.pk.
نقوم بإزالة المفتاح العام من المكدس، وعندما نحتاج إليه يمكننا الحصول عليه من المفتاح الخاص.
الآن نحن بحاجة إلى كتابة التحقق من التوقيع. لنبدأ بالاختبار. أولاً، نقرأ المفتاح الخاص من الملف باستخدام الوظيفة file>B واكتبها في متغير owner_private_key، ثم استخدم الدالة priv>pub قم بتحويل المفتاح الخاص إلى عام واكتب النتيجة إليه owner_public_key.
ونتيجة لذلك، تتم كتابة الرسالة التي سنرسلها إلى العقد الذكي إلى المتغير message_to_send، حول الوظائف hashu, ed25519_sign_uint يمكنك القراءة في الوثائق الخمس.
لنجري الاختبار وسيفشل، لذلك سنقوم بتغيير العقد الذكي حتى يتمكن من تلقي رسائل بهذا التنسيق والتحقق من التوقيع.
أولا، نقرأ 512 بت من التوقيع من الرسالة ونكتبها إلى متغير، ثم نقرأ 32 بت من متغير العداد.
نظرًا لأن لدينا وظيفة لقراءة البيانات من مخزن العقد الذكي، فسوف نستخدمها.
مزيد من التحقق من العداد المنقول مع التخزين والتحقق من التوقيع. إذا كان هناك شيء غير متطابق، فإننا نطرح استثناءً بالرمز المقابل.
var signature = in_msg~load_bits(512);
var message = in_msg;
int msg_seqno = message~load_uint(32);
(int stored_seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) = unpack_state();
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(in_msg), signature, pubkey));
لنجري الاختبارات ونرى أن الاختبار الثاني يفشل. لسببين، نقص البتات في الرسالة ونقص البتات في وحدة التخزين، وبالتالي يتعطل الكود عند التحليل. نحتاج إلى إضافة توقيع الرسالة التي نرسلها ونسخ مساحة التخزين من الاختبار الأخير.
في الاختبار الثاني، سنضيف توقيع الرسالة ونغير مساحة تخزين العقد الذكي. مثل يبدو وكأنه ملف مع الاختبارات في الوقت الراهن.
لنكتب الاختبار الرابع الذي سنرسل فيه رسالة موقعة بالمفتاح الخاص لشخص آخر. لنقم بإنشاء مفتاح خاص آخر وحفظه في ملف not-owner.pk. لنوقع الرسالة بهذا المفتاح الخاص. لنجري الاختبارات ونتأكد من اجتياز جميع الاختبارات. يقترف بهذه اللحظة.
الآن يمكننا أخيرًا الانتقال إلى تنفيذ منطق العقد الذكي.
В recv_external() سوف نتلقى نوعين من الرسائل.
وبما أن عقدنا سوف يتراكم خسائر اللاعبين، فيجب تحويل هذه الأموال إلى منشئ اليانصيب. تتم كتابة عنوان المحفظة الخاص بمنشئ اليانصيب في الخزينة عند إنشاء العقد.
فقط في حالة احتياجنا إلى القدرة على تغيير العنوان الذي نرسل إليه جرام الخاسرين. يجب أن نتمكن أيضًا من إرسال الجرام من اليانصيب إلى عنوان المالك.
لنبدأ بالأول. دعونا أولاً نكتب اختبارًا للتحقق من أنه بعد إرسال الرسالة، قام العقد الذكي بحفظ العنوان الجديد في وحدة التخزين. لاحظ أنه في الرسالة، بالإضافة إلى العداد والعنوان الجديد، نرسل أيضًا action عدد صحيح غير سالب 7 بت، اعتمادًا على ذلك، سنختار كيفية معالجة الرسالة في العقد الذكي.
<b 0 32 u, 1 @ 7 u, new_owner_wc @ 32 i, new_owner_account_id @ 256 u, b> message_to_sign !
في الاختبار، يمكنك أن ترى كيف يتم إلغاء تحقيق تخزين العقد الذكي storage في الخمس. تم وصف إلغاء التسلسل المتغير في وثائق Fift.
دعونا نجري الاختبار ونرى ما إذا كان سيفشل. الآن دعونا نضيف منطقًا لتغيير عنوان مالك اليانصيب.
في العقد الذكي، نواصل التحليل message، اقرأ في action. تذكر أن لدينا اثنان action: تغيير العنوان وإرسال غرام.
ثم نقرأ العنوان الجديد لصاحب العقد ونحفظه في المخزن.
نجري الاختبارات ونرى أن الاختبار الثالث فشل. تحدث الأعطال نظرًا لأن العقد الآن يقوم أيضًا بتوزيع 7 بتات من الرسالة، وهي مفقودة في الاختبار. دعونا نضيف رسالة غير موجودة إلى الرسالة action. دعونا نجري الاختبارات ونرى أن كل شيء يمر. هنا الالتزام بالتغييرات. عظيم.
لنكتب الآن منطق إرسال العدد المحدد من الجرامات إلى العنوان المحفوظ مسبقًا.
دعونا نكتب اختبارا أولا. سنكتب اختبارين، أحدهما عندما لا يكون الرصيد كافيًا، والثاني عندما يجب أن يمر كل شيء بنجاح. يمكن الاطلاع على الاختبارات في هذا الالتزام.
الآن دعونا نضيف بعض التعليمات البرمجية. أولاً، دعونا نكتب طريقتين مساعدتين. طريقة الحصول الأولى هي معرفة الرصيد الحالي للعقد الذكي.
int balance() inline_ref method_id {
return get_balance().pair_first();
}
والثاني هو إرسال الجرام إلى عقد ذكي آخر. لقد قمت بنسخ هذه الطريقة بالكامل من عقد ذكي آخر.
() send_grams(int wc, int addr, int grams) impure {
;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
cell msg = begin_cell()
;; .store_uint(0, 1) ;; 0 <= format indicator int_msg_info$0
;; .store_uint(1, 1) ;; 1 <= ihr disabled
;; .store_uint(1, 1) ;; 1 <= bounce = true
;; .store_uint(0, 1) ;; 0 <= bounced = false
;; .store_uint(4, 5) ;; 00100 <= address flags, anycast = false, 8-bit workchain
.store_uint (196, 9)
.store_int(wc, 8)
.store_uint(addr, 256)
.store_grams(grams)
.store_uint(0, 107) ;; 106 zeroes + 0 as an indicator that there is no cell with the data.
.end_cell();
send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value
}
دعونا نضيف هاتين الطريقتين إلى العقد الذكي ونكتب المنطق. أولا، نقوم بتحليل عدد الجرامات من الرسالة. ثم نتحقق من الرصيد، إذا لم يكن كافيًا، فإننا نطرح استثناءً. إذا كان كل شيء على ما يرام، فإننا نرسل جرامًا إلى العنوان المحفوظ ونقوم بتحديث العداد.
مثل يبدو وكأنه عقد ذكي في الوقت الراهن. دعونا نجري الاختبارات ونتأكد من اجتيازها.
بالمناسبة، يتم خصم عمولة مقابل الرسالة المعالجة من عقد ذكي في كل مرة. لكي تتمكن رسائل العقد الذكي من تنفيذ الطلب، بعد إجراء الفحوصات الأساسية، يجب عليك الاتصال accept_message().
الآن دعونا نتعامل مع الرسائل الداخلية. في الواقع، لن نقبل سوى الجرامات ونرسل مبلغًا مضاعفًا للاعب في حالة فوزه وثلثًا للمالك في حالة خسارته.
دعونا نكتب اختبارًا بسيطًا أولاً. للقيام بذلك، نحتاج إلى عنوان اختباري للعقد الذكي الذي نرسل منه الجرام إلى العقد الذكي.
يتكون عنوان العقد الذكي من رقمين، عدد صحيح 32 بت مسؤول عن سلسلة العمل ورقم حساب فريد عدد صحيح غير سالب 256 بت في سلسلة العمل هذه. على سبيل المثال، -1 و12345، سيتم حفظ هذا العنوان في ملف.
وأخيرًا، تتم كتابة البايتات إلى الملف B>file. بعد ذلك، مكدسنا فارغ. وقف Fift. تم إنشاء الملف في المجلد الحالي sender.addr. انقل الملف إلى المجلد الذي تم إنشاؤه test/addresses/.
لنكتب اختبارًا بسيطًا يرسل الجرام إلى عقد ذكي. هنا الالتزام.
الآن دعونا نتعامل مع منطق اليانصيب.
أول شيء نفعله هو التحقق من الرسالة bounced أم لا إذا bounced، ثم نتجاهله. bounced يعني أن العقد سيعيد الجرام في حالة حدوث خطأ ما. لن نقوم بإرجاع الجرامات في حالة حدوث خطأ، فلن نقوم بذلك.
نتحقق مما إذا كان الرصيد أقل من نصف جرام، ثم نقبل الرسالة ببساطة ونتجاهلها.
بعد ذلك، نقوم بتحليل عنوان العقد الذكي الذي جاءت منه الرسالة.
نقرأ البيانات من المخزن ثم نحذف الرهانات القديمة من السجل إذا كان هناك أكثر من عشرين منها. للراحة، كتبت ثلاث وظائف إضافية pack_order(), unpack_order(), remove_old_orders().
ثم ننظر إذا كان الرصيد لا يكفي للدفع، فإننا نعتبر أن هذا ليس رهانًا، ولكنه تجديد وحفظ التجديد في orders.
ثم أخيرًا جوهر العقد الذكي.
أولاً، إذا خسر اللاعب، فإننا نحفظه في سجل الرهانات وإذا كان المبلغ أكثر من 3 جرام، نرسل 1/3 إلى صاحب العقد الذكي.
إذا فاز اللاعب، فإننا نرسل مبلغًا مضاعفًا إلى عنوان اللاعب ثم نحفظ المعلومات المتعلقة بالرهان في السجل.
() recv_internal(int order_amount, cell in_msg_cell, slice in_msg) impure {
var cs = in_msg_cell.begin_parse();
int flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
if (flags & 1) { ;; ignore bounced
return ();
}
if (order_amount < 500000000) { ;; just receive grams without changing state
return ();
}
slice src_addr_slice = cs~load_msg_addr();
(int src_wc, int src_addr) = parse_std_addr(src_addr_slice);
(int stored_seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) = unpack_state();
orders = remove_old_orders(orders, order_seqno);
if (balance() < 2 * order_amount + 500000000) { ;; not enough grams to pay the bet back, so this is re-fill
builder order = pack_order(order_seqno, 1, now(), order_amount, src_wc, src_addr);
orders~udict_set_builder(32, order_seqno, order);
set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins, incoming_amount + order_amount, outgoing_amount, owner_wc, owner_account_id, orders));
return ();
}
if (rand(10) >= 4) {
builder order = pack_order(order_seqno, 3, now(), order_amount, src_wc, src_addr);
orders~udict_set_builder(32, order_seqno, order);
set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins, incoming_amount + order_amount, outgoing_amount, owner_wc, owner_account_id, orders));
if (order_amount > 3000000000) {
send_grams(owner_wc, owner_account_id, order_amount / 3);
}
return ();
}
send_grams(src_wc, src_addr, 2 * order_amount);
builder order = pack_order(order_seqno, 2, now(), order_amount, src_wc, src_addr);
orders~udict_set_builder(32, order_seqno, order);
set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins + 1, incoming_amount, outgoing_amount + 2 * order_amount, owner_wc, owner_account_id, orders));
}
لقد نسيت أيضًا إضافة الكود الذي سيعالج الطلب الأول الذي يحدث عند نشر العقد الذكي. الالتزام ذو الصلة. وأكثر من ذلك تصحيح خطأ في إرسال ثلث المبلغ إلى حساب المالك.
والخطوة التالية هي نشر العقد الذكي. لنقم بإنشاء مجلد requests.
ما يستحق الاهتمام به. نقوم بتشكيل تخزين العقد الذكي ورسالة الدخول. بعد ذلك، يتم إنشاء عنوان العقد الذكي، أي أن العنوان معروف حتى قبل النشر في TON. ثم تحتاج إلى إرسال بضعة جرامات إلى هذا العنوان، وبعد ذلك فقط تحتاج إلى إرسال ملف بالعقد الذكي نفسه، حيث تأخذ الشبكة عمولة لتخزين العقد الذكي والعمليات فيه (المدققون الذين يقومون بتخزين وتنفيذ العقد الذكي انكماش). يمكن الاطلاع على الكود هنا.
بعد ذلك، نقوم بتنفيذ رمز النشر والحصول على lottery-query.boc الملف وعنوان العقد الذكي.
نرسل إلى العنوان 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 جرام وبعد ثواني قليلة نقوم بتنفيذ نفس الأمر. لإرسال غرام أستخدمه المحفظة الرسمية، ويمكنك طلب اختبار الجرام من أحد الأشخاص في الدردشة، وهو ما سأتحدث عنه في نهاية المقال.
وبشكل أدق، سنترك الأول لتغيير العنوان كعمل مستقل، والثاني لإرسال الجرام إلى عنوان المالك. في الواقع، سنحتاج إلى القيام بنفس الشيء كما في اختبار إرسال الجرامات.
هذه هي الرسالة التي سنرسلها إلى العقد الذكي، حيث msg_seqno 165 action 2 و 9.5 جرام للإرسال.
<b 165 32 u, 2 7 u, 9500000000 Gram, b>
لا تنسَ توقيع الرسالة باستخدام مفتاحك الخاص lottery.pk، والتي تم إنشاؤها مسبقًا عند إنشاء عقد ذكي. هنا هو الالتزام ذات الصلة.
الحصول على المعلومات من عقد ذكي باستخدام أساليب الحصول
الآن دعونا نلقي نظرة على كيفية تشغيل طرق الحصول على العقد الذكي.
نطلق lite-client وقم بتشغيل طرق الحصول التي كتبناها.
سوف نستخدم lite-client ونحصل على طرق لعرض معلومات حول العقد الذكي على الموقع.
عرض بيانات العقد الذكي على الموقع
لقد قمت بكتابة موقع ويب بسيط بلغة Python لعرض بيانات العقود الذكية بطريقة مناسبة. هنا لن أتطرق إليه بالتفصيل وأنشر الموقع في التزام واحد.
يتم تقديم الطلبات إلى TON من Python من خلال lite-client. للراحة، تم تجميع الموقع في Docker ونشره على Google Cloud. نهاية لهذه الغاية.
محاولة
الآن دعونا نحاول إرسال الجرام إلى هناك للتجديد منه محفظة. سوف نرسل 40 جراما. ودعنا نراهن على الوضوح. نرى أن الموقع يعرض تاريخ الرهانات ونسبة الفوز الحالية وغيرها من المعلومات المفيدة.
تبين أن المقالة أطول بكثير مما توقعت، وربما كان من الممكن أن تكون أقصر، أو ربما فقط لشخص لا يعرف شيئًا عن TON ويريد كتابة ونشر عقد ذكي ليس من الأسهل التفاعل معه. ربما يمكن تفسير بعض الأمور بشكل أكثر بساطة.
ربما كان من الممكن تنفيذ بعض النقاط في التنفيذ بشكل أكثر كفاءة وأناقة، ولكن بعد ذلك كان إعداد المقال سيستغرق وقتًا أطول. من الممكن أيضًا أن أكون قد ارتكبت خطأً ما في مكان ما أو لم أفهم شيئًا ما، لذلك إذا كنت تفعل شيئًا جديًا، فأنت بحاجة إلى الاعتماد على الوثائق الرسمية أو المستودع الرسمي الذي يحتوي على رمز TON.
تجدر الإشارة إلى أنه نظرًا لأن TON نفسه لا يزال في مرحلة التطوير النشط، فقد تحدث تغييرات من شأنها كسر أي من الخطوات الواردة في هذه المقالة (وهو ما حدث أثناء كتابتي، وقد قمت بتصحيحه بالفعل)، ولكن النهج العام غير مرجح للتغيير.
لن أتحدث عن مستقبل TON. ربما ستصبح المنصة شيئًا كبيرًا وعلينا أن نأخذ الوقت الكافي لدراستها وتكوين مكانة مناسبة لمنتجاتنا الآن.
هناك أيضًا Libra من Facebook، والتي لديها جمهور محتمل أكبر من المستخدمين من TON. لا أعرف شيئًا تقريبًا عن Libra، نظرًا لأن منتدى الأنشطة به نشاط أكثر بكثير من مجتمع TON. على الرغم من أن المطورين ومجتمع TON يشبهون مترو الأنفاق، وهو أمر رائع أيضًا.
الدردشة حول TON في Telegram، مما ساعد كثيرًا في التعرف عليها في المرحلة الأولية. أعتقد أنه لن يكون من الخطأ أن أقول إن هناك كل من كتب شيئًا لـ TON. يمكنك أيضًا طلب اختبار الجرام هناك. https://t.me/tondev_ru