על איך לכתוב ולפרסם חוזה חכם ברשת הפתוחה של טלגרם (TON)

על איך לכתוב ולפרסם חוזה חכם ב-TON

על מה המאמר הזה?

בכתבה אדבר על איך השתתפתי בתחרות הבלוקצ'יין הראשונה (מתוך שתיים) של טלגרם, לא לקחתי פרס והחלטתי לרשום את החוויה שלי בכתבה כדי שהיא לא תשקע בשכחה ואולי תעזור מִישֶׁהוּ.

מכיוון שלא רציתי לכתוב קוד אבסטרקטי, אלא לעשות משהו עובד, עבור המאמר כתבתי חוזה חכם להגרלה מיידית ואתר שמציג נתוני חוזים חכמים ישירות מ-TON מבלי להשתמש באחסון ביניים.

המאמר יהיה שימושי למי שרוצה לעשות את החוזה החכם הראשון שלהם ב-TON, אבל לא יודע מאיפה להתחיל.

בעזרת ההגרלה כדוגמה, אעבור מהתקנת הסביבה לפרסום חוזה חכם, אינטראקציה עמו וכתיבת אתר לקבלה ופרסום נתונים.

לגבי השתתפות בתחרות

באוקטובר האחרון הכריזה טלגרם על תחרות בלוקצ'יין עם שפות חדשות Fift и FunC. היה צורך לבחור מתוך כתיבת כל אחד מחמשת החוזים החכמים המוצעים. חשבתי שזה יהיה נחמד לעשות משהו אחר, ללמוד שפה וליצור משהו, גם אם לא אצטרך לכתוב שום דבר אחר בעתיד. בנוסף, הנושא נמצא כל הזמן על השפתיים.

כדאי לומר שלא היה לי ניסיון בפיתוח חוזים חכמים.

תכננתי להשתתף עד הסוף עד שאוכל ואז לכתוב מאמר ביקורת, אבל נכשלתי מיד בהתחלה. אני כתב ארנק עם ריבוי חתימות על FunC ובדרך כלל זה עבד. לקחתי את זה כבסיס חוזה חכם על Solidity.

באותו זמן, חשבתי שזה בהחלט מספיק כדי לקחת לפחות מקום בפרס. כתוצאה מכך, כ-40 מתוך 60 משתתפים הפכו לזוכים בפרסים ואני לא הייתי ביניהם. באופן כללי, אין בזה שום דבר רע, אבל דבר אחד הפריע לי. בזמן פרסום התוצאות לא נעשתה סקירת הבדיקה לחוזה שלי, שאלתי את המשתתפים בצ'אט אם יש עוד מישהו שלא היה לו, לא היו.

כנראה ששמים לב להודעות שלי, יומיים אחר כך השופטים פרסמו תגובה ואני עדיין לא מבין אם הם פספסו בטעות את החוזה החכם שלי במהלך השיפוט או פשוט חשבו שזה כל כך גרוע שלא צריך הערה. שאלתי שאלה בעמוד, אך לא קיבלתי תשובה. למרות שזה לא סוד מי שפט, חשבתי שזה מיותר לכתוב הודעות אישיות.

זמן רב הושקע בהבנה, ולכן הוחלט לכתוב מאמר. מכיוון שעדיין אין הרבה מידע, מאמר זה יעזור לחסוך זמן לכל המעוניינים.

הקונספט של חוזים חכמים ב-TON

לפני שאתה כותב משהו, אתה צריך להבין מאיזה צד לגשת לדבר הזה. לכן, עכשיו אני אגיד לך מאילו חלקים המערכת מורכבת. ליתר דיוק, אילו חלקים אתה צריך לדעת כדי לכתוב לפחות חוזה עבודה כלשהו.

נתמקד בכתיבת חוזה חכם ובעבודה עם TON Virtual Machine (TVM), Fift и FunC, כך שהמאמר דומה יותר לתיאור של פיתוח תוכנית רגילה. לא נתעכב על איך הפלטפורמה עצמה עובדת כאן.

