Jak napsat a publikovat smart kontrakt v TON
O čem je tento článek?
V článku popíšu, jak jsem se zúčastnil prvního (z dvou) soutěže Telegramu o blockchain, nezískal jsem žádné ocenění a rozhodl se svůj zážitek zdokumentovat, aby nezapadl do zapomnění a možná pomohl někomu.
Protože jsem nechtěl psát abstraktní kód, ale vytvořit něco funkčního, napsal jsem pro článek smart kontrakt na okamžitou loterii a web, který zobrazuje data smart kontraktu přímo z TON bez použití meziskladů.
Článek bude užitečný pro ty, kteří chtějí vytvořit svůj první smart kontrakt v TON, ale nevědí, jak začít.
Na příkladu loterie projdu od nastavení prostředí až po publikaci smart kontraktu, interakci s ním a vytvořím web pro zisk a publikaci dat.
O účasti v soutěži
V říjnu minulého roku Telegram vyhlásil soutěž o blockchain s novými jazyky Fift a FunC. Bylo třeba napsat jakýkoliv z pěti navrhovaných smart kontraktů. Uvažoval jsem, že by nebylo špatné se pustit do něčeho neobvyklého, naučit se jazyk a vytvořit něco, i když v budoucnu nemusím psát nic jiného. Navíc, téma je stále v kurzu.
Je třeba říci, že jsem neměl žádné zkušenosti s vývojem smart kontraktů.
Plánoval jsem se účastnit až do samého konce, pokud to půjde, a poté napsat přehledový článek, ale hned v prvním kole jsem selhal. Já s multi-podpisem na FunC a ta v zásadě fungovala. Jako základ jsem vzal .
V tu chvíli jsem si myslel, že to určitě stačí, abych získal alespoň nějaké ocenění. Výsledkem bylo, že asi 40 z 60 účastníků se stalo oceněnými a mezi nimi jsem nebyl. V tom žádné drama nebylo, ale byla tam jedna věc, která mě znepokojila. V době vyhlášení výsledků žádné hodnocení s testem mého kontraktu nebylo provedeno, ptal jsem se účastníků v chatu, jestli je někdo další, kdo ho nemá, nebyli mezi nimi.
Zřejmě si všimli mých zpráv, a po dvou dnech rozhodčí zveřejnili komentář, a já jsem tak úplně nepochopil, zda můj smart kontrakt během hodnocení prostě přehlédli, nebo jej považovali za natolik špatný, že si nezaslouží komentář. Položil jsem otázku na stránce, ale odpovědi jsem se nedočkal. Ačkoliv kdo posuzoval — to není tajemství, považoval jsem za zbytečné psát osobní zprávy.
Na porozumění bylo vynaloženo nemálo času, proto bylo rozhodnuto napsat článek. Jelikož informací zatím není příliš, článek pomůže ušetřit čas všem zainteresovaným.
Koncept fungování chytrých kontraktů v TON
Než začneme něco psát, musíme se ujistit, z jakého úhlu k této věci přistoupit. Proto nyní vysvětlím, z jakých částí se systém skládá. Přesněji řečeno, které části je třeba znát, aby bylo možné napsat alespoň nějaký funkční kontrakt.
Soustředíme se na psaní chytrého kontraktu a práci s TON Virtual Machine (TVM), Fift a FunC, proto článek více připomíná popis vývoje běžného programu. Na tom, jak platforma samotná funguje, se zde zastavovat nebudeme.
O tom, jak funguje TVM a jazyk Fift , existuje dobrá oficiální dokumentace. Během účasti v soutěži a nyní při psaní aktuálního kontraktu jsem se na ni často obracel.
Hlavní jazyk, ve kterém jsou psány chytré kontrakty — FunC. V současnosti neexistuje žádná dokumentace, takže pro napsání něčeho je nutné studovat příklady chytrých kontraktů z oficiálního repozitáře a také samotnou implementaci jazyka tamtéž, navíc je dobré se podívat na příklady chytrých kontraktů z uplynulých dvou soutěží. Odkazy najdete na konci článku.
Představme si, že už jsme napsali chytrý kontrakt na FunC, poté zkompilujeme kód do Fift-assembleru.
Zkompilovaný chytrý kontrakt zbývá publikovat. K tomu musíme napsat funkci v Fift, která na vstupu přijme kód chytrého kontraktu a ještě některé parametry, a na výstupu se získá soubor s příponou .boc (což znamená "bag of cells"), a v závislosti na způsobu, jakým ji napíšeme, privátní klíč a adresu, která se generuje na základě kódu chytrého kontrakt. Na adresu chytrého kontraktu, který ještě nebyl publikován, je už možné posílat gramy.
Abychom publikovali chytrý kontrakt v TON, je nutné odeslat získaný .boc soubor do blockchainu pomocí light klienta (o čemž bude níže). Ale než to publikujeme, je potřeba převést gramy na vygenerovanou adresu, jinak chytrý kontrakt nebude publikován. Po publikaci s chytrým kontraktem bude možné interagovat, posílat mu zprávy zvenčí (například pomocí light klienta) nebo zevnitř (například jeden chytrý kontrakt posílá druhému zprávu uvnitř TON).
Jakmile jsme pochopili, jak se kód publikuje, další postup je jednodušší. Přesně víme, co chceme napsat a jak bude naše aplikace fungovat. Při psaní hledáme, jak bylo toto již implementováno v existujících chytrých smlouvách, nebo se podíváme na kód implementace. Fift a FunC v oficiálním repozitáři, nebo se podíváme do oficiální dokumentace.
Často jsem hledal klíčová slova v Telegram chatu, kde se setkali všichni účastníci soutěže a zaměstnanci Telegramu, tak se stalo, že během soutěže se tam všichni sešli a začali diskutovat o Fift a FunC. Odkaz na konci článku.
Je čas přejít od teorie k praxi.
Příprava prostředí pro práci s TON
Vše, co bude v článku popsáno, jsem provedl na MacOS a ověřil na čistém Ubuntu 18.04 LTS pomocí Dockeru.
První věc, kterou musíte udělat, je stáhnout a nainstalovat lite-client , pomocí kterého lze posílat dotazy do TON.
Návod na oficiálních stránkách poměrně podrobně a srozumitelně popisuje proces instalace a vynechává některé detaily. Zde se řídíme názorem a zároveň instalujeme chybějící závislosti. Nechtěl jsem sám kompilovat každý projekt a instaloval jsem z oficiálního repozitáře Ubuntu (na MacOS jsem použil 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 Jakmile jsou všechny závislosti nainstalovány, můžeme nainstalovat lite-client, Fift, FunC.
Nejprve klonujeme repozitář TON spolu se závislostmi. Pro pohodlí to všechno provedeme ve složce ~\/TON.
cd ~\/TON
git clone https:\/\/github.com\/ton-blockchain\/ton.git
cd .\/ton
git submodule update --init --recursiveV repozitáři se také nacházejí implementace Fift a FunC.
Teď jsme připraveni sestavit projekt. Kód repozitáře byl zklonován do složky ~\/TON\/ton. V ~\/TON vytvořte složku build a v ní sestavíme projekt.
mkdir ~\/TON\/build
cd ~\/TON\/build
cmake ..\/tonJelikož máme v úmyslu psát chytrou smlouvu, potřebujeme nejen lite-client, ale také Fift s FunC, proto kompilujeme všechno. Není to rychlý proces, takže čekáme.
cmake --build . --target lite-client
cmake --build . --target fift
cmake --build . --target funcDále stáhneme konfigurační soubor, ve kterém jsou údaje o nodu, ke kterému lite-client se připojíme.
wget https://test.ton.org/ton-lite-client-test1.config.jsonDěláme první dotazy do TON
Nyní spustíme lite-client.
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.jsonPokud kompilace proběhla úspěšně, po spuštění uvidíte log připojení klienta k nodu.
[ 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)
...Můžete vykonat příkaz help a podívat se, jaké příkazy jsou k dispozici.
helpVyjmenujeme příkazy, které budeme používat v tomto článku.
seznam dostupných příkazů:
last Získá poslední blok a informace o stavu ze serveru
sendfile Načte serializovanou zprávu z a odešle ji na server
getaccount [] Načte nejnovější stav specifikovaného účtu; je ve formátu [:]
runmethod [] ... Spustí GET metodu účtu se specifikovanými parametrylast získává poslední vytvořený blok ze serveru.
sendfile odesílá do TON soubor se zprávou, právě pomocí tohoto příkazu se publikuje smart kontrakt a jeho požadavky.
getaccount načítá aktuální stav smart kontraktu na uvedené adrese.
runmethod [] spouští get-metody smart kontraktu. Nyní jsme připraveni napsat samotný kontrakt.
Implementace
Nápad
Jak jsem již psal výše, smart kontrakt, který píšeme, je loterie.
Navíc to není loterie, kde si musíte koupit lístek a čekat hodinu, den nebo měsíc, ale okamžitá, ve které uživatel převádí na adresu kontraktu N gramy, a okamžitě dostává zpět 2 * N gramů nebo prohrává. Pravděpodobnost výhry uděláme kolem 40 %. Pokud gramů na výplatu nebude dost, budeme považovat transakci za doplnění.
Je důležité, aby sázky bylo možné vidět v reálném čase a pohodlným způsobem, aby uživatel ihned pochopil, jestli vyhrál nebo prohrál. Proto je třeba vytvořit webovou stránku, která ukáže sázky a výsledky přímo z TON.
Napsání smart kontraktu
Pro pohodlí jsem vytvořil zvýraznění kódu pro FunC, plugin můžete najít a nainstalovat v hledání Visual Studio Code. Pokud byste chtěli něco přidat, plugin je k dispozici veřejně. Dříve byl rovněž vytvořen plugin pro práci s Fift, který si také můžete nainstalovat v VSC.
I hned vytvoříme repozitář, do kterého budeme commitovat mezivýsledky.
Abychom si usnadnili život, budeme psát smart kontrakt a testovat jej lokálně, dokud nebude připraven. Až poté ho zveřejníme v TON.
Smart kontrakt má dvě externí metody, na které se lze obrátit. První, recv_external() tato funkce se provádí, když k kontraktu přichází žádost z vnějšího světa, tedy ne z TON, například když sami formulujeme zprávu a odesíláme ji přes lite-client. Druhý, recv_internal() to je, když uvnitř samotného TON nějaký kontrakt volá náš. V obou případech lze předat parametry do funkce.
Začněme jednoduchým příkladem, který bude fungovat, pokud ho zveřejníme, ale není v něm žádná funkční náplň.
() recv_internal(slice in_msg) impure {
;; TODO: implementace
}
() recv_external(slice in_msg) impure {
;; TODO: implementace
}Zde je třeba vysvětlit, co je slice. Všechna uložená data v TON Blockchain jsou kolekcí TVM cell nebo jen cell, v takové buňce lze uložit až 1023 bitů dat a až 4 odkazy na jiné buňky.
TVM cell slice nebo slice je část existující cell používá se pro její parsování, později to bude jasné. Hlavní pro nás je, že do chytrého kontraktu můžeme předat slice a v závislosti na typu zprávy zpracovat data v recv_external() nebo recv_internal().
impure — klíčové slovo, které naznačuje, že funkce mění data chytrého kontraktu.
Uložíme kód kontraktu do lottery-code.fc a zkompilujeme.
~\/TON\/build\/crypto\/func -APSR -o lottery-compiled.fif ~\/TON\/ton\/crypto\/smartcont\/stdlib.fc .\/lottery-code.fc Hodnoty flagů lze zobrazit pomocí příkazu
~\/TON\/build\/crypto\/func -helpZískali jsme zkompilovaný Fift-assembler kód v 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>cMůže být spuštěn lokálně, pro to připravíme prostředí.
Poznamenáme, že v prvním řádku se připojuje Asm.fif, to je kód napsaný v Fift pro Fift-assembler.
Protože chceme spouštět a testovat chytrý kontrakt lokálně, vytvoříme soubor lottery-test-suite.fif a zkopírujeme tam zkompilovaný kód, přičemž nahradíme poslední řádek, který zapisuje kód chytrého kontraktu do konstanty code, abychom ho později předali virtuální mu POD:
"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
Zatím to vypadá jasně, nyní přidáme do stejného souboru kód, který budeme používat pro spuštění TVM.
0 tuple 0x076ef1ea , \/\/ magic
0 , 0 , \/\/ akce msg_sents
1570998536 , \/\/ unix_time
1 , 1 , 3 , \/\/ block_lt, trans_lt, rand_seed
0 tuple 100000000000000 , dictnew , , \/\/ zbývající zůstatek
0 , dictnew , \/\/ contract_address, global_config
1 tuple \/\/ zabalit do dalšího tuple
constant c7
0 constant recv_internal \/\/ pro spuštění recv_internal()
-1 constant recv_external \/\/ pro vyvolání recv_external()V c7 zapíšeme kontext, tedy data, s nimiž se bude spouštět TVM (nebo stav sítě). Už během soutěže jeden z vývojářů ukázal, jak se vytváří c7 a já jsem si to zkopíroval. V tomto článku možná budeme potřebovat měnit rand_seed protože od něj závisí generace náhodného čísla a nemění se, takže se vždy vrátí stejné číslo.
recv_internal a recv_external konstanty se hodnotou 0 a -1 budou odpovídat za zavolání příslušných funkcí ve smart kontraktu.
Nyní jsme připraveni vytvořit první test pro náš prázdný smart kontrakt. Pro názornost zatím všechny testy přidáme do tohoto souboru lottery-test-suite.fif.
Vytvoříme proměnnou úložiště a zapíšeme do ní prázdnou cell, to bude úložiště smart kontraktu.
zpráva to je zpráva, kterou předáme smart kontraktu zvenku. Také to zatím uděláme prázdné.
úložný prostor
<b b> úložiště !
variabilní zpráva
<b b> zpráva ! Po přípravě konstant a proměnných spustíme TVM pomocí příkazu runvmctx a předáme vytvořené parametry jako vstup.
message @
recv_external
code
storage @
c7
runvmctx Nakonec dostaneme mezičlánkový kód na Fift.
Nyní můžeme spustit získaný kód.
export FIFTPATH=~\/TON\/ton\/crypto\/fift\/lib \/\/ provedeme jednou pro pohodlí
~\/TON\/build\/crypto\/fift -s lottery-test-suite.fif Program by měl proběhnout bez chyb a ve výstupu uvidíme log vykonávání:
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=0Skvěle, napsali jsme první funkční verzi smart kontraktu.
Nyní je potřeba přidat funkčnost. Nejprve se zaměříme na zprávy, které přicházejí z vnějšího světa do recv_external()
Vývojář si sám vybírá formát zprávy, který kontrakt může přijmout.
Ale obvykle,
- za prvé, chceme chránit náš kontrakt před vnějším světem a zajistit, aby pouze vlastník kontraktu mohl posílat externí zprávy na něj.
- za druhé, když posíláme platnou zprávu do TON, chceme, aby se to stalo přesně jednou, a při opakovaném poslání téže zprávy smart kontrakt ji odmítl.
Proto v téměř každém kontraktu řešíme tyto dva problémy, protože náš kontrakt přijímá externí zprávy, musíme se o to také postarat.
Uděláme to v opačném pořadí. Nejprve vyřešíme problém s opakováním, pokud kontrakt již obdržel takovou zprávu a zpracoval ji, nebude ji provádět podruhé. A pak vyřešíme problém, aby pouze určitý okruh osob mohl posílat zprávy smart kontraktu.
Existují různé způsoby, jak vyřešit problém s opakujícími se zprávami. Uděláme to takto. V chytrém kontraktu inicializujeme počítadlo přijatých zpráv s počáteční hodnotou 0. Do každé zprávy přidáme aktuální hodnotu počítadla. Pokud hodnota počítadla ve zprávě nesouhlasí s hodnotou v chytrém kontraktu, nebudeme ji zpracovávat; pokud souhlasí, zpracujeme ji a zvýšíme počítadlo v chytrém kontraktu o 1.
Vracíme se do lottery-test-suite.fif a dopíšeme do něj druhý test. Odešleme nesprávné číslo, kód by měl vyhodit výjimku. Například ať je v datech kontraktu uloženo 166, a my odešleme 165.
<b 166 32 u, b> úložiště !
<b 165 32 u, b> zpráva !
zpráva @
recv_external
kód
úložiště @
c7
runvmctx
drop
exit_code !
."Kód výstupu " exit_code @ . cr
exit_code @ 33 - abort"Test #2 nebyl úspěšný"Spustíme.
~ /TON/build/crypto/fift -s lottery-test-suite.fif A uvidíme, že test probíhá s chybou.
[ 1][t 0][1582283084.210902214][words.cpp:3046] lottery-test-suite.fif:67: abort": Test #2 nebyl splněn
[ 1][t 0][1582283084.210941076][fift-main.cpp:196] Chyba při interpretaci souboru `lottery-test-suite.fif`: chyba při interpretaci zahrnutého souboru `lottery-test-suite.fif` : lottery-test-suite.fif:67: abort": Test #2 nebyl splněnV této fázi lottery-test-suite.fif by měl vypadat jako .
Nyní přidáme logiku počítadla do chytrého kontraktu v lottery-code.fc.
() recv_internal(slice in_msg) impure {
;; TODO: implementace
}
() 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);
}V slice in_msg obsahuje zprávu, kterou posíláme.
První, co uděláme, je zkontrolovat, zda ve zprávě jsou data; pokud ne, jednoduše odejdeme.
Dále zprávu parsujeme. in_msg~load_uint(32) načítá číslo 165, 32bitové unsigned int z předané zprávy.
Poté načítáme 32 bitů ze skladu chytrého kontraktu. Kontrolujeme, že načtené číslo souhlasí s předaným; pokud ne, vyhodíme výjimku. V našem případě, protože předáváme nesouhlasící, by měla být vyhozená výjimka.
Nyní zkompilujeme.
~\/TON\/build\/crypto\/func -APSR -o lottery-compiled.fif ~\/TON\/ton\/crypto\/smartcont\/stdlib.fc .\/lottery-code.fc Výsledný kód zkopírujeme do lottery-test-suite.fif, nezapomínáme nahradit poslední řádek.
Zkontrolujeme, že test prochází:
~ /TON/build/crypto/fift -s lottery-test-suite.fifmůžete vidět odpovídající commit s aktuálními výsledky.
Poznamenáme, že neustále kopírovat zkompilovaný kód chytrého kontraktu do souboru s testy není pohodlné, proto napíšeme skript, který bude zapisovat kód do konstanty za nás, a my jednoduše připojíme zkompilovaný kód do našich testů pomocí "include".
V adresáři projektu vytvoříme soubor build.sh s následujícím obsahem.
#!/bin/bash
~/TON/build/crypto/func -SPA -R -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fcUčiníme ho spustitelným.
chmod +x ./build.shNyní stačí spustit náš skript, aby se zkompilovala smlouva. Kromě toho musíme jej zapsat do konstanty code. Proto vytvoříme nový soubor lotter-compiled-for-test.fif, který zahrneme v souboru lottery-test-suite.fif.
Přidáme do sh skriptu kód, který jednoduše zduplikuje zkompilovaný soubor do lotter-compiled-for-test.fif a změní v něm poslední řádek.
# 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.fifNyní, abychom to zkontrolovali, spustíme vzniklý skript a vygeneruje se nám soubor lottery-compiled-for-test.fif, který zahrneme do našeho lottery-test-suite.fif
V lottery-test-suite.fif odstraníme kód smlouvy a přidáme řádek "lottery-compiled-for-test.fif" include.
Spustíme testy, abychom zkontrolovali, že procházejí.
~ /TON/build/crypto/fift -s lottery-test-suite.fifSkvěle, nyní, abychom automatizovali spuštění testů, vytvoříme soubor test.sh, který nejprve vykoná build.sh, a poté spustí testy.
touch test.sh
chmod +x test.shDovnitř napíšeme
.\/build.sh
echo "nKompilace dokončena n"
export FIFTPATH=~\/TON\/ton\/crypto\/fift\/lib
~\/TON\/build\/crypto\/fift -s lottery-test-suite.fifUděláme test.sh a spustíme, abychom se ujistili, že testy fungují.
chmod +x .\/test.sh
.\/test.shKontrolujeme, že se smlouva kompiluje a testy se vykonávají.
Skvěle, nyní při spuštění test.sh bude okamžitě probíhat kompilace a spuštění testů. Zde je odkaz na .
Dobře, než budeme pokračovat, udělejme pro pohodlí ještě jednu věc.
Vytvoříme složku build kde budeme uchovávat zkompilovanou smlouvu a její klon zapsaný do konstanty lottery-compiled.fif, lottery-compiled-for-test.fif. Také vytvoříme složku test kde budou uloženy soubor s testy lottery-test-suite.fif a potenciálně další pomocné soubory. .
Pokračujme ve vývoji chytré smlouvy.
Dále by měl být test, který kontroluje, že zpráva je přijímána a čítač se aktualizuje ve skladu, když posíláme správné číslo. Ale to uděláme později.
Teď přemýšlíme o tom, jaká je struktura dat a jaká data je třeba uchovávat v chytré smlouvě.
Popíšu vše, co uchováváme.
`seqno` 32-bit celé kladné číslo, počítadlo.
`pubkey` 256-bit celé kladné číslo, veřejný klíč, pomocí kterého budeme kontrolovat podpis zprávy zaslané zvenčí, o čemž dále.
`order_seqno` 32-bit celé kladné číslo, které uchovává počítadlo počtu sázek.
`number_of_wins` 32-bit celé kladné číslo, které uchovává počet výher.
`incoming_amount` typ dat Gram (první 4 bity odpovídají délce), uchovává celkové množství gramů, které byly zaslány na kontrakt.
`outgoing_amount` celkové množství gramů, které bylo zasláno výhercům.
`owner_wc` číslo workchainu, 32-bit (na některých místech je uvedeno, že 8-bit) celé číslo. V současnosti jsou pouze dvě -1 a 0.
`owner_account_id` 256-bit celé kladné číslo, adresa kontraktu v aktuálním workchainu.
`orders` proměnná typu slovník, uchovává posledních dvacet sázek. Dále je potřeba napsat dvě funkce. První pojmenujme pack_state(), která bude balit data pro následné uložení do úložiště smart kontraktu. Druhou, nazveme unpack_state() , která bude číst a vracet data z úložiště.
_ 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;
}Přidáme tyto dvě funkce na začátek smart kontraktu. Výsledkem bude mezikrok.
Abychom uložili data, bude potřeba zavolat vestavěnou funkci set_data() a ta zapíše data z pack_state() do úložiště smart kontraktu.
cell packed_state = pack_state(arg_1, .., arg_n);
set_data(packed_state);Nyní, když máme pohodlné funkce pro zápis a čtení dat, můžeme jít dál.
Musíme zkontrolovat, že příchozí zpráva je podepsána vlastníkem kontraktu (nebo jiným uživatelem, který má přístup k soukromému klíči).
Když publikujeme smart kontrakt, můžeme jej inicializovat s potřebnými daty ve skladu, které se uchovají pro budoucí použití. Zapíšeme tam veřejný klíč, abychom mohli ověřit, že podpis příchozí zprávy byl vytvořen odpovídajícím privátním klíčem.
Než budeme pokračovat, vytvořme privátní klíč a zapíšeme jej do test/keys/owner.pk. Za tímto účelem spustíme Fift v interaktivním režimu a vykonáme čtyři příkazy.
`newkeypair` generuje veřejný a privátní klíč a zapisuje je do zásobníku.
`drop` odstraní zásobníku vrchní prvek (v tomto případě veřejný klíč)
`.s` pouze zobrazí, co je v zásobníku v tomto okamžiku
`"owner.pk" B>file` zapíše privátní klíč do souboru nazvaného `owner.pk`.
`bye` ukončuje práci s Fift. Vytvoříme složku keys ve složce test a tam zapíšeme privátní klíč.
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
byeVidíme v aktuální složce soubor owner.pk.
Odstraňujeme veřejný klíč ze zásobníku, kdykoli ho budeme potřebovat, můžeme jej získat z privátního.
Nyní potřebujeme napsat kontrolu podpisu. Začneme testem. Nejprve načteme privátní klíč ze souboru pomocí funkce file>B a zapíšeme jej do proměnné owner_private_key, dále pomocí funkce priv>pub převedeme privátní klíč na veřejný a uložíme výsledek do 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 !Oba klíče budeme potřebovat.
Inicializujeme úložiště smart kontraktu náhodnými daty ve stejné posloupnosti, jako v funkci pack_state()a zapíšeme do proměnné úložiště.
proměnná owner_private_key
proměnná owner_public_key
proměnná orders
proměnná owner_wc
proměnná owner_account_id
".\/keys\/owner.pk" soubor>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, gram, owner_wc i, owner_account_id 256 orders dict, b> úložiště !Dále vytvoříme podepsanou zprávu, která bude obsahovat pouze podpis a hodnotu čítače.
Nejprve vytvoříme data, která chceme přenést, potom je podepíšeme svým privátním klíčem a nakonec vytvoříme podepsanou zprávu.
variabilní zpráva_k_podepsání
variabilní zpráva_k_odeslání
variabilní podpis
<b 0 32 u, b> zpráva_k_podepsání !
zpráva_k_podepsání @ majitel_private_key @ ed25519_sign_uint podpis !
<b signature @ b, 0 32 u, b> <s ! Výsledná zpráva, kterou odešleme do smart kontraktu, je uložena v proměnné message_to_send, pokud jde o funkce hashu, ed25519_sign_uint si můžete přečíst .
A pro spuštění testu znovu zavoláme.
message_to_send @
recv_external
code
storage @
c7
runvmctxměla by vypadat jako soubor s testy v této fázi.
Spustíme test a selže, proto upravíme smart kontrakt, aby mohl přijímat zprávy takového formátu a ověřovat podpis.
Nejprve načteme 512 bitů podpisu z zprávy a uložíme je do proměnné, poté načteme 32 bitů z proměnné čítače.
Jelikož máme funkci pro načítání dat ze smart kontraktu, budeme ji používat.
Dále proběhne kontrola čítače předaného spolu se stavem a kontrola podpisu. Pokud se něco neshoduje, vyhodíme výjimku se odpovídající chybovým kódem.
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));Odpovídající commit .
Spustíme testy a uvidíme, že druhý test selhává. Z důvodu nedostatku bitů ve zprávě a nedostatku bitů ve skladu selhává kód při parsování. Musíme přidat podpis zprávy, kterou odesíláme, a zkopírovat sklad z posledního testu.
Ve druhém testu přidáme podpis zprávy a změníme sklad smart kontraktu. Takto momentálně vypadá soubor s testy.
Napíšeme čtvrtý test, ve kterém budeme odesílat zprávu podepsanou cizím soukromým klíčem. Vytvoříme další soukromý klíč a uložíme ho do souboru not-owner.pk. Tímto soukromým klíčem podepíšeme zprávu. Spustíme testy a ujistíme se, že všechny testy procházejí. do současné doby.
Nyní konečně můžeme přejít k implementaci logiky smart kontraktu.
V recv_external() Budeme přijímat dva typy zpráv.
Jelikož náš kontrakt bude akumulovat prohry hráčů, tyto peníze musí být převedeny na tvůrce loterie. Adresa peněženky tvůrce loterie je uložena ve skladu při vytváření kontraktu.
Pro jistotu potřebujeme možnost měnit adresu, na kterou posílat gramy prohrávajících. Také bychom měli mít možnost posílat gramy z loterie na adresu vlastníka.
Začněme s prvním. Nejprve napíšeme test, který prověří, že po odeslání zprávy smart kontrakt uložil novou adresu do skladu. Všimneme si, že do zprávy kromě čítače a nové adresy přidáváme také akce 7bitové celé nezáporné číslo, podle kterého budeme vybírat, jak zpracovat zprávu ve smart kontraktu.
<b 0 32 u, 1 @ 7 new_owner_wc i, new_owner_account_id 256 b> zpráva_k_podepsání !V testu lze vidět, jak probíhá deserializace skladu smart kontraktu. úložiště V Fift. Deserializace proměnných je popsána v dokumentaci k Fift.
s přidáním testu.
Spouštíme test a ujistíme se, že selhává. Nyní přidáme logiku pro změnu adresy vlastníka loterie.
Ve smart kontraktu pokračujeme v parsování zpráva, čteme do akce. Připomeňme, že budeme mít dvě akce: změna adresy a odeslání gramů.
Potom načteme novou adresu vlastníka kontraktu a uložíme ji do úložiště.
Spouštíme testy a vidíme, že třetí test selhává. Selhává kvůli tomu, že kontrakt nyní navíc parsuje 7 bitů z zprávy, které v testu chybí. Přidáme do zprávy neexistující akce. Spustíme testy a vidíme, že všechny projdou. commit na změny. Skvěle.
Nyní napíšeme logiku pro odeslání uvedeného počtu gramů na dříve uloženou adresu.
Nejdříve napíšeme test. Napíšeme dva testy, jeden kdy dostatečný zůstatek chybí, druhý kdy by vše mělo proběhnout úspěšně. Testy si můžete prohlédnout .
Nyní doplníme kód. Nejprve napíšeme dvě pomocné metody. První get metodu pro zjištění aktuálního zůstatku smart kontraktu.
int balance() inline_ref method_id {
return get_balance().pair_first();
}A druhou pro odeslání gramů na jiný smart kontrakt. Tuto metodu jsem zcela zkopíroval z jiného smart kontraktu.
() 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 <= formát indikátoru int_msg_info$0
;; .store_uint(1, 1) ;; 1 <= ihr zakázáno
;; .store_uint(1, 1) ;; 1 <= bounce = true
;; .store_uint(0, 1) ;; 0 <= bounced = false
;; .store_uint(4, 5) ;; 00100 <= adresní flagy, anycast = false, 8-bit pracovní řetězec
.store_uint (196, 9)
.store_int(wc, 8)
.store_uint(addr, 256)
.store_grams(grams)
.store_uint(0, 107) ;; 106 nul + 0 jako indikátor, že neexistuje buňka s daty.
.end_cell();
send_raw_message(msg, 3); ;; mod, 2 pro ignorování chyb, 1 pro platbu poplatků ze strany odesílatele, 64 pro vracení hodnoty příchozí zprávy
}Přidáme tyto dvě metody do smart kontraktu a napíšeme logiku. Nejprve parsujeme množství gramů ze zprávy. Dále kontrolujeme zůstatek, pokud nestačí, vyhodíme výjimku. Pokud je vše v pořádku, odešleme gramů na uloženou adresu a aktualizujeme počítadlo.
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));vypadá smart kontrakt v tuto chvíli. Spustíme testy a ujistíme se, že probíhají.
Mimochodem, za zpracovanou zprávu může být u smart kontraktu každou chvíli stržena provize. Aby smart kontrakt vykonal požadavek na zprávě, je třeba po základních kontrolách vyvolat accept_message().
Nyní se zaměřme na vnitřní zprávy. V podstatě budeme pouze přijímat gramy a posílat zpět hráči dvojnásobnou částku při výhře a třetinu vlastníkovi při prohře.
Nejprve napíšeme jednoduchý test. K tomu budeme potřebovat testovací adresu smart kontraktu, ze které budeme jakoby posílat gramy na smart kontrakt.
Adresa smart kontraktu se skládá ze dvou čísel, 32bitové celé číslo odpovídá za workchain a 256bitové celé nezáporné číslo je unikátní číslo účtu v tomto workchainu. Například, -1 a 12345, tuto adresu uložíme do souboru.
Zkopíroval jsem funkci pro uložení adresy z .
// ( wc addr fname -- ) Save address to file in 36-byte format
{ -rot 256 u>B swap 32 i>B B+ swap B>file } : save-addressPojďme se podívat, jak funguje funkce, to nám poskytne pochopení, jak funguje Fift. Spustíme Fift v interaktivním režimu.
~\/TON\/build\/crypto\/fift -i Nejprve vložíme do zásobníku -1, 12345 a název budoucího souboru "sender.addr":
-1 12345 "sender.addr" Dalším krokem se vykoná funkce -rot, která posune zásobník tak, že na vrcholu zásobníku bude unikátní číslo smart kontraktu:
"sender.addr" -1 12345256 u>B převede 256bitové nezáporné celé číslo na bajty.
"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039swap prohodí dva horní prvky zásobníku.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -132 i>B převede 32bitové celé číslo na bajty.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFFB+ spojí dvě sekvence bajtů.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFFZnovu swap.
BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" A nakonec dojde k zápisu bajtů do souboru B>file. Po tomto je náš zásobník prázdný. Zastavujeme Fift. V aktuálním adresáři byl vytvořen soubor sender.addr. Přesuneme soubor do vytvořené složky test\/addresses\/.
Napíšeme jednoduchý test, který bude posílat gramy na smart kontrakt. .
Nyní se zaměříme na logiku loterie.
První, co uděláme, je zkontrolovat zprávu bounced nebo ne, pokud bounced, tak ignorujeme. bounced to znamená, že kontrakt vrátí gramy, pokud dojde k nějaké chybě. My gramy zpět nevrátíme, pokud by náhodou došlo k chybě.
Kontrolujeme zůstatek, pokud je menší než půl gramu, tak prostě přijmeme zprávu a ignorujeme.
Dále parsujeme adresu smart kontraktu, ze kterého přišla zpráva.
Čteme data ze skladu a poté odstraňujeme staré sázky z historie, pokud jich je více než dvacet. Pro snadnost jsem napsal tři další funkce. pack_order(), unpack_order(), remove_old_orders().
Dále se podíváme, pokud není dostatek prostředků na výplatu, tak to považujeme za doplnění, a ukládáme doplnění do orders.
Dále konečně podstata smart kontraktu.
Nejprve, pokud hráč prohraje, ukládáme ho do historie sázek a pokud je suma větší než 3 gramy, pošleme 1/3 majiteli smart kontraktu.
Pokud hráč vyhraje, posíláme dvojnásobnou částku na adresu hráče a dále ukládáme informace o sázce do historie.
() 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) { ;; ignorovat odražené
return ();
}
if (order_amount < 500000000) { ;; pouze přijmout gramy bez změny stavu
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() = 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));
}A to je vše. .
Teď zbývá jednoduché, uděláme get metody, aby se z vnějšího světa dala získat informace o stavu kontraktu (v podstatě číst data ze skladu smart kontraktu).
. O tom, jak získávat informace o smart kontraktu, napíšeme níže.
Také jsem zapomněl přidat kód, který zpracovává první požadavek, který se odehrává při publikaci chytrého kontraktu. . A také chybu s odesíláním 1/3 částky na účet vlastníka.
Teď už zbývá publikovat chytrý kontrakt. Vytvořme složku requests.
Základem jsem vzal kód publikace který v oficiálním repozitáři.
Co si zaslouží pozornost. Vytváříme úložiště chytrého kontraktu a vstupní zprávu. Poté se generuje adresa chytrého kontraktu, tedy adresa je známa ještě před publikací v TON. Poté je třeba na tuto adresu poslat několik gramů a teprve poté odeslat soubor se samotným chytrým kontraktem, protože za uchovávání chytrého kontraktu a operace v něm síť požaduje poplatek (validátoři, kteří uchovávají a provádějí chytré kontrakty). .
Nyní spouštíme kód publikace a dostáváme lottery-query.boc soubor a adresu chytrého kontraktu.
~\/TON\/build\/crypto\/fift -s requests\/new-lottery.fif 0Nezapomínejte uložit vygenerované soubory: lottery-query.boc, lottery.addr, lottery.pk.
Mezi jiným v logu provádění uvidíme adresu chytrého kontraktu.
new wallet address = 0:044910149dbeaf8eadbb2b28722e7d6a2dc6e264ec2f1d9bebd6fb209079bc2a
(Ukládání adresy do souboru lottery.addr)
Non-bounceable address (for init): 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd
Bounceable address (for later access): kQAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KpFYPro zajímavost provedeme dotaz do TON
$ .\/lite-client\/lite-client -C ton-lite-client-test1.config.json
getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydA uvidíme, že účet s touto adresou je prázdný.
account state is emptyPosíláme na adresu 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 Gramy a po několika sekundách provádíme stejný příkaz. Pro odeslání gramů používám , a testovací gramy si můžete vyžádat od někoho z chatu, o kterém budu mluvit na konci článku.
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydDívá se, že v síti vznikl neinitialized (state:account_uninit) chytrý kontrakt s touto adresou a zůstatkem 1 000 000 000 nanogramů.
stav účtu je (účet
addr:(addr_std
anycast:nothing workchain_id:0 adresa: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_}
poslední transakce lt = 3825478000001 hash = B043616AE016682699477FFF01E6E903878CDFD6846042BA1BFC64775E7AC6C4
zůstatek účtu je 2000000000ngNyní zveřejníme chytrý kontrakt. Spustíme lite-client a provedeme.
> sendfile lottery-query.boc
[ 1][t 2][1583008371.631410122][lite-client.cpp:966][!testnode] odesílání dotazu z souboru lottery-query.boc
[ 3][t 1][1583008371.828550100][lite-client.cpp:976][!query] stav externí zprávy je 1 Zkontrolujeme, že kontrakt byl zveřejněn.
> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydMezi jinými obdržíme.
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_activeVidíme, že account_active.
Odpovídající commit s změnami .
Nyní vytvoříme požadavky pro interakci s chytrým kontraktem.
Konkrétně první pro změnu adresy necháme jako samostatnou práci a druhý pro odeslání gramů na adresu vlastníka provedeme. V podstatě budeme potřebovat udělat to samé jako v testu na odesílání gramů.
Takovou zprávu budeme posílat na chytrý kontrakt, kde msg_seqno 165, akce 2 a 9.5 gramů k odeslání.
<b 165 32 u, 2 7 9500000000 gram, b>Nezapomeňte podepsat zprávu soukromým klíčem lottery.pk, který byl vygenerován dříve při vytváření chytrého kontraktu. .
Získáváme informace z chytrého kontraktu pomocí get metod
Nyní se podíváme, jak spouštět get metody chytrého kontraktu.
Spouštíme lite-client a spouštíme get metody, které jsme napsali.
$ .\/lite-client\/lite-client -C ton-lite-client-test1.config.json
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd balance
argumenty: [ 104128 ]
výsledek: [ 64633878952 ]
...V result obsahuje hodnotu, kterou vrací funkce balance() z našeho chytrého kontraktu.
Totéž provedeme ještě pro další metody.
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_seqno
...
argumenty: [ 77871 ]
výsledek: [ 1 ] Požádáme o historii sázek.
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_orders
...
argumenty: [ 67442 ]
výsledek: [ ([0 1 1583258284 10000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [1 3 1583258347 4000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [2 1 1583259901 50000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308]) ] Budeme používat lite-client a GET metody k zobrazení informací o smart kontraktu na webu.
Zobrazujeme data smart kontraktu na webu
Napsal jsem jednoduchou webovou stránku v Pythonu, abych zobrazil data ze smart kontraktu přehledně. Na něj se teď nebudu podrobně zastavovat a zveřejním stránku .
Žádosti k TON se provádějí z CouchDB prostřednictvím lite-client. Pro pohodlí je web zabalen do Dockeru a zveřejněn na Google Cloud. .
Zkoušíme
Teď zkusíme poslat gramy pro dobití z . Pošleme 40 gramů a uděláme pár sázek pro názornost. Vidíme, že web zobrazuje historii sázek, aktuální procento výhry a další užitečné informace.
, že první jsme vyhráli, druhou jsme prohráli.
Poznámka na závěr
Článek se ukázal být mnohem delší, než jsem předpokládal, možná by bylo možné se zkrátit, ale možná je to právě pro někoho, kdo nic neví o TON a chce napsat a zveřejnit ne zcela jednoduchý smart kontrakt s možností s ním interagovat. Možná některé věci by šly vysvětlit jednodušeji.
Možná některé aspekty realizace by šly provést efektivněji a elegantněji, ale v tom případě by příprava článku trvala ještě déle. Také možná, že jsem někde udělal chybu nebo něco nepochopil, proto pokud děláte něco vážného, je třeba se opírat o oficiální dokumentaci nebo oficiální repozitář s kódem TON.
Je třeba poznamenat, že jelikož je samotný TON stále ve fázi aktivního vývoje, mohou nastat změny, které rozbijí některé kroky v tomto článku (což se už stalo, zatímco jsem psal, už jsem to opravil), ale celkový přístup se pravděpodobně nezmění.
O budoucnosti TON se vyjadřovat nebudu. Možná se platforma stane něčím větším a stojí za to věnovat čas jejímu studiu a prosadit se na trhu s našimi produkty už nyní.
Je zde také Libra od Facebooku, která má potenciální publikum uživatelů větší než TON. O Libře toho moc nevím, podle fóra tam je mnohem více aktivity než ve společenství TON. Ačkoli vývojáři a komunita TON se více podobají undergroundu, což je také skvělé.
Odkazy
- Oficiální dokumentace k TON:
- Oficiální repozitář TON:
- Oficiální peněženka pro různé platformy:
- Repozitář smart kontraktu z tohoto článku:
- Odkaz na web smart kontraktu:
- Repozitář na rozšíření pro Visual Studio Code pro FunC:
- Chat o TONu na Telegramu, který mi velmi pomohl se na začátku zorientovat. Myslím, že nebude chybou říct, že tam jsou všichni, kdo něco napsali pro TON. Můžete také požádat o testovací gramy.
- Další chat o TONu, ve kterém jsem našel užitečné informace:
- První etapa soutěže:
- Druhá etapa soutěže:
Zdroj: habr.com
