درباره نحوه نوشتن و انتشار قرارداد هوشمند در شبکه باز تلگرام (TON)

درباره نحوه نوشتن و انتشار قرارداد هوشمند در TON

این مقاله درباره چیست؟

در این مقاله در مورد اینکه چگونه در اولین (از دو) مسابقه بلاک چین تلگرام شرکت کردم، جایزه نگرفتم و تصمیم گرفتم تجربه خود را در یک مقاله ثبت کنم تا در فراموشی فرو نرود و شاید کمک کند صحبت خواهم کرد. کسی.

از آنجایی که نمی‌خواستم کد انتزاعی بنویسم، بلکه می‌خواستم کاری انجام دهم، برای مقاله یک قرارداد هوشمند برای یک قرعه‌کشی فوری و یک وب‌سایت نوشتم که داده‌های قرارداد هوشمند را مستقیماً از TON بدون استفاده از فضای ذخیره‌سازی میانی نشان می‌دهد.

این مقاله برای کسانی مفید خواهد بود که می خواهند اولین قرارداد هوشمند خود را در TON ببندند، اما نمی دانند از کجا شروع کنند.

با استفاده از قرعه کشی به عنوان مثال، از نصب محیط به انتشار یک قرارداد هوشمند، تعامل با آن و نوشتن یک وب سایت برای دریافت و انتشار داده ها می پردازم.

در مورد شرکت در مسابقه

اکتبر گذشته، تلگرام یک رقابت بلاک چین با زبان های جدید را اعلام کرد Fift и FunC. لازم بود از میان نوشتن هر یک از پنج قرارداد هوشمند پیشنهادی، یکی را انتخاب کنید. فکر می کردم خوب است که کار متفاوتی انجام دهم، زبانی یاد بگیرم و چیزی بسازم، حتی اگر در آینده مجبور نباشم چیز دیگری بنویسم. بعلاوه، موضوع دائماً بر لب است.

شایان ذکر است که من هیچ تجربه ای در توسعه قراردادهای هوشمند نداشتم.

من قصد داشتم تا آخرش شرکت کنم تا بتوانم و سپس یک مقاله مروری بنویسم، اما در اولین مقاله بلافاصله شکست خوردم. من یک کیف پول نوشت با چند امضا روشن FunC و به طور کلی کار می کرد. من آن را به عنوان مبنا قرار دادم قرارداد هوشمند روی Solidity.

در آن زمان فکر می‌کردم که این برای حداقل یک جایزه کافی است. در نتیجه از 40 شرکت کننده حدود 60 نفر برنده جایزه شدند و من در بین آنها نبودم. در کل ایرادی نداره ولی یه چیز اذیتم کرد. در زمان اعلام نتایج، بررسی آزمون قرارداد من انجام نشده بود، از شرکت کنندگان در چت پرسیدم که آیا فرد دیگری وجود دارد که آن را ندارد، وجود ندارد.

ظاهراً با توجه به پیام های من، دو روز بعد داوران نظری را منتشر کردند و من هنوز متوجه نشدم که آیا آنها به طور تصادفی قرارداد هوشمند من را در حین داوری از دست دادند یا صرفاً فکر کردند که آنقدر بد است که نیازی به نظر ندارد. من یک سوال در صفحه پرسیدم، اما پاسخی دریافت نکردم. اگرچه این راز نیست که چه کسی قضاوت کرده است، من نوشتن پیام های شخصی را غیر ضروری دانستم.

زمان زیادی برای درک صرف شد، بنابراین تصمیم به نوشتن مقاله شد. از آنجایی که هنوز اطلاعات زیادی وجود ندارد، مقاله به صرفه جویی در وقت برای همه علاقه مندان کمک می کند.

مفهوم قراردادهای هوشمند در TON

قبل از نوشتن هر چیزی، باید بفهمید که از کدام طرف باید به این موضوع نزدیک شوید. بنابراین، اکنون به شما می گویم که سیستم از چه بخش هایی تشکیل شده است. به طور دقیق تر، چه بخش هایی را برای نوشتن حداقل نوعی قرارداد کاری باید بدانید.

ما بر نوشتن یک قرارداد هوشمند و کار با آن تمرکز خواهیم کرد TON Virtual Machine (TVM), Fift и FunC، بنابراین مقاله بیشتر شبیه توضیح توسعه یک برنامه معمولی است. ما در اینجا به نحوه عملکرد خود پلتفرم نمی پردازیم.

به طور کلی در مورد نحوه کار TVM و زبان Fift اسناد رسمی خوبی وجود دارد. در حین شرکت در مسابقه و اکنون هنگام نوشتن قرارداد فعلی، اغلب به او مراجعه می کردم.

زبان اصلی که قراردادهای هوشمند به آن نوشته می شود این است FunC. در حال حاضر هیچ مدرکی در مورد آن وجود ندارد، بنابراین برای نوشتن چیزی باید نمونه هایی از قراردادهای هوشمند را از مخزن رسمی و پیاده سازی خود زبان در آنجا مطالعه کنید، به علاوه می توانید به نمونه هایی از قراردادهای هوشمند از دو مورد گذشته نگاه کنید. مسابقات لینک ها در انتهای مقاله.

فرض کنید قبلاً یک قرارداد هوشمند برای آن نوشته ایم FunC، پس از آن کد را در اسمبلر Fift کامپایل می کنیم.

قرارداد هوشمند تدوین شده هنوز منتشر می شود. برای این کار باید یک تابع در آن بنویسید Fift، که کد قرارداد هوشمند و برخی پارامترهای دیگر را به عنوان ورودی می گیرد و خروجی یک فایل با پسوند خواهد بود. .boc (به معنی کیسه سلول ها) و بسته به نحوه نوشتن آن، یک کلید خصوصی و آدرس که بر اساس کد قرارداد هوشمند تولید می شود. از قبل می توانید گرام را به آدرس قرارداد هوشمندی که هنوز منتشر نشده است ارسال کنید.