באופן כללי על איך זה עובד TVM ושפה Fift יש תיעוד רשמי טוב. תוך כדי ההשתתפות בתחרות ועכשיו תוך כדי כתיבת החוזה הנוכחי פניתי אליה לא פעם.

השפה העיקרית בה נכתבים חוזים חכמים היא FunC. אין על זה תיעוד כרגע, אז כדי לכתוב משהו צריך ללמוד דוגמאות של חוזים חכמים מהמאגר הרשמי ויישום השפה עצמה שם, בנוסף אפשר להסתכל על דוגמאות של חוזים חכמים מהשניים האחרונים תחרויות. קישורים בסוף המאמר.

נניח שכבר כתבנו חוזה חכם עבור FunC, לאחר מכן אנו מרכיבים את הקוד לתוך Fift assembler.

החוזה החכם המלוקט נותר לפרסום. כדי לעשות זאת אתה צריך לכתוב פונקציה ב Fift, שייקח את קוד החוזה החכם וכמה פרמטרים נוספים כקלט, והפלט יהיה קובץ עם הסיומת .boc (שפירושו "שקית תאים"), ובהתאם לאופן שבו נכתוב את זה, מפתח וכתובת פרטיים, שנוצרים על סמך קוד החוזה החכם. כבר אפשר לשלוח גרמים לכתובת של חוזה חכם שטרם פורסם.

לפרסום חוזה חכם ב-TON קיבל .boc את הקובץ יהיה צורך לשלוח לבלוקצ'יין באמצעות לקוח קל (עוד על כך בהמשך). אבל לפני הפרסום צריך להעביר גרמים לכתובת שנוצרה, אחרת החוזה החכם לא יפורסם. לאחר הפרסום, ניתן ליצור אינטראקציה עם החוזה החכם על ידי שליחת הודעות מבחוץ (לדוגמה, באמצעות לקוח קל) או מבפנים (לדוגמה, חוזה חכם אחד שולח לאחר הודעה בתוך TON).

ברגע שהבנו איך הקוד מפורסם, זה נעשה קל יותר. אנחנו יודעים בערך מה אנחנו רוצים לכתוב ואיך התוכנית שלנו תעבוד. ובזמן הכתיבה, אנחנו מחפשים איך זה כבר מיושם בחוזים חכמים קיימים, או שאנחנו בוחנים את קוד ההטמעה Fift и FunC במאגר הרשמי, או עיין בתיעוד הרשמי.

לעתים קרובות מאוד חיפשתי מילות מפתח בצ'אט של טלגרם שבו התאספו כל משתתפי התחרות ועובדי טלגרם, וכך קרה שבמהלך התחרות כולם התאספו שם והתחילו לדון ב-Fift וב-FunC. קישור בסוף המאמר.

הגיע הזמן לעבור מתיאוריה לפרקטיקה.

הכנת הסביבה לעבודה עם TON

עשיתי כל מה שיתואר במאמר על MacOS ובדקתי זאת שוב ושוב במערכת נקייה. Ubuntu 18.04 LTS על דוקר.

הדבר הראשון שאתה צריך לעשות הוא להוריד ולהתקין lite-client איתם ניתן לשלוח בקשות ל-TON.

ההוראות באתר הרשמי מתארות את תהליך ההתקנה בצורה די יסודית וברורה, תוך השמטת פרטים מסוימים. כאן אנו עוקבים אחר ההוראות, ומתקינים את כל התלויות החסרות לאורך הדרך. לא ביצעתי קומפילציה של כל פרויקט בעצמי והתקנתי מהמאגר הרשמי. Ubuntu (ב-MacOS השתמשתי brew).

apt -y install git 
apt -y install wget 
apt -y install cmake 
apt -y install g++ 
apt -y install zlib1g-dev 
apt -y install libssl-dev 

