Před více než rokem se objevily plány messengera Telegram na vydání vlastní decentralizované sítě. Telegram Open Network. V té době byl zveřejněn obsáhlý technický dokument, který pravděpodobně napsal Nikolaj Durov a popisoval strukturu budoucí sítě. Pro ty, kteří to propásli — doporučuji se seznámit s mým shrnutím tohoto dokumentu (, ; třetí část, bohužel, stále zůstává v konceptu).
Od té doby nebyly žádné významné novinky o stavu vývoje TON, až před pár dny (v jednom z ) se objevila odkaz na stránku , kde jsou umístěny:
◦ — zdrojové kódy lehkého klienta pro testovací síť TON;
◦ — konfigurační soubor pro připojení k testovací síti;
◦ — informace o sestavení a spuštění klienta;
◦ — podrobný návod na vytvoření chytrého kontraktu pomocí klienta;
◦ — aktualizovaný dokument (z 2. března 2019) s technickým přehledem sítě TON;
◦ — technický popis TVM (TON Virtual Machine, virtuální stroj TON);
◦ — technický popis blockchainu TON;
◦ — popis nového jazyka Fift, určeného pro vytváření chytrých kontraktů v TON.
Opakuji, oficiální potvrzení stránky a všech těchto dokumentů od Telegramu nebylo, ale objem těchto materiálů dává tomuto postavení dostatečnou věrohodnost. Spuštění publikovaného klienta provádějte na vlastní riziko.
Sestavení testovacího klienta
Nejprve se pokusíme sestavit a spustit testovacího klienta — naštěstí, podrobně popisuje tento nenáročný proces. Budu to dělat na příkladu macOS 10.14.5, úspěšnost sestavení na jiných systémech nemohu zaručit.
Stáhněte a rozbalte . Je důležité stahovat poslední verzi, protože zpětná kompatibilita v této fázi není zaručena.
Ujistěte se, že v systému jsou nainstalovány poslední verze make, cmake (verze 3.0.2 nebo vyšší), OpenSSL (včetně hlavičkových souborů C), g++ nebo clang. Nic dalšího jsem nenainstaloval, vše se sestavilo hned.
Předpokládejme, že se zdrojové kódy rozbalily do složky
~/lite-client. Odděleně od ní vytvořte prázdnou složku pro sestavený projekt (například,~/liteclient-build), a z ní (cd ~/liteclient-build) spustíme příkazy:cmake ~/lite-client cmake --build . --target test-lite-client
Pro sestavení interpretru jazyka Fift pro chytré kontrakty (o něm níže), také vyvolávámecmake --build . --target fiftStáhněte aktuální pro připojení k testovací síti a vložte ho do složky se sestaveným klientem.
Hotovo, můžete spustit klienta:
./test-lite-client -C ton-lite-client-test1.config.json
Pokud bylo vše uděláno správně, měli byste vidět něco takového:

Dostupných příkazů, jak vidíme, není mnoho:
◦ help — vypsat tento seznam příkazů;
◦ quit — vystoupit;
◦ čas — zobrazit aktuální čas na serveru;
◦ status — zobrazit stav připojení a místní databáze;
◦ poslední — aktualizovat stav blockchainu (stáhnout poslední blok). Tento příkaz je důležité vykonat před jakýmkoli dotazem, abyste si byli jisti, že vidíte aktuální stav sítě.
◦ odeslat soubor <filename> — nahrát místní soubor do sítě TON. Takto probíhá interakce se sítí — včetně například vytváření nových chytrých kontraktů a žádostí o převod prostředků mezi účty;
◦ získat účet — zobrazit aktuální (v okamžiku vykonání příkazu poslední) stav účtu s uvedenou adresou;
◦ privátní klíč <filename> — nahrát privátní klíč z místního souboru.
Pokud při spuštění klienta předáte složku pomocí možnosti -D, pak do ní uloží poslední blok masterchainu:
./test-lite-client -C ton-lite-client-test1.config.json -D ~/ton-db-dirNyní se můžeme přesunout k zajímavějším věcem — prozkoumat jazyk Fift, pokusit se zkompilovat chytrý kontrakt (například vytvořit testovanou peněženku), nahrát jej do sítě a vyzkoušet převod prostředků mezi účty.
Jazyk Fift
Z dokumentu se lze dozvědět, že pro vytváření chytrých kontraktů tým Telegramu vytvořil nový zásobníkový jazyk Fift (pravděpodobně od číslovky pátý, analogicky s Forth — jazykem, s nímž má Fift mnoho společného).
Dokument je dost obsáhlý, má 87 stran, a nebudu podrobně přepisovat jeho obsah v rámci tohoto článku (minimálně proto, že jsem ho sám nedokončil :) Zastavím se na hlavních bodech a uvedu pár příkladů kódu v tomto jazyce.
Na základní úrovni je syntaxe Fiftu dost jednoduchá: jeho kód se skládá z slov, obvykle oddělených mezerami nebo novými řádky (výjimečně: některá slova nevyžadují oddělovač po sobě). Jakékoli slovo — je závislá na velikosti znaková sekvence, které odpovídá nějaké definice (zjednodušeně řečeno, to, co má interpreter udělat, když se setká s tímto slovem). Pokud neexistuje definice slova, má interpreter tendenci parsovat ho jako číslo a vložit na zásobník. Mimochodem, čísla jsou zde — překvapivě — 257bitové celá čísla, a zlomkové čísla neexistují — přesněji, okamžitě se převádějí na pár celých čísel, které tvoří čitatel a jmenovatel racionálního zlomku.
Slova obvykle interagují s hodnotami, které se nacházejí na vrcholu zásobníku. Oddělený typ slov — prefixové — používá nejen zásobník, ale i následující znaky z původního souboru. Například takto jsou realizovány řetězcové literály — znak „uvozovky“ (") je prefixové slovo, které hledá následující (uzavírací) uvozovku a vloží řetězec mezi nimi na zásobník. Podobně se chovají jednořádkové (//) a vícerořádkové (/*) komentáře.
Na tomto téměř končí vnitřní struktura jazyka. Vše ostatní (včetně řídicích struktur) je definováno jako slova (buď interní, jako aritmetické operace a definice nových slov; nebo definovaná ve „standardní knihovně“ Fift.fif, která se nachází v složce crypto/fift v zdrojích).
Jednoduchý příklad programu v Fift:
{ dup =: x dup * =: y } : setxy
3 setxy x . y . x y + .
7 setxy x . y . x y + .V prvním řádku je definováno nové slovo setxy (všimněte si prefixu {, který vytváří blok do uzavíracího } a prefixu :, který vlastně definuje slovo). setxy vezme číslo z vrcholu zásobníku, definuje (nebo redefinuje) ho jako globální konstantu x, a čtverec tohoto čísla — jako konstantu y (s ohledem na to, že hodnoty konstant lze redefinovat, spíše bych je nazval proměnnými, ale následuji názvosloví v jazyce).
V následujících dvou řádcích je na zásobník umístěno číslo, je zavoláno setxy, poté jsou vypsány hodnoty konstant x, y (pro výstup se používá slovo .), obě konstanty jsou vloženy na zásobník, sčítají se a výsledek je také vypsán. Výsledkem bude:
3 9 12 ok
7 49 56 ok(Řetězec „ok“ vypisuje interpreter, když končí zpracování aktuálního řádku v interaktivním vstupním režimu)
A tady je kompletní příklad kódu:
"Asm.fif" zahrnuje
-1 konstantní wc // vytvořte peněženku v pracovním řetězci -1 (hlavní řetězec)
// Vytvořte novou jednoduchou peněženku
<{ SETCP0 DUP IFNOTRET INC 32 THROWIF // vraťte se, pokud je recv_internal, selhání, pokud je recv_external
512 INT LDSLICEX DUP 32 PLDU // podepište cs cnt
c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // podepište cs cnt cnt' pubk
s1 s2 XCPU // podepište cs cnt pubk cnt' cnt
EQUAL 33 THROWIFNOT // ( nesoulad seqno? )
s2 PUSH HASHSU // podepište cs cnt pubk hash
s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk
CHKSIGNU // pubk cs cnt ?
34 THROWIFNOT // nesoulad s podpisem
ACCEPT
SWAP 32 LDU NIP
DUP SREFS IF:<{
8 LDU LDREF // pubk cnt mode msg cs
s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent )
}>
ENDS
INC NEWC 32 STU 256 STU ENDC c4 POPCTR
}>c
// kód
<b 0 32 u, newkeypair swap dup constant wallet_pk "new-wallet.pk" b>soubor
B,
b> // data
// žádné knihovny
<b b{00110} s, rot ref, swap b> // create StateInit
dup ."StateInit: " <s csr. cr
dup hash dup constant wallet_addr
."new wallet address = " wc . .": " x. cr
wc over 7 smca>$ typ cr
256 u>B "nová-peněženka.addr" B>file
<b 0 32 u, b>
dup ."podepisování zprávy: " <s csr. cr
dup hash wallet_pk ed25519_sign_uint rot
<b b{1000100} s, wc 8 i, wallet_addr 256 u, b{000010} swap <s b{0} b, b>
dup ."Externí zpráva pro inicializaci je " <s csr. cr
2 boc+>B dup Bx. cr
"nová-peněženka-dotaz.boc" tuck B>file
."(Uloženo do souboru " typ .")" crTento na pohled děsivý soubor je určen k vytvoření chytrého kontraktu — bude umístěn do souboru new-wallet-query.boc po vykonání. Upozorňuji, že zde je také použit další assembler pro TON Virtual Machine (na kterém se nebudu podrobně zastavovat), jehož instrukce budou vloženy do blockchainu.
Takže assembler pro TVM je napsán v Fift – zdrojové kódy tohoto assembleru jsou v souboru crypto/fift/Asm.fif a jsou připojeny na začátku výše uvedeného úryvku kódu.
Co mohu říct, evidentně Nikolaj Durov má prostě rád vytváření nových programovacích jazyků 🙂
Vytvoření chytrého kontraktu a interakce s TON
Předpokládejme tedy, že jsme sestavili klienta TON a interpret Fift, jak bylo popsáno výše, a seznámili se s jazykem. Jak nyní vytvořit chytrý kontrakt? O tom vypráví soubor , připojený k zdrojovým kódům.
Účty v TON
Jak jsem popisoval v , tato síť obsahuje více než jeden blockchain – je zde jeden společný, tzv. "masterchain", a také libovolný počet dalších "workchainů", identifikovaných 32-bitovým číslem. Masterchain má identifikátor -1, kromě něj může být také použit "základní" workchain s identifikátorem 0. Každý workchain může mít svou vlastní konfiguraci. Vnitřně je každý workchain rozdělen na shardchainy, ale to již je detail implementace, který není nutné mít na paměti.
V rámci jednoho workchainu je uloženo mnoho účtů, které mají své identifikátory account_id. Pro masterchain a nulový workchain mají délku 256 bitů. Takže identifikátor účtu se zapisuje například takto:
-1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0dTo je "neutrální" formát: nejprve identifikátor workchainu, poté dvojtečka, a identifikátor účtu v šestnáctkové podobě.
Kromě toho existuje zkrácený formát – číslo workchainu a adresa účtu jsou kódovány v binárním formátu, k nim je přidán kontrolní součet a to všechno je kódováno do Base64:
Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkbPokud známe tento formát zápisu, můžeme požádat o aktuální stav nějakého účtu prostřednictvím testovacího klienta pomocí příkazu
getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0dObdržíme přibližně takovou odpověď:
[ 3][t 2][1558746708.815218925][test-lite-client.cpp:631][!testnode] požadavek na stav účtu pro -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D
[ 3][t 2][1558746708.858564138][test-lite-client.cpp:652][!testnode] obdržel stav účtu pro -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D s ohledem na bloky (-1,8000000000000000,72355):F566005749C1B97F18EDE013EBA7A054B9014961BC1AD91F475B9082919A2296:1BD5DE54333164025EE39D389ECE2E93DA2871DA616D488253953E52B50DC03F a (-1,8000000000000000,72355):F566005749C1B97F18EDE013EBA7A054B9014961BC1AD91F475B9082919A2296:1BD5DE54333164025EE39D389ECE2E93DA2871DA616D488253953E52B50DC03F
stav účtu je (účet
addr:(addr_std
anycast:nothing workchain_id:-1 adresa:x8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D)
storage_stat:(storage_info
used:(storage_used
cells:(var_uint len:1 value:3)
bits:(var_uint len:2 value:539)
public_cells:(var_uint len:0 value:0)) poslední_zaplaceno:0
dlužná platba:nothing)
storage:(account_storage poslední_trans_lt:74208000003
balance:(currencies
gramů:(nanograms
amount:(var_uint len:7 value:999928362430000))
ostatní:(extra_currencies
dict:hme_empty))
state:(account_active
(
split_depth:nothing
special:nothing
code:(just
value:(raw@^Cell
x{}
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
))
data:(just
value:(raw@^Cell
x{}
x{0000000D}
))
library:hme_empty))))
x{CFF8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D2068086C000000000000000451C90E00DC0E35B7DB5FB8C134_}
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
x{0000000D}Vidíme strukturu, která se ukládá v DHT daného pracovního řetězce. Například v poli storage.balance se nachází aktuální zůstatek účtu, v storage.state.code — kód smart kontraktu, a v storage.state.data — jeho aktuální data. Všimněte si, že úložiště dat TON — Cell, buňky — je stromová struktura, každá buňka může mít jak svá vlastní data, tak podřízené buňky. To je znázorněno odsazením v posledních řádcích.
Sestavení smart kontraktu
Teď si vytvoříme takovou strukturu (nazývá se BOC — bag of cells) pomocí jazyka Fift. Naštěstí nebude nutné psát smart kontrakt od nuly — ve složce crypto/block z archivu se zdrojovými kódy je soubor new-wallet.fif, který nám pomůže vytvořit nový peněženku. Zkopírujeme ho do složky se sestaveným klientem (~/liteclient-build, pokud jste postupovali podle předchozího návodu). Jeho obsah jsem uvedl výše jako příklad kódu v jazyce Fift.
Tento soubor vykonáme následujícím způsobem:
./crypto/fift -I"/crypto/fift" new-wallet.fifZde <source-directory> je třeba nahradit cestou k rozbaleným zdrojům (symbol „~“ zde, bohužel, nelze použít, potřebujeme úplnou cestu). Místo použití klíče -I můžete definovat proměnnou prostředí FIFTPATH a umístit tuto cestu do ní.
Protože Fift jsme spustili s názvem souboru new-wallet.fif, vykoná ho a skončí. Pokud název souboru vynecháte, můžete si hrát s interpreterem v interaktivním režimu.
V konzoli po vykonání by mělo být vypsáno něco takového:
StateInit: x{34_}
x{FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
x{0000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B}
nová adresa peněženky = -1 : 4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2
0f9PzVILj8yglrVn1zS-NSjtxr7QBfaTCp7JrBqnFPIR8nhZ
signování zprávy: x{00000000}
Externí zpráva pro inicializaci je x{89FEE120E20C7E953E31546F64C23CD654002C1AA919ADD24DB12DDF85C6F3B58AE41198A28AD8DAF3B9588E7A629252BA3DB88F030D00BC1016110B2073359EAC3C13823C53245B65D056F2C070B940CDA09789585935C7ABA4D2AD4BED139281CFA1200000001_}
x{FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
x{0000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B}
B5EE9C724104030100000000D60002CF89FEE120E20C7E953E31546F64C23CD654002C1AA919ADD24DB12DDF85C6F3B58AE41198A28AD8DAF3B9588E7A629252BA3DB88F030D00BC1016110B2073359EAC3C13823C53245B65D056F2C070B940CDA09789585935C7ABA4D2AD4BED139281CFA1200000001001020084FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED5400480000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B6290698B
(Uloženo do souboru new-wallet-query.boc)To znamená, že peněženka s identifikátorem -1:4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2 (nebo, co je totéž, 0f9PzVILj8yglrVn1zS-NSjtxr7QBfaTCp7JrBqnFPIR8nhZ) byla úspěšně vytvořena. Příslušný kód se ocitne v souboru new-wallet-query.boc, jeho adresa bude v new-wallet.addr, a privátní klíč bude v new-wallet.pk (buďte opatrní — opětovné spuštění skriptu tyto soubory přepíše).
Samozřejmě, síť TON o této peněžence zatím neví, je uložena pouze v podobě těchto souborů. Nyní je ji potřeba nahrát do sítě. Problém je v tom, že pro vytvoření smart kontraktu je třeba zaplatit poplatek, zatímco zůstatek na vašem účtu je nulový.
V pracovním režimu se tento problém vyřeší nákupem gramů na burze (nebo převodem z jiné peněženky). V současném testovacím režimu byl zaveden speciální smart kontrakt, od kterého lze požádat o 20 gramů zcela zdarma.
Vytváření žádosti k cizímu smart kontraktu
Žádost k smart kontraktu, který rozdává gramy vlevo a vpravo, provedeme takto. Ve stejné složce crypto/block najdeme soubor testgiver.fif:
// "testgiver.addr" file>B 256 B>u@
0x8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
dup constant wallet_addr ."Test giver address = " x. cr
0x4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2
constant dest_addr
-1 constant wc
0x00000011 constant seqno
1000000000 constant Gram
{ Gram swap */ } : Gram*/
6.666 Gram*/ constant amount
// b x --> b' ( serializes a Gram amount )
{ -1 { 1+ 2dup 8 * ufits } until
rot over 4 u, -rot 8 * u, } : Gram,
// create a message (NB: 01b00.., b = bounce)
<b b{010000100} s, wc 8 i, dest_addr 256 u, amount gram, 0 9 64 32 + 1+ "gift" $, b>
<b seqno 32 u, 1 8 swap ref, b>
dup ."obalující zpráva: " <s csr. cr
<b b{1000100} s, wc 8 i, wallet_addr 256 u, 0 gram, b{00} s,
swap <s b>
dup ."výsledná externí zpráva: " <s csr. cr
2 boc+>B dup Bx. cr
"wallet-query.boc" B>souborTen také uložíme do složky se sestaveným klientem, ale upravíme pátý řádek — před řádkem „constant dest_addr„. Vyměníme jej za adresu té peněženky, kterou jste vytvořili dříve (celou, ne zkrácenou). „-1:“ na začátku psát není třeba, místo toho na začátku dejte „0x“.
Ještě je možné změnit řádek 6.666 Gram* / konstantní množství — je to množství v gramech, které požadujete (ne více než 20). I když uvedete celé číslo, ponechte desetinnou čárku.
Nakonec je nutné upravit řetězec 0x00000011 konstantní seqno. První číslo zde je aktuální číslo sekvence, které je uloženo v účtu vydávajícím gramy. Odkud ho vzít? Jak bylo zmíněno výše, spusťte klienta a proveďte:
last
getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0dNa konci dat chytrého kontraktu bude
...
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
x{0000000D}Číslo 0000000D (u vás to bude větší) je číslo sekvence, které je třeba nahradit v testgiver.fif.
Všechno, uložíme soubor a spustíme (./crypto/fift testgiver.fif). Na výstupu dostaneme soubor wallet-query.boc. To je vytvořené prohlášení k cizímu chytrému kontraktu — žádost „převeď tolik gramů na takový účet“.
Pomocí klienta ho načteme do sítě:
> sendfile wallet-query.boc
[ 1][t 1][1558747399.456575155][test-lite-client.cpp:577][!testnode] posílání dotazu ze souboru wallet-query.boc
[ 3][t 2][1558747399.500236034][test-lite-client.cpp:587][!query] externí stav zprávy je 1Pokud nyní zavoláme poslední, a pak znovu požádáme o stav účtu, na který jsme požádali o gramy, měli bychom vidět, že číslo sekvence se zvýšilo o jednu jednotku — to znamená, že poslal peníze na náš účet.
Zůstává poslední krok — načteme kód naší peněženky (jeho zůstatek je již navýšen, ale bez kódu chytrého kontraktu s ním nebudeme moci manipulovat). Provádíme sendfile new-wallet-query.boc — a je to, máte vlastní peněženku v síti TON (i když zatím jen testovací).
Vytváření odchozích transakcí
Aby bylo možné převádět peníze ze zůstatku vytvořeného účtu, existuje soubor crypto/block/wallet.fif, který také musíte umístit do složky se sestaveným klientem.
Podobně jako v předchozích krocích je třeba upravit částku, kterou převádíte, adresu příjemce (dest_addr) a seqno vaší peněženky (rovná se 1 po inicializaci peněženky a zvyšuje se o 1 po každé odchozí transakci — budete ho moci vidět, pokud požádáte o stav vašeho účtu). Pro testy můžete použít například mou peněženku — 0x4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2.
Při spuštění (./crypto/fift wallet.fif) skript vezme adresu vaší peněženky (odkud převádíte) a její soukromý klíč ze souborů new-wallet.addr a new-wallet.pk, a obdrženou zprávu zapíše do new-wallet-query.boc.
Jak dříve, abychom transakci skutečně provedli, voláme sendfile new-wallet-query.boc v klientovi. Po tom nezapomeňte aktualizovat stav blockchainu (poslední) a zkontrolujte, že se změnily zůstatek a seqno naší peněženky (getaccount).

A je to, nyní umíme vytvářet chytré kontrakty v TON a posílat k nim dotazy. Jak vidíme, současná funkčnost je už dostatečná k tomu, abychom například vytvořili přívětivější peněženku s grafickým rozhraním (předpokládá se, že to bude také k dispozici jako součást messengeru).
Pouze registrovaní uživatelé se mohou účastnit ankety. , prosím.
Máte zájem o pokračování článků s analýzou TON, TVM, Fift?
Ano, čekám na dokončení cyklu článků s obecným přehledem TON
Ano, zajímá mě podrobněji si přečíst o jazyce Fift
Ano, chci se dozvědět více o TON Virtual Machine a assembleru pro něj
Ne, nic z toho mě nezajímá
Hlasovalo 39 uživatelů. Zdrželo se 12 uživatelů.
Jak se díváte na plány Telegramu ohledně spuštění TON?
Vkládám do tohoto projektu velké naděje
Jednoduše sleduji jeho vývoj s zájmem
Jsem skeptický, pochybuji o jeho úspěchu
Myslím, že tato iniciativa je neúspěšná a nepotřebná pro širokou veřejnost
Hlasovalo 47 uživatelů. Zdrželo se 12 uživatelů.
Zdroj: habr.com