برای انتشار قرارداد هوشمند در TON دریافت کرد .boc فایل باید با استفاده از یک کلاینت لایت به بلاک چین ارسال شود (در ادامه در مورد آن بیشتر توضیح می دهیم). اما قبل از انتشار باید گرم ها را به آدرس تولید شده انتقال دهید در غیر این صورت قرارداد هوشمند منتشر نمی شود. پس از انتشار، می‌توانید با ارسال پیام‌هایی از خارج (مثلاً با استفاده از یک کلاینت لایت) یا از داخل (به عنوان مثال، یک قرارداد هوشمند به دیگری پیامی در داخل TON ارسال می‌کند) با قرارداد هوشمند تعامل کنید.

هنگامی که درک کنیم که چگونه کد منتشر می شود، کار آسان تر می شود. ما تقریباً می دانیم که می خواهیم چه بنویسیم و برنامه ما چگونه کار خواهد کرد. و در حین نوشتن، به دنبال نحوه اجرای این امر در قراردادهای هوشمند موجود می‌شویم یا کد پیاده‌سازی را بررسی می‌کنیم Fift и FunC در مخزن رسمی، یا در اسناد رسمی نگاه کنید.

خیلی وقت ها در چت تلگرامی که همه شرکت کنندگان مسابقه و کارمندان تلگرام جمع می شدند به دنبال کلمات کلیدی می گشتم و اتفاقاً در طول مسابقه همه در آنجا جمع می شدند و شروع به بحث Fift و FunC می کردند. لینک در انتهای مقاله

زمان حرکت از تئوری به عمل فرا رسیده است.

آماده سازی محیط برای کار با TON

من تمام آنچه در مقاله در MacOS توضیح داده خواهد شد انجام دادم و آن را در Ubuntu 18.04 LTS تمیز در Docker دوباره بررسی کردم.

اولین کاری که باید انجام دهید دانلود و نصب است lite-client که با آن می توانید درخواست ها را به TON ارسال کنید.

دستورالعمل‌های موجود در وب‌سایت رسمی، فرآیند نصب را با جزئیات کامل و واضح توضیح می‌دهد و برخی جزئیات را حذف می‌کند. در اینجا ما دستورالعمل ها را دنبال می کنیم و وابستگی های گم شده را در طول مسیر نصب می کنیم. من هر پروژه را خودم کامپایل نکردم و از مخزن رسمی اوبونتو (در 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 یک مجموعه است 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 را در آن کامپایل کرده ایم 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 نوشته شده است.

از آنجایی که می خواهیم قرارداد هوشمند را به صورت محلی اجرا و آزمایش کنیم، یک فایل ایجاد می کنیم 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

درست همین جا می توانید commit مربوطه را با نتایج فعلی مشاهده کنید.

توجه داشته باشید که کپی کردن مداوم کد کامپایل شده یک قرارداد هوشمند در یک فایل همراه با تست ناخوشایند است، بنابراین ما اسکریپتی می نویسیم که کد را به یک ثابت برای ما می نویسد و به سادگی کد کامپایل شده را با استفاده از تست های خود وصل می کنیم. "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 تو میتوانی بخوانی در مستندات پنج.

و برای اجرای تست دوباره تماس می گیریم.

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 توضیح داده شده است.

پیوند تعهد با اضافه کردن خمیر

بیایید تست را اجرا کنیم و مطمئن شویم که شکست خورده است. حالا بیایید منطق را برای تغییر آدرس صاحب قرعه کشی اضافه کنیم.

در قرارداد هوشمند ما به تجزیه ادامه می دهیم 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));
}

همین. تعهد مربوطه.

اکنون همه چیز ساده است، بیایید روش‌هایی را ایجاد کنیم تا بتوانیم اطلاعاتی درباره وضعیت قرارداد از دنیای خارج به دست آوریم (در واقع، داده‌ها را از ذخیره قرارداد هوشمند آنها بخوانید).

بیایید روش های دریافت را اضافه کنیم. در زیر در مورد نحوه دریافت اطلاعات قرارداد هوشمند خواهیم نوشت.

همچنین فراموش کردم کدی را اضافه کنم که اولین درخواستی را که هنگام انتشار یک قرارداد هوشمند رخ می دهد، پردازش می کند. تعهد مربوطه. و بیشتر تصحیح شده اشکال با ارسال 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-client را راه اندازی کنیم و اجرا کنیم.

> 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 شماره ۱۰۲۹، action 2 و 9.5 گرم برای ارسال.

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

فراموش نکنید که پیام را با کلید خصوصی خود امضا کنید lottery.pk، که قبل از ایجاد قرارداد هوشمند ایجاد شد. در اینجا commit مربوطه است.

دریافت اطلاعات از قرارداد هوشمند با استفاده از روش های دریافت

حال بیایید نحوه اجرای روش های دریافت قرارداد هوشمند را بررسی کنیم.

راه اندازی 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 صحبت نمی کنم. شاید این پلتفرم به چیزی بزرگ تبدیل شود و ما باید برای مطالعه آن وقت بگذاریم و اکنون جایگاهی را با محصولات خود پر کنیم.

همچنین Libra از فیس بوک وجود دارد که مخاطبان بالقوه ای از کاربران بزرگتر از TON دارد. من تقریباً هیچ چیز در مورد Libra نمی دانم، با قضاوت در انجمن، فعالیت بسیار بیشتری در آنجا نسبت به انجمن 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. مخزن پسوند برای کد ویژوال استودیو برای 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

اضافه کردن نظر