לאחר התקנת כל התלות תוכל להתקין lite-client, Fift, FunC.

ראשית, אנו משכפלים את מאגר ה-TON יחד עם התלות שלו. מטעמי נוחות, נעשה הכל בתיקייה ~/TON.

cd ~/TON
git clone https://github.com/ton-blockchain/ton.git
cd ./ton
git submodule update --init --recursive

המאגר מאחסן גם יישומים Fift и FunC.

כעת אנו מוכנים להרכיב את הפרויקט. קוד המאגר משובט לתוך תיקיה ~/TON/ton. בתוך ~/TON ליצור תיקיה build ולאסוף בו את הפרויקט.

mkdir ~/TON/build 
cd ~/TON/build
cmake ../ton

מכיוון שאנחנו הולכים לכתוב חוזה חכם, אנחנו צריכים לא רק lite-clientאבל Fift с FunC, אז בואו נעשה הידור הכל. זה לא תהליך מהיר, אז אנחנו מחכים.

cmake --build . --target lite-client
cmake --build . --target fift
cmake --build . --target func

לאחר מכן, הורד את קובץ התצורה המכיל נתונים על הצומת שאליו lite-client יתחבר.

wget https://test.ton.org/ton-lite-client-test1.config.json

ביצוע הבקשות הראשונות ל-TON

עכשיו בואו נתחיל lite-client.

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, למשל, כאשר אנו בעצמנו יוצרים הודעה ושולחים אותה דרך ה-lite-client. שְׁנִיָה, recv_internal() זה כאשר, בתוך TON עצמה, כל חוזה מתייחס לחוזה שלנו. בשני המקרים, ניתן להעביר פרמטרים לפונקציה.

נתחיל בדוגמה פשוטה שתעבוד אם תפורסם, אבל אין בה עומס פונקציונלי.

() recv_internal(slice in_msg) impure {
    ;; TODO: implementation 
}

() recv_external(slice in_msg) impure {
    ;; TODO: implementation  
}

כאן אנחנו צריכים להסביר מה זה slice. כל הנתונים המאוחסנים ב-TON Blockchain הם אוסף TVM cell או פשוט cell, בתא כזה ניתן לאחסן עד 1023 סיביות של נתונים ועד 4 קישורים לתאים אחרים.

TVM cell slice או slice זה חלק מהקיים cell משמש כדי לנתח אותו, זה יתברר מאוחר יותר. העיקר מבחינתנו הוא שנוכל להעביר slice ובהתאם לסוג ההודעה, עבד את הנתונים פנימה recv_external() או recv_internal().

impure — מילת מפתח שמציינת שהפונקציה משנה נתוני חוזה חכם.

בואו נשמור את קוד החוזה lottery-code.fc והידור.

~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc 

ניתן לראות את משמעות הדגלים באמצעות הפקודה

~/TON/build/crypto/func -help

ריכזנו את קוד ה-Fift assembler ב 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 עבור ה-Fift assembler.

מכיוון שאנו רוצים להפעיל ולבדוק את החוזה החכם באופן מקומי, ניצור קובץ 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.

0 tuple 0x076ef1ea , // magic
0 , 0 , // actions msg_sents
1570998536 , // unix_time
1 , 1 , 3 , // block_lt, trans_lt, rand_seed
0 tuple 100000000000000 , dictnew , , // remaining balance
0 , dictnew , // contract_address, global_config
1 tuple // wrap to another tuple
constant c7

0 constant recv_internal // to run recv_internal() 
-1 constant recv_external // to invoke recv_external()

В c7 אנו מתעדים את ההקשר, כלומר, הנתונים שאיתם יופעל ה-TVM (או מצב הרשת). עוד במהלך התחרות, אחד המפתחים הראה כיצד ליצור c7 והעתקתי. במאמר זה ייתכן שנצטרך לשנות rand_seed מאחר ויצירת מספר אקראי תלוי בו ואם לא ישתנה, אותו מספר יוחזר בכל פעם.

