Այն մասին, թե ինչպես գրել և հրապարակել խելացի պայմանագիր Telegram Open Network-ում (TON)

Այն մասին, թե ինչպես գրել և հրապարակել խելացի պայմանագիր TON-ում

Ինչի՞ մասին է այս հոդվածը։

Հոդվածում ես կխոսեմ այն ​​մասին, թե ինչպես ես մասնակցեցի Telegram-ի բլոկչեյնի առաջին (երկու) մրցույթին, մրցանակ չստացա և որոշեցի իմ փորձը գրանցել հոդվածում, որպեսզի այն մոռացության մեջ չընկնի և, գուցե, օգնի. ինչ-որ մեկին.

Քանի որ ես չէի ուզում գրել վերացական կոդ, այլ ինչ-որ բան աշխատել, հոդվածի համար ես գրեցի խելացի պայմանագիր ակնթարթային վիճակախաղի և կայքէջի համար, որը ցույց է տալիս խելացի պայմանագրի տվյալները անմիջապես TON-ից՝ առանց միջանկյալ պահեստավորման օգտագործման:

Հոդվածը օգտակար կլինի նրանց համար, ովքեր ցանկանում են իրենց առաջին խելացի պայմանագիրը կնքել TON-ում, բայց չգիտեն, թե որտեղից սկսել:

Որպես օրինակ օգտագործելով վիճակախաղը՝ ես միջավայրի տեղադրումից կանցնեմ մինչև խելացի պայմանագիր հրապարակելը, դրա հետ շփվելը և տվյալների ստացման և հրապարակման համար կայք գրելը:

Մրցույթին մասնակցելու մասին

Անցյալ հոկտեմբերին Telegram-ը հայտարարեց նոր լեզուներով բլոկչեյն մրցույթի մասին 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 պաշտոնական շտեմարանում կամ նայեք պաշտոնական փաստաթղթերում:

Շատ հաճախ ես որոնում էի հիմնաբառեր Telegram չաթում, որտեղ հավաքվում էին մրցույթի բոլոր մասնակիցները և Telegram-ի աշխատակիցները, և այնպես ստացվեց, որ մրցույթի ժամանակ բոլորը հավաքվեցին այնտեղ և սկսեցին քննարկել Fift-ը և FunC-ն։ Հղումը հոդվածի վերջում:

Ժամանակն է տեսությունից անցնել պրակտիկային:

Շրջակա միջավայրի նախապատրաստում TON-ի հետ աշխատելու համար

Ես արեցի այն ամենը, ինչ նկարագրված կլինի MacOS-ի հոդվածում և կրկնակի ստուգեցի այն մաքուր Ubuntu 18.04 LTS-ում Docker-ում:

Առաջին բանը, որ դուք պետք է անեք, ներբեռնեք և տեղադրեք 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-ի կոդը, plugin-ը կարելի է գտնել և տեղադրել 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.

Եկեք sh-ին ավելացնենք skipt code, որը պարզապես կկրկնօրինակի կազմված ֆայլը 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 հինգում: Փոփոխականների ապասերիալացումը նկարագրված է Հինգերորդ փաստաթղթերում:

Պարտավորել հղումը ավելացված խմորով։

Եկեք անցկացնենք թեստը և համոզվեք, որ այն ձախողվում է: Հիմա եկեք տրամաբանություն ավելացնենք վիճակախաղի տիրոջ հասցեն փոխելու համար։

Խելացի պայմանագրում մենք շարունակում ենք վերլուծել 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 165, action 2 և 9.5 գրամ ուղարկելու համար։

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

Մի մոռացեք ստորագրել հաղորդագրությունը ձեր անձնական բանալիով lottery.pk, որն ավելի վաղ ստեղծվել էր խելացի պայմանագիրը ստեղծելիս: Ահա համապատասխան պարտավորությունը.

Ստանալով տեղեկատվություն խելացի պայմանագրից՝ օգտագործելով get մեթոդները

Այժմ եկեք նայենք, թե ինչպես գործարկել խելացի պայմանագրերի ստացման մեթոդները:

Մենք մեկնարկում ենք lite-client և գործարկեք ստանալ մեթոդները, որոնք մենք գրել ենք:

