Аб тым як напісаць і апублікаваць смарт-кантракт у 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-асэмблер.

Скампіляваны смарткантракт застаецца апублікаваць. Для гэтага трэба напісаць функцыю на Fift, які на ўваход будзе прымаць код смарт-кантракту і яшчэ некаторыя параметры, а на выхадзе атрымаецца файл з пашырэннем .boc (што азначае "bag of cells"), і, у залежнасці ад таго як напішам, прыватны ключ і адрас, які генеруецца на аснове кода смарт-кантракта. На адрас смарт-кантракта, які яшчэ не апублікаваны, ужо можна адпраўляць грамы.

Каб апублікаваць смарт-кантракт у 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, убудова можна знайсці і ўсталяваць у пошуку 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-асэмблер код у 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

Вот тут можна паглядзець адпаведны коміт з бягучымі вынікамі.

Заўважым, што ўвесь час капіяваць скампіляваны код смарт-кантракту ў файл з тэстамі няёмка, таму напішам скрыпт, які будзе запісваць код у канстанту за нас, а мы проста падлучым скампіляваны код у нашы тэсты з дапамогай "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 скирпт код, які будзе проста дубляваць скампляваны файл у lotter-compiled-for-test.fif і мяняць у ім апошні радок.

# copy and change for test 
cp lottery-compiled.fif lottery-compiled-for-test.fif
sed '$d' lottery-compiled-for-test.fif > test.fif
rm lottery-compiled-for-test.fif
mv test.fif lottery-compiled-for-test.fif
echo -n "}END>s constant code" >> lottery-compiled-for-test.fif

Зараз каб праверыць, запусцім атрыманы скрыпт і ў нас згенеруецца файл lottery-compiled-for-test.fif, які мы ўключым у наш lottery-test-suite.fif

В lottery-test-suite.fif выдаляем код кантракт і дадаем радок "lottery-compiled-for-test.fif" include.

Запускаем тэсты, каб праверыць, што яны праходзяць.

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

Выдатна, зараз, каб аўтаматызаваць запуск тэстаў створым файл test.sh, які спачатку будзе выконваць build.sh, А потым запускаць тэсты.

touch test.sh
chmod +x test.sh

Унутр пішам

./build.sh 

echo "nCompilation completedn"

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

Зробім test.sh і запусцім, каб пераканацца ў працаздольнасці тэстаў.

chmod +x ./test.sh
./test.sh

Правяраем, што кантракт кампілюецца і тэсты выконваюцца.

Выдатна, зараз пры запуску test.sh адразу будзе адбывацца кампіляцыя і запуск тэстаў. Вось спасылка на коміт.

Окей, перш чым мы працягнем давайце для зручнасці зробім яшчэ адну рэч.

Створым тэчку build дзе будзем захоўваць скапіляваны кантракт і яго клон запісаны ў канстанту lottery-compiled.fif, lottery-compiled-for-test.fif. Таксама створым тэчку test дзе будуць захоўваецца файл з тэстамі lottery-test-suite.fif і патэнцыйна іншыя дапаможныя файлы. Спасылка на адпаведныя змены.

Працягнем распрацоўку смарт-кантракта.

Далей павінен быць тэст, які правярае, што паведамленне прымаецца і лічыльнік абнаўляецца ў сховішча, калі мы адпраўляем правільную колькасць. Але мы зробім гэта пазней.

Цяпер падумаем над тым, якая структура дадзеных і якія дадзеныя трэба захоўваць у смарт-кантракце.

Апішу ўсё што мы захоўваем.

`seqno` 32-х битное целое положительное число счетчик. 

`pubkey` 256-ти битное целое положительное число публичный ключ, с помощью которого, мы будем проверять подпись отправленного извне сообщения, о чем ниже. 

`order_seqno` 32-х битное целое положительное число хранит счетчик количества ставок. 

`number_of_wins` 32-х битное целое положительное число хранит  количество побед. 

`incoming_amount` тип данных Gram (первые 4 бита отвечает за длину), хранит общее количество грамов, которые были отправлены на контртакт. 

`outgoing_amount` общее количество грамов, которое было отправлено победителям. 

`owner_wc` номер воркчейна, 32-х битное (в некоторых местах написано, что 8-ми битное) целое число. В данный момент всего два -1 и 0. 

`owner_account_id` 256-ти битное целое положительное число, адрес контракта в текущем воркчейне. 