recv_internal и recv_external קבועים עם ערכים 0 ו-1 יהיו אחראים לקריאה לפונקציות המתאימות בחוזה החכם.

כעת אנו מוכנים ליצור את המבחן הראשון לחוזה החכם הריק שלנו. למען הבהירות, לעת עתה נוסיף את כל הבדיקות לאותו קובץ lottery-test-suite.fif.

בואו ניצור משתנה storage וכתוב ריק לתוכו cell, זה יהיה אחסון החוזה החכם.

message זה המסר שנעביר לאיש הקשר החכם מבחוץ. אנחנו גם נהפוך אותו לריק לעת עתה.

variable storage 
<b b> storage ! 

variable message 
<b b> message ! 

לאחר שהכנו את הקבועים והמשתנים, אנו מפעילים את TVM באמצעות הפקודה runvmctx ולהעביר את הפרמטרים שנוצרו לקלט.

message @ 
recv_external 
code 
storage @ 
c7 
runvmctx 

בסוף נצליח ככה קוד ביניים עבור Fift.

כעת נוכל להריץ את הקוד שנוצר.

export FIFTPATH=~/TON/ton/crypto/fift/lib // выполняем один раз для удобства 
~/TON/build/crypto/fift -s lottery-test-suite.fif 

התוכנית אמורה לפעול ללא שגיאות ובפלט נראה את יומן הביצוע:

execute SETCP 0
execute DICTPUSHCONST 19 (xC_,1)
execute DICTIGETJMPZ
execute DROP
execute implicit RET
[ 3][t 0][1582281699.325381279][vm.cpp:479]     steps: 5 gas: used=304, max=9223372036854775807, limit=9223372036854775807, credit=0

נהדר, כתבנו את גרסת העבודה הראשונה של החוזה החכם.

עכשיו אנחנו צריכים להוסיף פונקציונליות. ראשית בואו נעסוק במסרים שמגיעים מהעולם החיצון אל 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 סיביות מאחסון החוזה החכם. אנו בודקים שהמספר שנטען תואם למספר שעבר; אם לא, אנו זורקים חריג. במקרה שלנו, מכיוון שאנו עוברים אי-התאמה, יש לזרוק חריג.

עכשיו בואו נעשה קומפילציה.

~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc 

העתק את הקוד שהתקבל ל lottery-test-suite.fif, לא לשכוח להחליף את השורה האחרונה.

אנו בודקים שהמבחן עובר:

~/TON/build/crypto/fift -s lottery-test-suite.fif

ממש כאן אתה יכול לראות את ההתחייבות המקבילה עם התוצאות הנוכחיות.

שימו לב שזה לא נוח להעתיק כל הזמן את הקוד הקומפילי של חוזה חכם לקובץ עם בדיקות, לכן נכתוב סקריפט שיכתוב לנו את הקוד לקבוע, ופשוט נחבר את הקוד הקומפיל לבדיקות שלנו באמצעות "include".

צור קובץ בתיקיית הפרויקט build.sh עם התוכן הבא.

#!/bin/bash

~/TON/build/crypto/func -SPA -R -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc

בואו נעשה את זה בר הפעלה.

chmod +x ./build.sh

עכשיו, פשוט הפעל את הסקריפט שלנו כדי להרכיב את החוזה. אבל חוץ מזה, אנחנו צריכים לכתוב את זה לתוך קבוע code. אז ניצור קובץ חדש lotter-compiled-for-test.fif, אותו נכלול בקובץ lottery-test-suite.fif.

בואו נוסיף קוד skirpt ל-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, ולאחר מכן הרץ את הבדיקות.

touch test.sh
chmod +x test.sh

אנחנו כותבים בפנים

./build.sh 

echo "nCompilation completedn"

export FIFTPATH=~/TON/ton/crypto/fift/lib
~/TON/build/crypto/fift -s lottery-test-suite.fif