$ ./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 և կստանանք մեթոդներ՝ խելացի պայմանագրի մասին տեղեկատվությունը կայքում ցուցադրելու համար:

Կայքում խելացի պայմանագրի տվյալների ցուցադրում

Ես Python-ում գրեցի պարզ կայք՝ խելացի պայմանագրի տվյալները հարմար ձևով ցուցադրելու համար: Այստեղ ես դրա վրա մանրամասն չեմ անդրադառնա և կհրապարակեմ կայքը մեկ պարտավորության մեջ.

TON-ին ուղղված հարցումները կատարվում են Python միջոցով lite-client. Հարմարության համար կայքը փաթեթավորված է Docker-ում և հրապարակված Google Cloud-ում: Հղում.

Արի փորձենք

Հիմա եկեք փորձենք այնտեղ գրամներ ուղարկել համալրման համար դրամապանակ. 40 գրամ կուղարկենք։ Եվ պարզության համար եկեք մի քանի խաղադրույք կատարենք: Մենք տեսնում ենք, որ կայքը ցույց է տալիս խաղադրույքների պատմությունը, ընթացիկ շահումների տոկոսը և այլ օգտակար տեղեկություններ։

Մենք տեսնում ենքոր առաջինը հաղթեցինք, երկրորդը կորցրինք։

Հետո

Հոդվածը շատ ավելի երկար ստացվեց, քան ես սպասում էի, միգուցե այն կարող էր ավելի կարճ լինել, կամ գուցե պարզապես այն մարդու համար, ով ոչինչ չգիտի TON-ի մասին և ցանկանում է գրել և հրապարակել ոչ այնքան պարզ խելացի պայմանագիր՝ հետ շփվելու ունակությամբ: այն. Միգուցե որոշ բաներ կարելի էր ավելի պարզ բացատրել։

Միգուցե իրագործման որոշ ասպեկտներ կարող էին ավելի արդյունավետ և նրբագեղ արվել, բայց հետո հոդվածը պատրաստելու համար ավելի շատ ժամանակ կպահանջվեր: Հնարավոր է նաև, որ ես ինչ-որ տեղ սխալվել եմ կամ ինչ-որ բան չեմ հասկացել, այնպես որ, եթե լուրջ բան եք անում, ապա պետք է ապավինեք պաշտոնական փաստաթղթերին կամ TON կոդով պաշտոնական պահեստին:

Հարկ է նշել, որ քանի որ TON-ն ինքը դեռ զարգացման ակտիվ փուլում է, կարող են տեղի ունենալ փոփոխություններ, որոնք կխախտեն այս հոդվածի որևէ քայլ (ինչը տեղի է ունեցել ես գրում էի, այն արդեն ուղղվել է), բայց ընդհանուր մոտեցումը հետևյալն է. դժվար թե փոխվի:

Ես չեմ խոսի TON-ի ապագայի մասին: Միգուցե հարթակը դառնա ինչ-որ մեծ բան, և մենք պետք է ժամանակ ծախսենք դրա ուսումնասիրության վրա և այժմ մեր արտադրանքով տեղ լրացնենք:

Կա նաև Libra-ն Facebook-ից, որն ունի 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. FunC-ի համար Visual Studio Code-ի ընդլայնման պահեստ. https://github.com/raiym/func-visual-studio-plugin
  7. Զրուցեք TON-ի մասին Telegram-ում, որն իսկապես օգնեց պարզել այն սկզբնական փուլում: Կարծում եմ, որ սխալ չի լինի, եթե ասեմ, որ բոլոր նրանք, ովքեր ինչ-որ բան գրել են TON-ի համար, այնտեղ են: Այնտեղ կարող եք նաև թեստային գրամներ խնդրել: https://t.me/tondev_ru
  8. Մեկ այլ զրույց TON-ի մասին, որում ես գտա օգտակար տեղեկատվություն. https://t.me/TONgramDev
  9. Մրցույթի առաջին փուլ. https://contest.com/blockchain
  10. Մրցույթի երկրորդ փուլ. https://contest.com/blockchain-2

Source: www.habr.com

Добавить комментарий