За тоа како да напишете и објавите паметен договор во Telegram Open Network (TON)

За тоа како да напишете и објавите паметен договор во ТОН

За што е оваа статија?

Во написот ќе зборувам за тоа како учествував на првиот (од два) натпревар за блокчејн Telegram, не добив награда и решив да го забележам моето искуство во статија за да не потоне во заборав и, можеби, да помогне некој.

Бидејќи не сакав да пишувам апстрактен код, туку да направам нешто што функционира, за статијата напишав паметен договор за инстант лотарија и веб-локација што прикажува податоци за паметни договори директно од TON без користење на средно складирање.

Статијата ќе биде корисна за оние кои сакаат да го направат својот прв паметен договор во ТОН, но не знаат од каде да почнат.

Користејќи ја лотаријата како пример, ќе преминам од инсталирање на околината до објавување паметен договор, интеракција со него и пишување веб-локација за примање и објавување податоци.

За учеството на натпреварот

Минатиот октомври, Telegram објави натпревар за блокчејн со нови јазици Fift и FunC. Беше неопходно да се избере од пишување кој било од петте предложени паметни договори. Мислев дека би било убаво да направам нешто поинакво, да научам јазик и да направам нешто, дури и ако не морам ништо друго да пишувам во иднина. Плус, темата е постојано на усните.

Вреди да се каже дека немав искуство со развој на паметни договори.

Планирав да учествувам до самиот крај додека не можам и потоа да напишам рецензентска статија, но на првата не успеав веднаш. Јас напиша паричник со вклучен повеќе потпис FunC и генерално функционираше. Го земав како основа паметен договор на Solidity.

Тогаш мислев дека ова е дефинитивно доволно за да заземам барем некое наградно место. Како резултат на тоа, околу 40 од 60 учесници станаа наградени и јас не бев меѓу нив. Во принцип, нема ништо лошо во ова, но една работа ми пречеше. Во моментот на објавување на резултатите не беше направен преглед на тестот за мојот договор, ги прашав учесниците во чатот дали има уште некој што го нема, нема.

Очигледно внимавајќи на моите пораки, два дена подоцна судиите објавија коментар и сè уште не разбирам дали случајно го пропуштија мојот паметен договор за време на судењето или едноставно мислеа дека е толку лош што не треба коментар. Поставив прашање на страницата, но не добив одговор. Иако не е тајна кој судеше, сметав дека е непотребно да пишувам лични пораки.

Беше потрошено многу време за разбирање, па беше одлучено да се напише статија. Бидејќи сè уште нема многу информации, овој напис ќе помогне да заштедите време за сите заинтересирани.

Концептот на паметни договори во ТОН

Пред да напишете било што, треба да сфатите од која страна да пристапите на оваа работа. Затоа, сега ќе ви кажам од кои делови се состои системот. Поточно, кои делови треба да ги знаете за да напишете барем некаков договор за работа.

Ќе се фокусираме на пишување паметен договор и работа со него TON Virtual Machine (TVM), Fift и FunC, така што статијата е повеќе како опис на развојот на редовна програма. Ние нема да се задржиме на тоа како функционира самата платформа овде.

Општо за тоа како функционира TVM и јазикот Fift има добра официјална документација. Додека учествував на натпреварот и сега додека го пишував актуелниот договор, често се обраќав кон неа.

Главниот јазик на кој се пишуваат паметните договори е FunC. Во моментов нема документација за тоа, па за да напишете нешто треба да проучите примери на паметни договори од официјалното складиште и имплементацијата на самиот јазик таму, плус можете да погледнете примери на паметни договори од минатите два натпревари. Линкови на крајот од статијата.

Да речеме дека веќе напишавме паметен договор за FunC, после тоа го компајлираме кодот во Fift асемблер.

Останува да се објави составениот паметен договор. За да го направите ова, треба да напишете функција во Fift, кој ќе го земе кодот за паметен договор и некои други параметри како влез, а излезот ќе биде датотека со наставката .boc (што значи „торба со ќелии“) и, во зависност од тоа како го пишуваме, приватен клуч и адреса, кои се генерираат врз основа на кодот за паметен договор. Веќе можете да испраќате грамови на адреса на паметен договор кој сè уште не е објавен.