בא נעשה 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() באחסון החוזה החכם.

cell packed_state = pack_state(arg_1, .., arg_n); 
set_data(packed_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.

variable owner_private_key
variable owner_public_key 

"./keys/owner.pk" file>B owner_private_key !
owner_private_key @ priv>pub owner_public_key !

נצטרך את שני המפתחות.

אנו מאתחלים את אחסון החוזה החכם עם נתונים שרירותיים באותו רצף כמו בפונקציה pack_state()ולכתוב את זה לתוך משתנה storage.

variable owner_private_key
variable owner_public_key 
variable orders
variable owner_wc
variable owner_account_id

"./keys/owner.pk" file>B owner_private_key !
owner_private_key @ priv>pub owner_public_key !
dictnew orders !
0 owner_wc !
0 owner_account_id !

<b 0 32 u, owner_public_key @ B, 0 32 u, 0 32 u, 0 Gram, 0 Gram, owner_wc @ 32 i, owner_account_id @ 256 u,  orders @ dict, b> storage !

לאחר מכן, נרכיב הודעה חתומה, היא תכיל רק את החתימה ואת ערך המונה.

ראשית, אנו יוצרים את הנתונים שברצוננו להעביר, לאחר מכן אנו חותמים אותם עם מפתח פרטי ולבסוף אנו יוצרים הודעה חתומה.

variable message_to_sign
variable message_to_send
variable signature
<b 0 32 u, b> message_to_sign !
message_to_sign @ hashu owner_private_key @ ed25519_sign_uint signature !
<b signature @ B, 0 32 u, b> <s  message_to_send !  

כתוצאה מכך, ההודעה שנשלח לחוזה החכם נרשמת במשתנה message_to_send, על פונקציות hashu, ed25519_sign_uint אתה יכול לקרוא בתיעוד של Fift.

וכדי להפעיל את המבחן אנחנו קוראים שוב.

message_to_send @ 
recv_external 
code 
storage @
c7
runvmctx

הנה כך הקובץ עם הבדיקות אמור להיראות כך בשלב זה.

בואו נריץ את הבדיקה והיא תיכשל, אז נשנה את החוזה החכם כך שיוכל לקבל הודעות בפורמט הזה ולאמת את החתימה.

ראשית, נספור 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. דה-סריאליזציה של משתנים מתוארת בתיעוד של 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
}

בואו נוסיף את שתי השיטות הללו לחוזה החכם ונכתוב את ההיגיון. ראשית, ננתח את מספר הגרמים מההודעה. לאחר מכן נבדוק את היתרה, אם זה לא מספיק אנחנו זורקים חריג. אם הכל בסדר, אז אנחנו שולחים את הגרמים לכתובת השמורה ומעדכנים את המונה.

int amount_to_send = message~load_grams();
throw_if(36, amount_to_send + 500000000 > balance());
accept_message();
send_grams(owner_wc, owner_account_id, amount_to_send);
set_data(pack_state(stored_seqno + 1, pubkey, order_seqno, number_of_wins, incoming_amount, outgoing_amount, owner_wc, owner_account_id, orders));

הנה כך נראה כמו החוזה החכם כרגע. בואו נריץ את המבחנים ונוודא שהם עוברים.

אגב, בכל פעם נגרעת עמלה מהחוזה החכם עבור הודעה מעובדת. כדי שהודעות החוזה החכם יבצעו את הבקשה, לאחר בדיקות בסיסיות צריך להתקשר accept_message().

כעת נעבור להודעות פנימיות. למעשה, נקבל רק גרמים ונשלח סכום כפול לשחקן אם ינצח ושליש לבעלים אם יפסיד.

ראשית, בואו נכתוב מבחן פשוט. לשם כך, אנו זקוקים לכתובת בדיקה של החוזה החכם שממנו אנו כביכול שולחים גרמים לחוזה החכם.