`orders` переменная типа словарь, хранит последние двадцать ставок. 

Далей трэба напісаць дзве функцыі. Першую назавем pack_state(), якая будзе пакаваць дадзеныя для наступнага захавання яго ў сховішча смарт-кантракту. Другую, назавем unpack_state() будзе счытваць і вяртаць дадзеныя са сховішча.

_ pack_state(int seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) inline_ref {
    return begin_cell()
            .store_uint(seqno, 32)
            .store_uint(pubkey, 256)
            .store_uint(order_seqno, 32)
            .store_uint(number_of_wins, 32)
            .store_grams(incoming_amount)
            .store_grams(outgoing_amount)
            .store_int(owner_wc, 32)
            .store_uint(owner_account_id, 256)
            .store_dict(orders)
            .end_cell();
}

_ unpack_state() inline_ref {
    var ds = begin_parse(get_data());
    var unpacked = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_uint(32), ds~load_grams(), ds~load_grams(), ds~load_int(32), ds~load_uint(256), ds~load_dict());
    ds.end_parse();
    return unpacked;
}

Дадаем гэтыя дзве функцыі спачатку смарт-кантракту. Атрымаецца вось такі прамежкавы вынік.

Каб захаваць дадзеныя трэба будзе выклікаць убудаваную фунцкію set_data() і яна запіша дадзеныя з pack_state() у сховішча смарт-кантракта.

cell packed_state = pack_state(arg_1, .., arg_n); 
set_data(packed_state);

Цяпер калі ў нас ёсць зручныя функцыі запісу і чытання дадзеных мы можам рухацца далей.

Нам трэба праверыць, што ўваходнае звонку паведамленне падпісана ўладальнікам кантракту (ну ці іншым карыстальнікам, які мае доступ да прыватнага ключа).

Калі мы публікуем смарт-кантракт мы можам ініцыялізаваць яго з патрэбнымі нам дадзенымі ў сховішча, якія захаваюцца для будучага выкарыстання. Мы запішам туды публічны ключ, каб можна было праверыць, што подпіс уваходнага паведамлення быў зроблены адпаведным прыватным ключом.

Перад тым як працягнуць створым прыватны ключ і запішам яго ў test/keys/owner.pk. Для гэтага запусцім Fift у інтэрактыўным рэжыме і выканаем чатыры каманды.

`newkeypair` генерация публичного и приватного ключа и запись их в стек. 

`drop` удаления из стека верхнего элемента (в данном случае публичный ключ)  

`.s` просто посмотреть что лежит в стеке в данный момент 

`"owner.pk" B>file` запись приватного ключа в файл с именем `owner.pk`. 

`bye` завершает работу с Fift. 

Створым тэчку keys ўнутры тэчкі test і туды запішам прыватны ключ.

mkdir test/keys
cd test/keys
~/TON/build/crypto/fift -i 
newkeypair
 ok
.s 
BYTES:128DB222CEB6CF5722021C3F21D4DF391CE6D5F70C874097E28D06FCE9FD6917 BYTES:DD0A81AAF5C07AAAA0C7772BB274E494E93BB0123AA1B29ECE7D42AE45184128 
drop 
 ok
"owner.pk" B>file
 ok
bye

Бачым у бягучай тэчцы файл owner.pk.

Мы выдаляем публічны ключ са стэка, калі спатрэбіцца можам атрымаць яго з прыватнага.

Цяпер нам трэба напісаць праверку подпісу. Пачнём з цеста. Спачатку мы счытваем прыватны ключ з файла з дапамогай функцыі file>B і запісваем яго ў зменную owner_private_key, далей з дапамогай функцыі priv>pub канвертуем прыватны ключ у публічны і запішам вынік у owner_public_key.

variable owner_private_key
variable owner_public_key 

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

Абодва ключы нам спатрэбяцца.

Ініцыялізуем адвольнымі дадзенымі сховішча смарт-кантракту ў той жа самай паслядоўнасці, як у функцыі pack_state()і запішам у зменную storage.

variable owner_private_key
variable owner_public_key 
variable orders
variable owner_wc
variable owner_account_id

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

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

Далей складзем падпісанае паведамленне, у ім будзе толькі подпіс і значэнне лічыльніка.

Спачатку ствараем дадзеныя, якія хочам перадаць, потым падпісваем іх прыватным ключом і нарэшце фармуем падпісанае паведамленне.

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