Да се ​​објави паметен договор во ТОН примени .boc датотеката ќе треба да се испрати до блокчејнот користејќи лесен клиент (повеќе за тоа подолу). Но, пред да објавите, треба да префрлите грамови на генерираната адреса, во спротивно паметниот договор нема да биде објавен. По објавувањето, можете да комуницирате со паметниот договор со испраќање пораки однадвор (на пример, користејќи лесен клиент) или од внатре (на пример, еден паметен договор испраќа порака на друг во TON).

Откако ќе разбереме како се објавува кодот, станува полесно. Отприлика знаеме што сакаме да напишеме и како ќе функционира нашата програма. И додека пишуваме, бараме како ова е веќе имплементирано во постоечките паметни договори или го разгледуваме кодот за имплементација Fift и FunC во официјалното складиште или погледнете во официјалната документација.

Многу често барав клучни зборови во четот на Телеграм каде се собираа сите учесници на натпреварот и вработени во Телеграм и се случуваше за време на натпреварот сите да се соберат таму и да почнат да разговараат за Fift и FunC. Линк на крајот од статијата.

Време е да преминеме од теорија во пракса.

Подготовка на околината за работа со ТОН

Направив сè што ќе биде опишано во написот за MacOS и го проверив двапати во чист Ubuntu 18.04 LTS на Docker.

Првото нешто што треба да направите е да преземете и инсталирате lite-client со кој можете да испраќате барања до ТОН.

Упатствата на официјалната веб-страница го опишуваат процесот на инсталација доста детално и јасно и испуштаат некои детали. Овде ги следиме упатствата, инсталирајќи ги зависностите што недостасуваат на патот. Не го составив секој проект сам и не го инсталирав од официјалното складиште на 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

Правење на првите барања до ТОН

Сега да започнеме 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%. Доколку нема доволно грами за плаќање, тогаш трансакцијата ќе ја сметаме за надополнување.

Згора на тоа, важно е облозите да можат да се видат во реално време и во пригодна форма, за да може корисникот веднаш да разбере дали победил или изгубил. Затоа, треба да направите веб-локација која ќе прикажува облози и резултати директно од ТОН.

Пишување паметен договор

За погодност, го истакнав кодот за FunC; приклучокот може да се најде и инсталира во пребарувањето за код на Visual Studio; ако одеднаш сакате да додадете нешто, го направив приклучокот јавно достапен. Исто така, некој претходно направил додаток за работа со Fift, исто така можете да го инсталирате и да го најдете во VSC.

Ајде веднаш да создадеме складиште каде што ќе ги извршиме средните резултати.

За да ни го олесниме животот, ќе напишеме паметен договор и ќе го тестираме локално додека не биде подготвен. Дури потоа ќе го објавиме во ТОН.

Паметниот договор има два надворешни методи до кои може да се пристапи. Прво, recv_external() оваа функција се извршува кога барањето до договорот доаѓа од надворешниот свет, односно не од TON, на пример, кога ние самите генерираме порака и ја испраќаме преку lite-client. Второ, recv_internal() ова е кога, во самиот ТОН, секој договор се однесува на нашиот. Во двата случаи, можете да пренесете параметри на функцијата.

Да почнеме со едноставен пример кој ќе функционира доколку е објавен, но во него нема функционално оптоварување.

() 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

Досега се чини јасно, сега да го додадеме во истата датотека кодот што ќе го користиме за да го стартуваме ТВМ.

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 го снимаме контекстот, односно податоците со кои ќе се стартува ТВМ (или мрежната состојба). Дури и за време на натпреварот, еден од програмерите покажа како се создава c7 и копирав. Во оваа статија можеби ќе треба да се промениме rand_seed бидејќи генерирањето на случаен број зависи од тоа и доколку не се промени, истиот број ќе се враќа секој пат.

recv_internal и recv_external константите со вредности 0 и -1 ќе бидат одговорни за повикување на соодветните функции во паметниот договор.