כתובת החוזה החכם מורכבת משני מספרים, מספר שלם של 32 סיביות האחראי על שרשרת העבודה ומספר חשבון ייחודי של 256 סיביות לא שלילי בשרשרת עבודה זו. לדוגמה, -1 ו-12345, זו הכתובת שנשמור לקובץ.

העתקתי את הפונקציה לשמירת הכתובת TonUtil.fif.

// ( wc addr fname -- )  Save address to file in 36-byte format
{ -rot 256 u>B swap 32 i>B B+ swap B>file } : save-address

בואו נסתכל על איך הפונקציה עובדת, זה ייתן הבנה של איך Fift עובד. הפעל את Fift במצב אינטראקטיבי.

~/TON/build/crypto/fift -i 

ראשית אנו דוחפים -1, 12345 ואת שם הקובץ העתידי "sender.addr" על המחסנית:

-1 12345 "sender.addr" 

השלב הבא הוא ביצוע הפונקציה -rot, אשר מעביר את הערימה בצורה כזו שבחלק העליון של הערימה יש מספר חוזה חכם ייחודי:

"sender.addr" -1 12345

256 u>B ממירה מספר שלם לא שלילי של 256 סיביות לבייטים.

"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039

swap מחליף את שני האלמנטים העליונים של הערימה.

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -1

32 i>B ממירה מספר שלם של 32 סיביות לבייטים.

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFF

B+ מחבר שני רצפים של בתים.

 "sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF

שוב swap.

BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" 

ולבסוף הבייטים נכתבים לקובץ 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));
}

זה מכיל. התחייבות מקבילה.

עכשיו כל מה שנותר הוא פשוט, בואו ניצור שיטות קבל כדי שנוכל לקבל מידע על מצב החוזה מהעולם החיצון (למעשה, קרא את הנתונים מאחסון החוזה החכם שלהם).

בואו נוסיף שיטות get. נכתוב בהמשך כיצד לקבל מידע על חוזה חכם.

שכחתי גם להוסיף את הקוד שיעבד את הבקשה הראשונה שמתרחשת בעת פרסום חוזה חכם. התחייבות מקבילה. ועוד מְתוּקָן באג עם שליחת 1/3 מהסכום לחשבון הבעלים.

השלב הבא הוא פרסום החוזה החכם. בואו ניצור תיקיה requests.

לקחתי את קוד הפרסום כבסיס simple-wallet-code.fc אשר יכול למצוא במאגר הרשמי.

משהו ששווה לשים אליו לב. אנו מייצרים אחסון חוזים חכם והודעת קלט. לאחר מכן נוצרת כתובת החוזה החכם, כלומר הכתובת ידועה עוד לפני הפרסום ב-TON. לאחר מכן צריך לשלוח מספר גרמים לכתובת הזו, ורק לאחר מכן צריך לשלוח קובץ עם החוזה החכם עצמו, שכן הרשת לוקחת עמלה על שמירת החוזה החכם והפעולות בו (מאחסנים ומבצעים חוזים חכמים ). את הקוד ניתן לראות כאן.

לאחר מכן אנו מבצעים את קוד הפרסום ומקבלים lottery-query.boc קובץ וכתובת חוזה חכם.

~/TON/build/crypto/fift -s requests/new-lottery.fif 0

אל תשכח לשמור את הקבצים שנוצרו: lottery-query.boc, lottery.addr, lottery.pk.

בין היתר נראה את כתובת החוזה החכם ביומני הביצוע.

new wallet address = 0:044910149dbeaf8eadbb2b28722e7d6a2dc6e264ec2f1d9bebd6fb209079bc2a 
(Saving address to file lottery.addr)
Non-bounceable address (for init): 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd
Bounceable address (for later access): kQAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KpFY

סתם בשביל הכיף, בואו נגיש בקשה ל-TON

$ ./lite-client/lite-client -C ton-lite-client-test1.config.json 
getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