У выніку паведамленне якое мы адправім у смарт-кантракт запісана ў зменную message_to_send, пра функцыі hashu, ed25519_sign_uint можна пачытаць у дакументацыі па Fift.

І для запуску цеста зноў выклікаем.

message_to_send @ 
recv_external 
code 
storage @
c7
runvmctx

Вось так павінен выглядаць файл з тэстамі на дадзеным этапе.

Запусцім тэст і ён упадзе, таму зменім смарт-кантракт, каб ён змог атрымліваць паведамленні такога фармату і правяраць подпіс.

Спачатку лічым з паведамлення 512 біт подпісы і запішам у зменную, далей лічым 32 біта зменнай лічыльніка.

Так як у нас ёсць функцыя счытвання дадзеных са сховішча смарт-канракту будзем выкарыстоўваць яе.

Далей праверка лічыльніка перададзенага са сховішчам і праверка подпісу. Калі нешта не супадзе, то выкідваем выключэнне з адпаведным кодам.

var signature = in_msg~load_bits(512);
var message = in_msg;
int msg_seqno = message~load_uint(32);
(int stored_seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) = unpack_state();
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(in_msg), signature, pubkey));

Супаведны коміт вось тут.

Запусцім тэсты і ўбачым, што другі тэст падае. Па двух прычынах, недахоп бітаў у паведамленні і недахоп бітаў у сховішчы, таму код падае пры парсінгу. Трэба дадаць подпіс паведамлення, якое мы адпраўляем і скапіяваць сховішча з апошняга тэста.

У другім тэсце дадамо подпіс паведамлення і зменім сховішча смарт-кантракту. Вось так выглядае файл з тэстамі ў дадзены момант.

Напішам чацвёрты тэст, у якім будзем адпраўляць паведамленне падпісанае чужым прыватным ключом. Створым яшчэ адзін прыватны ключ і захаваем у файл not-owner.pk. Гэтым прыватным ключом падпішам паведамленне. Запусцім тэсты і пераканаемся, што ўсе тэсты праходзяць. Коміт на бягучы момант.

Цяпер нарэшце мы можам перайсці да рэалізацыі логікі смарткантракта.
В recv_external() мы будзем прымаць два тыпы паведамленняў.

Паколькі наш кантракт будзе акумуляваць пройгрышы гульцоў, гэтыя грошы трэба пералічваць стваральніку латарэі. Адрас кашалька стваральніка латарэі запісваецца ў сховішча пры стварэнні кантракта.

На ўсялякі выпадак нам патрэбна магчымасць мяняць адрас на які адпраўляць грамы прайграўшых. Таксама мы павінны мець магчымасць адпраўляць грамы з латарэі на адрас уладальніка.

Пачнём з першага. Напішам спачатку тэст, які будзе правяраць, што пасля адпраўкі паведамлення смарт-кантракт захаваў новы адрас у сховішча. Звернем увагу, што ў паведамленне акрамя лічыльніка і новага адраса мы перадаем яшчэ action 7-мі бітнае цэлае неадмоўнае лік, у залежнасці ад яго, мы будзем выбіраць як апрацоўваць паведамленне ў смарт-кантракце.

<b 0 32 u, 1 @ 7 u, new_owner_wc @  32 i, new_owner_account_id @ 256 u, b> message_to_sign !

У цесце можна ўбачыць як адбываецца дэсерэалізацыя сховішча смарткантратка storage у Fift. Дэсерыялізацыя зменных апісана ў дакументацыі па Fift.

Спасылка на коміт з даданнем цеста.

Запускаем тэст і пераканаемся, што ён падае. Цяпер дададзім логіку па змене адраса ўладальніка латарэі.

У смарт-кантракце мы працягваем парсіць message, счытваем у action. Нагадаем, што ў нас будзе два action: змена адрасу і адпраўка грамаў.

Потым счытваем новы адрас уладальніка кантракту і захоўваемы ў сховішчы.
Запускаем тэсты і бачым, што трэці тэст падае. Падае з-за таго, што кантракт зараз дадаткова парсіць 7 біт з паведамлення, якіх не хапае ў цесцю. Дадамо ў паведамленне неіснуючы action. Запусцім тэсты і бачым, што ўсё праходзяць. Тут коміт на змены. Выдатна.

Цяпер напішам логіку адпраўкі названай колькасці грамаў на захаваны раней адрас.