Сега сме подготвени да го создадеме првиот тест за нашиот празен паметен договор. За јасност, засега ќе ги додадеме сите тестови во истата датотека lottery-test-suite.fif.

Ајде да создадеме променлива storage и напишете празно во него cell, ова ќе биде паметното складирање на договори.

message Ова е пораката што ќе ја пренесеме на паметниот контакт однадвор. Засега и ќе го испразниме.

variable storage 
<b b> storage ! 

variable message 
<b b> message ! 

Откако ќе ги подготвиме константите и променливите, го стартуваме TVM користејќи ја командата runvmctx а креираните параметри префрлете ги на влезот.

message @ 
recv_external 
code 
storage @ 
c7 
runvmctx 

На крајот ќе успееме како ова среден код за Fift.

Сега можеме да го извршиме добиениот код.

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

Програмата треба да работи без грешки и на излезот ќе го видиме дневникот за извршување:

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

Одлично, ја напишавме првата работна верзија на паметниот договор.

Сега треба да додадеме функционалност. Прво да се справиме со пораките што доаѓаат од надворешниот свет до recv_external()

Самиот развивач го избира форматот на пораката што може да го прифати договорот.

Но, обично

  • прво, сакаме да го заштитиме нашиот договор од надворешниот свет и да го направиме така што само сопственикот на договорот може да му испраќа надворешни пораки.
  • второ, кога испраќаме валидна порака до TON, сакаме ова да се случи точно еднаш и кога повторно ќе ја испратиме истата порака, паметниот договор ја отфрла.

Значи, речиси секој договор ги решава овие два проблема, бидејќи нашиот договор прифаќа надворешни пораки, треба да се грижиме и за тоа.

Ќе го направиме тоа во обратен редослед. Прво, да го решиме проблемот со повторување, ако договорот веќе добил таква порака и ја обработил, нема да го изврши втор пат. И тогаш ќе го решиме проблемот, така што само одреден круг на луѓе може да испраќа пораки до паметниот договор.

Постојат различни начини за решавање на проблемот со дупликат пораки. Еве како ќе го направиме тоа. Во паметниот договор го иницијализираме бројачот на примени пораки со почетна вредност 0. Во секоја порака на паметниот договор ќе ја додадеме тековната бројач вредност. Ако бројачот во пораката не се совпаѓа со вредноста во паметниот договор, тогаш не ја обработуваме; ако е така, тогаш ја обработуваме и бројачот во паметниот договор го зголемуваме за 1.

Да се ​​вратиме на lottery-test-suite.fif и додадете втор тест на него. Ако испратиме неточен број, кодот треба да фрли исклучок. На пример, оставете ги податоците за договорот да складираат 166, а ние ќе испратиме 165.

<b 166 32 u, b> storage !
<b 165 32 u, b> message !

message @ 
recv_external 
code 
storage @ 
c7 
runvmctx

drop 
exit_code ! 
."Exit code " exit_code @ . cr 
exit_code @ 33 - abort"Test #2 Not passed"

Ајде да започнеме.

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

И ќе видиме дека тестот се извршува со грешка.

[ 1][t 0][1582283084.210902214][words.cpp:3046] lottery-test-suite.fif:67: abort": Test #2 Not passed
[ 1][t 0][1582283084.210941076][fift-main.cpp:196]      Error interpreting file `lottery-test-suite.fif`: error interpreting included file `lottery-test-suite.fif` : lottery-test-suite.fif:67: abort": Test #2 Not passed

Во оваа фаза lottery-test-suite.fif треба да изгледа како по ссылке.

Сега да ја додадеме контра-логиката на паметниот договор во lottery-code.fc.

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

() recv_external(slice in_msg) impure {
    if (slice_empty?(in_msg)) {
        return (); 
    }
    int msg_seqno = in_msg~load_uint(32);
    var ds = begin_parse(get_data());
    int stored_seqno = ds~load_uint(32);
    throw_unless(33, msg_seqno == stored_seqno);
}

В slice in_msg лежи пораката што ја испраќаме.

Првото нешто што го правиме е да провериме дали пораката содржи податоци, ако не, тогаш едноставно излегуваме.