ונראה שהחשבון עם הכתובת הזו ריק.

account state is empty

אנחנו שולחים לכתובת 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 גרם ולאחר מספר שניות אנו מבצעים את אותה פקודה. כדי לשלוח גרמים אני משתמש ארנק רשמי, ואתה יכול לבקש ממישהו מהצ'אט גרם מבחן, עליהם אדבר בסוף המאמר.

> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

נראה כמו לא אתחול (state:account_uninit) חוזה חכם עם אותה כתובת ויתרה של 1 ננוגרם.

account state is (account
  addr:(addr_std
    anycast:nothing workchain_id:0 address:x044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A)
  storage_stat:(storage_info
    used:(storage_used
      cells:(var_uint len:1 value:1)
      bits:(var_uint len:1 value:103)
      public_cells:(var_uint len:0 value:0)) last_paid:1583257959
    due_payment:nothing)
  storage:(account_storage last_trans_lt:3825478000002
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:4 value:2000000000))
      other:(extra_currencies
        dict:hme_empty))
    state:account_uninit))
x{C00044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A20259C2F2F4CB3800000DEAC10776091DCD650004_}
last transaction lt = 3825478000001 hash = B043616AE016682699477FFF01E6E903878CDFD6846042BA1BFC64775E7AC6C4
account balance is 2000000000ng

עכשיו בואו נפרסם את החוזה החכם. בואו נפעיל לקוח Lite ונבצע.

> sendfile lottery-query.boc
[ 1][t 2][1583008371.631410122][lite-client.cpp:966][!testnode] sending query from file lottery-query.boc
[ 3][t 1][1583008371.828550100][lite-client.cpp:976][!query]    external message status is 1 

בואו נבדוק שהחוזה פורסם.

> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