Спачатку напішам тэст. Мы напішам два тэст адзін калі балансу не хапае, другі калі ўсё павінна прайсці паспяхова. Тэсты можна паглядзець у гэтым коміце.

Цяпер дапішам код. Спачатку напішам два дапаможныя метады. Першы гет метад для таго каб даведацца пра бягучы баланс смарт-кантракту.

int balance() inline_ref method_id {
    return get_balance().pair_first();
}

І другі для адпраўкі грамаў на іншы смарт-кантракт. Гэты метад я цалкам скапіяваў з іншага смарт-кантракта.

() send_grams(int wc, int addr, int grams) impure {
    ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
    cell msg = begin_cell()
    ;;  .store_uint(0, 1) ;; 0 <= format indicator int_msg_info$0 
    ;;  .store_uint(1, 1) ;; 1 <= ihr disabled
    ;;  .store_uint(1, 1) ;; 1 <= bounce = true
    ;;  .store_uint(0, 1) ;; 0 <= bounced = false
    ;;  .store_uint(4, 5)  ;; 00100 <= address flags, anycast = false, 8-bit workchain
        .store_uint (196, 9)
        .store_int(wc, 8)
        .store_uint(addr, 256)
        .store_grams(grams)
        .store_uint(0, 107) ;; 106 zeroes +  0 as an indicator that there is no cell with the data.
        .end_cell(); 
    send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value
}

Дададзім гэтыя два метады ў смарт-кантракт і напішам логіку. Спачатку парсім колькасць грамаў з паведамлення. Далей правяраем баланс, калі не хапае выкідваем выключэнне. Калі ўсё добра, то адпраўляем грамы на захаваны адрас і абнаўляем лічыльнік.

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

Вось так выглядае смарт-кантракт на дадзены момант. Запусцім тэсты і пераканаемся, што яны праходзяць.

Дарэчы, за апрацаванае паведамленне ў смарт-кантракта кожны раз спісваецца камісія. Каб паведамленні смарт-кантракт вылупіў запыт, пасля базавых праверак трэба выклікаць accept_message().

Цяпер зоймемся ўнутранымі паведамленнямі. Па факце мы будзем толькі прымаць грамы і адсылаць зваротна гульцу падвойную суму пры выйгрышы і траціна ўладальніку пры пройгрышы.

Спачатку напішам просты тэст. Для гэтага нам спатрэбіцца тэставы адрас смарт-кантракта, з якога мы быццам бы адпраўляем грамы на смарт-кантракт.

Адрас смарткантракта складаецца з двух лікаў, 32-х бітнае цэлае лік адказвае за workchain і 256-ці цэлае неадмоўнае унікальны нумар акаўнта ў гэтым workchain. Напрыклад, -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 Gram і праз некалькі секунд выконваем тужэй каманду. Для адпраўкі грамаў я выкарыстоўваю афіцыйны кашалёк, А тэставыя грамы можна папрасіць у каго-небудзь з чата, аб якім я скажу ў канцы артыкула.

> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Глядзіць, што ў сеціве зьявіўся неініцыялізаваны (state:account_uninit) смарткантракт з такім адрасам і балансам 1 000 000 000 нанаграм.

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/XNUMX грам для адпраўкі.

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

Не забываем падпісаць паведамленне прыватным ключом lottery.pk, Які згенераваўся раней пры стварэнні смарт-кантракту. Вось адпаведны коміт.

Атрымліваем іншармацыю з смарт-кантракту з дапамогай гет-метадаў

Цяпер разгледзім як запускаць гет-метады смарткантракта.

Запускаем 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. Аб 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. Рэпазітар на пашырэнне для Visual Studio Code для FunC: https://github.com/raiym/func-visual-studio-plugin
  7. Чат пра ТON у Telegram, які вельмі дапамог разабрацца на пачатковым этапе. Я думаю не будзе памылкай, калі скажу, што там ёсць усе, хто пісаў нешта для TON. Тамсама можна папрасіць тэставых грамаў. https://t.me/tondev_ru
  8. Яшчэ адзін чат пра TON у якім я знаходзіў карысную інфармацыю: https://t.me/TONgramDev
  9. Першы этап конкурсу: https://contest.com/blockchain
  10. Другі этап конкурсу: https://contest.com/blockchain-2

Крыніца: habr.com

Дадаць каментар