Следно, ја анализираме пораката. in_msg~load_uint(32) го вчитува бројот 165, 32-битен unsigned int од пренесената порака.

Следно, вчитуваме 32 бита од складиштето за паметни договори. Проверуваме дали вчитаниот број се совпаѓа со поминатиот, ако не, ставаме исклучок. Во нашиот случај, бидејќи поминуваме ненатпревар, треба да се фрли исклучок.

Сега ајде да составиме.

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

Копирајте го добиениот код во lottery-test-suite.fif, не заборавајќи да ја замениме последната линија.

Проверуваме дали тестот поминува:

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

Точно тука Можете да го видите соодветниот commit со тековните резултати.

Забележете дека е незгодно постојано да се копира компајлираниот код на паметен договор во датотека со тестови, па ајде да напишеме скрипта што ќе го запише кодот во константа за нас, а ние едноставно ќе го поврземе компајлираниот код со нашите тестови користејќи "include".

Направете датотека во папката на проектот build.sh со следнава содржина.

#!/bin/bash

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

Ајде да го направиме извршно.

chmod +x ./build.sh

Сега, само стартувајте ја нашата скрипта за да го составите договорот. Но, покрај ова, треба да го запишеме во константа code. Така ќе создадеме нова датотека lotter-compiled-for-test.fif, што ќе го вклучиме во датотеката lottery-test-suite.fif.

Ајде да додадеме код за прескокнување на 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.

Врска за обврзување со додадено тесто.

Ајде да го извршиме тестот и да се увериме дека не успее. Сега да додадеме логика за да ја смениме адресата на сопственикот на лотаријата.

Во паметниот договор продолжуваме да анализираме 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 што може да најде во официјалното складиште.

Нешто на што вреди да се обрне внимание. Ние генерираме паметно складирање на договори и влезна порака. После ова се генерира адресата на паметниот договор, односно адресата е позната уште пред објавувањето во ТОН. Следно, треба да испратите неколку грама на оваа адреса, а само после тоа треба да испратите датотека со самиот паметен договор, бидејќи мрежата зема провизија за складирање на паметниот договор и операциите во него (валидатори кои складираат и извршуваат паметни договори). Кодот може да се види овде.

Следно, го извршуваме кодот за објавување и добиваме 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

Само за забава, ајде да побараме до ТОН

$ ./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, кој беше генериран порано при креирањето на паметниот договор. Еве ја соодветната заложба.

Примање информации од паметен договор користејќи методи за добивање

Сега ајде да погледнеме како да се водат методите за добивање паметни договори.

Лансира 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 и сака да напише и објави не толку едноставен паметен договор со можност за интеракција со тоа. Можеби некои работи можеше да се објаснат поедноставно.

Можеби некои аспекти од имплементацијата можеа да се направат поефикасно и поелегантно, но тогаш ќе беше потребно уште повеќе време да се подготви статијата. Исто така, можно е да згрешив некаде или да не разбрав нешто, па ако работите нешто сериозно, треба да се потпрете на официјалната документација или на официјалното складиште со кодот ТОН.

Треба да се напомене дека со оглед на тоа што самиот ТОН е сè уште во активна фаза на развој, може да се случат промени кои ќе прекршат некој од чекорите во овој напис (што се случи додека пишував, веќе е коригирано), но општиот пристап е веројатно нема да се промени.

Нема да зборувам за иднината на ТОН. Можеби платформата ќе стане нешто големо и треба да потрошиме време да ја проучуваме и да пополниме ниша со нашите производи сега.

Тука е и 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. Разговарајте за TON во Telegram, што навистина помогна да се разбере во почетната фаза. Мислам дека нема да биде грешка ако кажам дека сите што напишале нешто за ТОН се таму. Таму може да побарате и тест грамови. https://t.me/tondev_ru
  8. Уште еден разговор за ТОН во кој најдов корисни информации: https://t.me/TONgramDev
  9. Првата фаза од натпреварот: https://contest.com/blockchain
  10. Втора фаза од натпреварот: https://contest.com/blockchain-2

Извор: www.habr.com

Додадете коментар