בין השאר אנחנו מקבלים.

  storage:(account_storage last_trans_lt:3825499000002
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:4 value:1987150999))
      other:(extra_currencies
        dict:hme_empty))
    state:(account_active

אנחנו רואים ש account_active.

התחייבות מקבילה עם שינויים כאן.

עכשיו בואו ניצור בקשות לאינטראקציה עם החוזה החכם.

ליתר דיוק, את הראשון נשאיר לשינוי הכתובת כיצירה עצמאית, ואת השני נשאיר לשליחת גרמים לכתובת הבעלים. למעשה, נצטרך לעשות את אותו הדבר כמו בבדיקה לשליחת גרמים.

זה המסר שנשלח לחוזה החכם, איפה msg_seqno 165, action 2 ו-9.5 גרם לשליחה.

<b 165 32 u, 2 7 u, 9500000000 Gram, b>

אל תשכח לחתום על ההודעה עם המפתח הפרטי שלך lottery.pk, אשר נוצר קודם לכן בעת ​​יצירת החוזה החכם. הנה ההתחייבות המתאימה.

קבלת מידע מחוזה חכם באמצעות שיטות get

עכשיו בואו נסתכל כיצד להפעיל שיטות קבל חוזה חכמות.

רוץ lite-client והפעל את שיטות get שכתבנו.

$ ./lite-client/lite-client -C ton-lite-client-test1.config.json
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd balance
arguments:  [ 104128 ] 
result:  [ 64633878952 ] 
...

В result מכיל את הערך שהפונקציה מחזירה balance() מהחוזה החכם שלנו.
אנו נעשה את אותו הדבר עבור מספר שיטות נוספות.

> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_seqno
...
arguments:  [ 77871 ] 
result:  [ 1 ] 

בוא נבקש את היסטוריית ההימורים שלך.

> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_orders
...
arguments:  [ 67442 ] 
result:  [ ([0 1 1583258284 10000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [1 3 1583258347 4000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [2 1 1583259901 50000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308]) ] 

נשתמש ב-lite-client ונקבל שיטות להצגת מידע על החוזה החכם באתר.

הצגת נתוני חוזים חכמים באתר

כתבתי אתר פשוט בפייתון כדי להציג את הנתונים מהחוזה החכם בצורה נוחה. כאן לא אתעכב על כך בהרחבה ואפרסם את האתר בהתחייבות אחת.

בקשות ל-TON נעשות מ Python באמצעות lite-client. מטעמי נוחות, האתר ארוז ב-Docker ומפורסם ב-Google Cloud. קישור.

מנסה

עכשיו בואו ננסה לשלוח גרמים לשם למילוי מ ארנק. נשלח 40 גרם. ובואו נעשה כמה הימורים למען הבהירות. אנו רואים שהאתר מציג את היסטוריית ההימורים, אחוז הזכייה הנוכחי ומידע שימושי נוסף.

אנחנו מביניםשניצחנו את הראשון, הפסדנו את השני.

אחרית דבר

הכתבה התבררה כהרבה יותר ארוכה ממה שציפיתי, אולי היא הייתה יכולה להיות קצרה יותר, או אולי רק לאדם שלא יודע כלום על TON ורוצה לכתוב ולפרסם חוזה חכם לא פשוט עם יכולת אינטראקציה. זה. אולי אפשר היה להסביר כמה דברים בצורה פשוטה יותר.

אולי חלק מההיבטים של היישום היו יכולים להיעשות בצורה יעילה ואלגנטית יותר, אבל אז היה לוקח עוד יותר זמן להכין את המאמר. ייתכן גם שטעיתי איפשהו או לא הבנתי משהו, אז אם אתה עושה משהו רציני, אתה צריך להסתמך על התיעוד הרשמי או המאגר הרשמי עם קוד ה-TON.

יש לציין שמכיוון ש-TON עצמה עדיין בשלב הפיתוח הפעיל, עלולים להתרחש שינויים שישברו כל אחד מהשלבים במאמר זה (שקרה בזמן שכתבתי, זה כבר תוקן), אבל הגישה הכללית היא לא סביר שישתנה.

אני לא אדבר על העתיד של TON. אולי הפלטפורמה תהפוך למשהו גדול וכדאי לנו להשקיע זמן בלימוד שלה ולמלא נישה במוצרים שלנו עכשיו.

יש גם מאזניים מפייסבוק, שיש לה קהל משתמשים פוטנציאלי גדול מ-TON. אני לא יודע כמעט כלום על מאזניים, אם לשפוט לפי הפורום יש שם הרבה יותר פעילות מאשר בקהילת TON. למרות שהמפתחים והקהילה של TON דומים יותר למחתרת, וזה גם מגניב.

תזכור

  1. תיעוד רשמי של TON: https://test.ton.org
  2. מאגר TON הרשמי: https://github.com/ton-blockchain/ton
  3. ארנק רשמי לפלטפורמות שונות: https://wallet.ton.org
  4. מאגר חוזים חכם ממאמר זה: https://github.com/raiym/astonished
  5. קישור לאתר החוזה החכם: https://ton-lottery.appspot.com
  6. מאגר עבור ההרחבה עבור Visual Studio Code for FunC: https://github.com/raiym/func-visual-studio-plugin
  7. צ'אט על TON בטלגרם, מה שבאמת עזר להבין את זה בשלב הראשוני. אני חושב שזו לא תהיה טעות אם אגיד שכל מי שכתב משהו ל-TON נמצא שם. אפשר גם לבקש שם גרם מבחן. https://t.me/tondev_ru
  8. צ'אט נוסף על TON בו מצאתי מידע שימושי: https://t.me/TONgramDev
  9. שלב ראשון בתחרות: https://contest.com/blockchain
  10. שלב שני בתחרות: https://contest.com/blockchain-2

מקור: www.habr.com

קנה אירוח אמין לאתרים עם הגנת DDoS, שרתי VPS VDS 🔥 קנה אחסון אתרים אמין עם הגנת DDoS, שרתי VPS VDS | ProHoster