Jak napisać i opublikować inteligentny kontrakt w Telegram Open Network (TON)

Jak napisać i opublikować smart kontrakt w TON

O czym jest ten artykuł?

W artykule opowiem o tym, jak wziąłem udział w pierwszym (z dwóch) konkursie Telegramu poświęconym blockchainowi, nie zdobyłem nagrody i postanowiłem spisać swoje doświadczenie w artykule, aby nie zniknęło ono w przeszłości i być może pomogło komuś innemu.

Ponieważ nie chciałem pisać abstrakcyjnego kodu, a zrobić coś funkcjonalnego, napisałem na potrzeby artykułu smart kontrakt loterii oraz stronę, która wyświetla dane smart kontraktu bezpośrednio z TON bez użycia pośrednich magazynów.

Artykuł będzie przydatny dla tych, którzy chcą stworzyć swój pierwszy smart kontrakt w TON, ale nie wiedzą, od czego zacząć.

Na przykładzie loterii przeprowadzę od instalacji środowiska do publikacji smart kontraktu, interakcji z nim oraz napiszę stronę do odbierania i publikacji danych.

O udziale w konkursie

W październiku zeszłego roku Telegram ogłosił konkurs na blockchain z nowymi językami (prawdopodobnie od liczby i FunC. Należało na wybór napisać dowolny z pięciu zaproponowanych smart kontraktów. Uznałem, że dobrze byłoby zająć się czymś nietypowym, poznać ten język i stworzyć cokolwiek, nawet jeśli w przyszłości nie będę musiał pisać niczego więcej. Ponadto temat był ciągle na topie.

Warto powiedzieć, że nie miałem doświadczenia w tworzeniu smart kontraktów.

Planowałem uczestniczyć do samego końca, dopóki mogłem, a potem napisać artykuł przeglądowy, ale od razu zawiodłem na pierwszym etapie. Napisałem portfel z wieloma podpisami na FunC i generalnie działał. Jako podstawę wziąłem smart kontrakt w Solidity.

W tamtym momencie uznałem, że to z pewnością wystarczające, aby zająć chociaż jakieś miejsce na podium. Ostatecznie około 40 z 60 uczestników zostało laureatami i nie było mnie wśród nich. W sumie to nic strasznego, ale zaniepokoiła mnie jedna rzecz. W momencie ogłoszenia wyników recenzja z testem mojego kontraktu nie została wykonana, zapytałem uczestników na czacie, czy jest jeszcze ktoś, kto go nie ma, ale takich nie było.

Wygląda na to, że zwracając uwagę na moje wiadomości, dwa dni później sędziowie opublikowali komentarz i tak nigdy nie zrozumiałem, czy przypadkowo pominęli mój smart kontrakt podczas oceniania, czy po prostu uznali, że jest na tyle zły, że nie wymaga komentarza. Zadałem pytanie na stronie, ale nie otrzymałem odpowiedzi. Mimo że kto sędziował — nie jest tajemnicą, uznałem pisanie prywatnych wiadomości za zbędne.

Czas poświęcony na zrozumienie nie był mały, dlatego zdecydowano się napisać artykuł. Ponieważ informacji na razie jest niewiele, artykuł ten pomoże zaoszczędzić czas wszystkim zainteresowanym.

Koncepcja działania smart kontraktów w TON

Zanim zaczniemy coś pisać, trzeba dowiedzieć się, jak do tego podejść. Dlatego teraz opowiem, z jakich części składa się system. A dokładniej, jakie części trzeba znać, aby napisać przynajmniej jakiś działający kontrakt.

Skupimy się na pisaniu smart kontraktu oraz pracy z TON Virtual Machine (TVM), (prawdopodobnie od liczby i FunC, dlatego artykuł bardziej przypomina opis tworzenia zwykłego programu. Nie będziemy się zatrzymywać na tym, jak działa sama platforma.

Ogólnie, na temat tego, jak działa TVM i język (prawdopodobnie od liczby istnieje dobra oficjalna dokumentacja. W trakcie uczestnictwa w konkursie oraz podczas pisania bieżącego kontraktu często się na nią powoływałem.

Głównym językiem, w którym pisane są smart kontrakty — FunC. Na chwilę obecną nie ma dokumentacji w tym zakresie, dlatego aby napisać coś, należy zapoznać się z przykładami smart kontraktów z oficjalnego repozytorium, jak również z samą implementacją języka, a także można zobaczyć przykłady smart kontraktów z dwóch ostatnich konkursów. Linki na końcu artykułu.

Załóżmy, że już napisaliśmy smart kontrakt na FunC, następnie kompilujemy kod do assemblera Fift.

Skompilowany smart kontrakt musi być opublikowany. W tym celu należy napisać funkcję w (prawdopodobnie od liczby, która będzie przyjmować kod smart kontraktu oraz inne parametry, a na wyjściu otrzymamy plik z rozszerzeniem .boc (co oznacza "bag of cells"), a w zależności od tego, jak napiszemy, klucz prywatny oraz adres, który jest generowany na podstawie kodu smart kontraktu. Na adres smart kontraktu, który jeszcze nie został opublikowany, można już wysyłać gramów.

Aby opublikować smart kontrakt w TON, otrzymany .boc plik należy wysłać do blockchainu za pomocą lekkiego klienta (o czym poniżej). Ale przed publikacją należy przelać gramów na wygenerowany adres, w przeciwnym razie smart kontrakt nie zostanie opublikowany. Po publikacji można interactować z smart kontraktem, wysyłając mu wiadomości z zewnątrz (na przykład za pomocą lekkiego klienta) lub z wewnątrz (na przykład jeden smart kontrakt wysyła drugiemu wiadomość wewnątrz TON).

Po tym, jak zrozumieliśmy, jak publikowany jest kod, dalsza część staje się łatwiejsza. Mniej więcej wiemy, co chcemy napisać i jak nasz program będzie działał. Podczas pisania szukamy, jak to zostało już zrealizowane w istniejących inteligentnych kontraktach, albo zaglądamy do kodu implementacji. (prawdopodobnie od liczby i FunC w oficjalnym repozytorium, albo zaglądamy do oficjalnej dokumentacji.

Bardzo często szukałem według słów kluczowych na czacie Telegram, gdzie zebrali się wszyscy uczestnicy konkursu, w tym pracownicy Telegram. Tak się złożyło, że podczas konkursu wszyscy zebrali się właśnie tam i zaczęli dyskutować o Fift i FunC. Link na końcu artykułu.

Czas przejść od teorii do praktyki.

Przygotowanie środowiska do pracy z TON.

Wszystko, co zostanie opisane w artykule, robiłem na MacOS i sprawdziłem na czystym Ubuntu 18.04 LTS na Dockerze.

Pierwszą rzeczą, którą trzeba zrobić, to pobrać i zainstalować. lite-client, za pomocą którego można wysyłać zapytania do TON.

Instrukcja na oficjalnej stronie bardzo szczegółowo i jasno opisuje proces instalacji, pomijając pewne szczegóły. Tutaj postępujemy zgodnie z instrukcją, równocześnie instalując brakujące zależności. Nie zdecydowałem się samodzielnie kompilować każdego projektu i zainstalowałem z oficjalnego repozytorium Ubuntu (na MacOS użyłem. 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. 

Po zainstalowaniu wszystkich zależności można zainstalować. lite-client,, (prawdopodobnie od liczby, FunC.

Najpierw klonujemy repozytorium TON wraz z zależnościami. Dla wygody wszystko będziemy robić w folderze. ~\/TON..

cd ~\/TON
git clone https:\/\/github.com\/ton-blockchain\/ton.git
cd .\/ton
git submodule update --init --recursive.

W repozytorium przechowywane są również implementacje. (prawdopodobnie od liczby i FunC.

Teraz jesteśmy gotowi do zbudowania projektu. Kod repozytorium został sklonowany do folderu. ~\/TON\/ton.. W ~\/TON. Tworzymy folder. build i kompilujemy w nim projekt.

mkdir ~\/TON\/build 
cd ~\/TON\/build
cmake ..\/ton.

Ponieważ zamierzamy pisać inteligentny kontrakt potrzebujemy nie tylko. lite-client,, ale także (prawdopodobnie od liczby z FunC, dlatego kompilujemy wszystko. To nie jest szybki proces, więc czekamy.

cmake --build . --target lite-client
cmake --build . --target fift
cmake --build . --target func.

Następnie pobieramy plik konfiguracyjny, w którym znajdują się dane o węźle, do którego. lite-client, będzie się łączyć.

wget https://test.ton.org/ton-lite-client-test1.config.json

Wykonujemy pierwsze zapytania do TON.

Teraz uruchomimy. lite-client,.

cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json

Jeśli budowa przebiegła pomyślnie, po uruchomieniu zobaczysz log połączenia klienta lite z węzłem.

[ 1][t 2][1582054822.963129282][lite-client.h:201][!testnode]   conn ready
[ 2][t 2][1582054823.085654020][lite-client.cpp:277][!testnode] version serwera to 1.1, możliwości 7
[ 3][t 2][1582054823.085725069][lite-client.cpp:286][!testnode] czas serwera to 1582054823 (delta 0)
...

Można wykonać polecenie help i sprawdzić, jakie polecenia są dostępne.

help

Wymieńmy polecenia, które będziemy używać w tym artykule.

lista dostępnych poleceń:
last    Pobierz ostatni blok i informacje o stanie z serwera
sendfile  Załaduj zserializowaną wiadomość z  i wyślij ją do serwera
getaccount  []  Ładuje najnowszy stan określonego konta;  jest w formacie []:
runmethod  []  ...   Uruchamia metodę GET  konta  z określonymi parametrami

last pobiera ostatnio utworzony blok z serwera.
sendfile  wysyła do TON plik z wiadomością, właśnie za pomocą tej komendy publikowany jest smart kontrakt i zapytania do niego.
getaccount  ładuje aktualny stan smart kontraktu z podanym adresem.
runmethod  []   uruchamia metody GET smart kontraktu. 

Teraz jesteśmy gotowi do napisania samego kontraktu.

Realizacja

Idea

Jak już wspomniałem, smart kontrakt, który piszemy, to loteria.

Przy czym nie jest to loteria, w której trzeba kupić bilet i czekać godzinę, dzień lub miesiąc, a natychmiastowa, w której użytkownik przekazuje na adres kontraktu N gramy, i natychmiast otrzymuje z powrotem 2 * N gramów lub przegrywa. Szansę na wygraną ustalimy na około 40%. Jeśli gramów na wypłatę jest niewystarczająco, będziemy uważać transakcję za doładowanie.

Przy czym ważne jest, aby zakłady można było widzieć w czasie rzeczywistym i w wygodnej formie, aby użytkownik mógł od razu zrozumieć, czy wygrał, czy przegrał. Dlatego konieczne jest stworzenie strony internetowej, która pokaże zakłady i wyniki bezpośrednio z TON.

Pisanie smart kontraktu

Aby ułatwić sobie życie, stworzyłem podświetlenie kodu dla FunC, wtyczkę można znaleźć i zainstalować w wyszukiwarce Visual Studio Code, jeśli ktoś zechce dodać coś, udostępniłem wtyczkę w otwartym dostępie. Również wcześniej ktoś zrobił wtyczkę do pracy z Fift, którą też można znaleźć i zainstalować w VSC.

Od razu stworzymy repozytorium, do którego będziemy commitować pośrednie wyniki.

Aby ułatwić sobie życie, będziemy pisać smart kontrakt i testować lokalnie, aż będzie gotowy. Dopiero potem opublikujemy go w TON.

Smart kontrakt ma dwie zewnętrzne metody, do których można się odwoływać. Pierwsza, recv_external() ta funkcja jest wykonywana, gdy żądanie do kontraktu pochodzi ze świata zewnętrznego, to znaczy nie z TON, na przykład gdy sami formujemy wiadomość i wysyłamy ją przez lite-client. Druga, recv_internal() to jest wtedy, gdy wewnętrzny kontrakt TON odnosi się do naszego. W obu przypadkach można przekazać parametry do funkcji.

Zacznijmy od prostego przykładu, który będzie działał, jeśli go opublikujemy, ale nie ma w nim żadnego funkcjonalnego obciążenia.

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

() recv_external(slice in_msg) impure {
    ;; TODO: implementacja  
}

Tutaj trzeba wyjaśnić, co to jest slice. Wszystkie przechowywane dane w TON Blockchain to kolekcja komórka TVM lub po prostu komórka, w takiej komórce można przechowywać do 1023 bitów danych i do 4 odniesień do innych komórek.

slice komórki TVM lub slice to część istniejącej komórka jest używana do jej parsowania, dalej będzie jasne. Najważniejsze dla nas, że do smart kontraktu możemy przekazać slice a w zależności od rodzaju wiadomości przetworzyć dane w recv_external() lub recv_internal().

impure — kluczowe słowo, które wskazuje, że funkcja zmienia dane smart kontraktu.

Zachowamy kod kontraktu w lottery-code.fc i skompilujemy.

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

Wartość flag można sprawdzić za pomocą polecenia

~\/TON\/build\/crypto\/func -help

Uzyskaliśmy skompilowany kod assemblera Fift w 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

Można go uruchomić lokalnie, w tym celu przygotujemy środowisko.

Zauważmy, że pierwsza linijka ładuje Asm.fif, to kod napisany w Fift dla assemblera Fift.

Ponieważ chcemy uruchomić i testować smart kontrakt lokalnie, stworzymy plik lottery-test-suite.fif i skopiujemy tam skompilowany kod, zastępując ostatnią linijkę, która zapisuje kod smart kontraktu w stałej code, aby potem przekazać go do maszyny wirtualnej:

"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

Na razie wszystko wygląda jasno, teraz dodamy do tego samego pliku kod, który będziemy używać do uruchamiania TVM.

0 tuple 0x076ef1ea , \/\/ magic
0 , 0 , \/\/ działania msg_sents
1570998536 , \/\/ unix_time
1 , 1 , 3 , \/\/ block_lt, trans_lt, rand_seed
0 tuple 100000000000000 , dictnew , , \/\/ saldo pozostałe
0 , dictnew , \/\/ contract_address, global_config
1 tuple \/\/ opakować w inną krotkę
constant c7

0 constant recv_internal \/\/ aby uruchomić recv_internal() 
-1 constant recv_external \/\/ aby wywołać recv_external()

W c7 zapisujemy kontekst, czyli dane, z którymi będzie uruchamiana TVM (lub stan sieci). Jeszcze podczas konkursu jeden z programistów pokazał, jak to się tworzy c7 i skopiowałem. W tym artykule być może będziemy musieli zmienić rand_seed ponieważ od tego zależy generowanie losowej liczby i nie zmieniając jego wartości, za każdym razem będzie zwracana ta sama liczba.

recv_internal i recv_external stałe o wartościach 0 i -1 będą odpowiadać za wywołanie odpowiednich funkcji w smart kontrakcie.

Teraz jesteśmy gotowi stworzyć pierwszy test naszego pustego smart kontraktu. Dla przejrzystości na razie wszystkie testy dodamy do tego samego pliku. lottery-test-suite.fif.

Stworzymy zmienną magazyn i zapiszemy w niej pusty komórka, to będzie przechowalnia smart kontraktu.

message to wiadomość, którą przekażemy smart kontraktowi z zewnątrz. Na razie również zrobimy ją pustą.

przechowywanie zmiennych 
<b b> przechowywanie ! 

wiadomość zmienna 
<b b> wiadomość ! 

Po przygotowaniu stałych i zmiennych uruchamiamy TVM za pomocą polecenia runvmctx i przekazujemy stworzone parametry jako dane wejściowe.

message @ 
recv_external 
code 
storage @ 
c7 
runvmctx 

W rezultacie otrzymamy taki pośredni kod w (prawdopodobnie od liczby.

Teraz możemy uruchomić otrzymany kod.

export FIFTPATH=~\/TON\/ton\/crypto\/fift\/lib \/\/ wykonujemy raz dla wygody 
~\/TON\/build\/crypto\/fift -s lottery-test-suite.fif 

Program powinien działać bez błędów, a w wyjściu zobaczymy log wykonania:

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

Świetnie, napisaliśmy pierwszy działający wariant smart kontraktu.

Teraz musimy dodać funkcjonalność. Na początku zajmiemy się wiadomościami, które przychodzą z zewnętrznego świata do recv_external()

Programista sam wybiera format wiadomości, które kontrakt może przyjąć.

Ale zazwyczaj,

  • po pierwsze, chcemy zabezpieczyć nasz kontrakt przed zewnętrznym światem i uczynić tak, aby tylko właściciel kontraktu mógł wysyłać do niego wiadomości z zewnątrz.
  • po drugie, gdy wysyłamy ważną wiadomość do TON, chcemy, aby wydarzyło się to dokładnie raz, a podczas ponownej wysyłki tej samej wiadomości smart kontrakt ją odrzucał.

Dlatego w prawie każdym kontrakcie rozwiązuje się te dwa problemy, ponieważ nasz kontrakt akceptuje wiadomości z zewnątrz, musimy się tym również zająć.

Zrobimy to w odwrotnej kolejności. Najpierw rozwiążemy problem z powtórkami, jeśli kontrakt już otrzymał taką wiadomość i ją przetworzył, nie wykona jej ponownie. A potem rozwiążemy problem, aby tylko określona grupa osób mogła wysyłać wiadomości do smart kontraktu.

Istnieje wiele sposobów na rozwiązanie problemu z powtarzającymi się wiadomościami. Zrobimy to tak: w inteligentnym kontrakcie zainicjujemy licznik odebranych wiadomości z początkową wartością 0. Do każdej wiadomości dodamy aktualną wartość licznika. Jeśli wartość licznika w wiadomości nie pasuje do wartości w kontrakcie, nie przetwarzamy jej; jeśli pasuje, przetwarzamy i zwiększamy licznik w kontrakcie o 1.

Powracamy do lottery-test-suite.fif i dodajemy drugi test. Wyślemy niewłaściwy numer, kod powinien zgłosić wyjątek. Na przykład, niech w danych kontraktu będzie przechowywana wartość 166, a my wyślemy 165.

<b 166 32 u, b> magazyn !
<b 165 32 u, b> wiadomość !

wiadomość @ 
recv_external 
code 
magazyn @ 
c7 
runvmctx

drop 
exit_code ! 
."Kod wyjścia " exit_code @ . cr 
exit_code @ 33 - abort"Test #2 niezaliczony"

Uruchamiamy.

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

I zobaczymy, że test kończy się błędem.

[ 1][t 0][1582283084.210902214][words.cpp:3046] lottery-test-suite.fif:67: abort": Test #2 Nie przeszedł
[ 1][t 0][1582283084.210941076][fift-main.cpp:196]      Błąd w interpretacji pliku `lottery-test-suite.fif`: błąd w interpretacji dołączonego pliku `lottery-test-suite.fif` : lottery-test-suite.fif:67: abort": Test #2 Nie przeszedł

Na tym etapie lottery-test-suite.fif powinien wyglądać jak pod tym linkiem.

Teraz dodajmy logikę licznika do inteligentnego kontraktu w lottery-code.fc.

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

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

W slice in_msg zawiera wiadomość, którą wysyłamy.

Pierwsze, co robimy, to sprawdzamy, czy wiadomość zawiera dane; jeśli nie, po prostu wychodzimy.

Następnie parsujemy wiadomość. in_msg~load_uint(32) ładuje liczbę 165, 32-bitową unsigned int z przesłanej wiadomości.

Dalej ładujemy 32 bity z pamięci inteligentnego kontraktu. Sprawdzamy, czy wczytana liczba pasuje do przesłanej, jeśli nie, zgłaszamy wyjątek. W naszym przypadku, ponieważ przekazujemy wartość, która się nie zgadza, powinien zostać zgłoszony wyjątek.

Teraz skompilujemy.

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

Otrzymany kod kopiujemy do lottery-test-suite.fif, pamiętając o zamianie ostatniego wiersza.

Sprawdzamy, czy test przechodzi:

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

Tutaj można zobaczyć odpowiedni commit z bieżącymi wynikami.

Zauważamy, że ciągłe kopiowanie skompilowanego kodu inteligentnego kontraktu do pliku testów jest niepraktyczne, więc napiszemy skrypt, który zapisze kod w stałej za nas, a my po prostu połączymy skompilowany kod w naszych testach za pomocą "include".

W folderze z projektem utworzymy plik build.sh z następującą zawartością.

#!/bin/bash

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

Uczynimy go wykonywalnym.

chmod +x .\/build.sh

Teraz wystarczy uruchomić nasz skrypt, aby skompilować kontrakt. Oprócz tego musimy zapisać go w stałej code. Dlatego utworzymy nowy plik lotter-compiled-for-test.fif, który załączymy w pliku lottery-test-suite.fif.

Dodamy do skryptu sh kod, który po prostu skopiuje skompilowany plik do lotter-compiled-for-test.fif i zmieni w nim ostatnią linię.

# 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

Teraz, aby sprawdzić, uruchomimy nasz skrypt, a w wyniku tego wygeneruje się plik lottery-compiled-for-test.fif, który załączymy w naszym lottery-test-suite.fif

W lottery-test-suite.fif usuwamy kod kontraktu i dodajemy linię "lottery-compiled-for-test.fif" include.

Uruchamiamy testy, aby sprawdzić, czy przechodzą.

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

Świetnie, teraz, aby zautomatyzować uruchamianie testów, tworzymy plik test.sh, który najpierw wykona build.sh, a następnie uruchomi testy.

touch test.sh
chmod +x test.sh

Wewnątrz wpisujemy

.\/build.sh 

echo "nKompilacja zakończonan"

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

Zrobimy test.sh i uruchomimy, aby upewnić się, że testy działają.

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

Sprawdzamy, czy kontrakt kompiluje się i testy są wykonywane.

Świetnie, teraz przy uruchamianiu test.sh kompilacja nastąpi od razu, a testy będą uruchamiane. Oto link do commitu.

Ok, zanim przejdziemy dalej, zróbmy dla wygody jeszcze jedną rzecz.

Utwórzmy folder build gdzie będziemy przechowywać skompilowany kontrakt i jego klon zapisany w stałej lottery-compiled.fif, lottery-compiled-for-test.fif. Utworzymy także folder test gdzie będą przechowywane pliki z testami lottery-test-suite.fif i potencjalnie inne pliki pomocnicze. Link do odpowiednich zmian.

Kontynuujmy rozwój smart kontraktu.

Dalej powinien być test, który sprawdza, czy wiadomość jest odbierana i licznik jest aktualizowany w magazynie, gdy wysyłamy poprawną liczbę. Ale zrobimy to później.

Teraz zastanowimy się, jaka struktura danych i jakie dane musimy przechować w smart kontrakcie.

Opiszę wszystko, co przechowujemy.

`seqno` 32-bitowa liczba całkowita dodatnia licznik. 

`pubkey` 256-bitowa liczba całkowita dodatnia klucz publiczny, za pomocą którego będziemy weryfikować podpis wysłanej wiadomości z zewnątrz, o czym poniżej. 

`order_seqno` 32-bitowa liczba całkowita dodatnia przechowuje licznik ilości zakładów. 

`number_of_wins` 32-bitowa liczba całkowita dodatnia przechowuje liczbę wygranych. 

`incoming_amount` typ danych Gram (pierwsze 4 bity odpowiadają za długość), przechowuje całkowitą ilość gramów, które zostały wysłane do kontraktu. 

`outgoing_amount` całkowita ilość gramów, które zostały wysłane do zwycięzców. 

`owner_wc` numer workchaina, 32-bitowa liczba całkowita (w niektórych miejscach zapisano, że 8-bitowa). W tej chwili tylko dwa -1 i 0. 

`owner_account_id` 256-bitowa liczba całkowita dodatnia, adres kontraktu w bieżącym workchainie. 

`orders` zmienna typu słownik, przechowuje ostatnie dwadzieścia zakładów. 

Następnie musimy napisać dwie funkcje. Pierwszą nazwiemy pack_state(), która będzie pakować dane do dalszego zapisania ich w magazynie smart kontraktu. Drugą, nazwijmy unpack_state() będzie odczytywać i zwracać dane z magazynu.

_ 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;
}

Dodajemy te dwie funkcje na początku smart kontraktu. Otrzymamy taki pośredni wynik.

Aby zapisać dane, trzeba będzie wywołać wbudowaną funkcję set_data() i zapisze dane z pack_state() do magazynu smart kontraktu.

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

Teraz, gdy mamy wygodne funkcje zapisu i odczytu danych, możemy ruszać dalej.

Musimy sprawdzić, czy wiadomość przychodząca z zewnątrz jest podpisana przez właściciela kontraktu (lub innym użytkownikiem, który ma dostęp do prywatnego klucza).

Kiedy publikujemy smart kontrakt, możemy zainicjować go z danymi, które chcemy przechować na przyszłość. Zapiszemy tam klucz publiczny, aby można było zweryfikować, że podpis przychodzącej wiadomości został dokonany odpowiednim kluczem prywatnym.

Zanim przejdziemy dalej, stwórzmy klucz prywatny i zapiszmy go w test/keys/owner.pk. Aby to zrobić, uruchomimy Fift w trybie interaktywnym i wykonamy cztery komendy.

`newkeypair` generuje klucz publiczny i prywatny oraz zapisuje je na stosie. 

`drop` usuwa górny element ze stosu (w tym przypadku klucz publiczny) 

`.s` pozwala zobaczyć, co obecnie znajduje się na stosie 

`"owner.pk" B>file` zapisuje klucz prywatny do pliku o nazwie `owner.pk`. 

`bye` kończy pracę z Fift. 

Utwórzmy folder klucze w środku folderu test i tam zapiszemy klucz prywatny.

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

Widzimy w bieżącym folderze plik owner.pk.

Usuwamy klucz publiczny ze stosu, gdy zajdzie potrzeba, możemy go uzyskać z klucza prywatnego.

Teraz musimy napisać weryfikację podpisu. Zacznijmy od testu. Najpierw odczytujemy klucz prywatny z pliku za pomocą funkcji file>B i zapisujemy go w zmiennej owner_private_key, potem za pomocą funkcji priv>pub konwertujemy klucz prywatny na publiczny i zapisujemy wynik w owner_public_key.

zmiennej owner_private_key
zmiennej owner_public_key

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

Oba klucze będą nam potrzebne.

Inicjujemy dane przechowujące smart kontraktu losowymi danymi w tej samej kolejności, jak w funkcji pack_state()i zapisujemy je w zmiennej magazyn.

zmienna owner_private_key
zmienna owner_public_key
zmienna orders
zmienna owner_wc
zmienna owner_account_id

".\/keys\/owner.pk" plik&gt;B owner_private_key !
owner_private_key @ priv&gt;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> magazyn !

Następnie stworzymy podpisaną wiadomość, w której będzie tylko podpis i wartość licznika.

Najpierw tworzymy dane, które chcemy przesłać, następnie podpisujemy je kluczem prywatnym i w końcu formujemy podpisaną wiadomość.

zmienna wiadomość_do_podpisania
zmienna wiadomość_do_wysłania
zmienna podpis
<b 0 32 u, b> wiadomość_do_podpisania !
wiadomość_do_podpisania @ właściciel_klucza_prywatnego @ ed25519_podpisz_uint podpis !
<b signature @ b, 0 32 u, b> &lt;s !  

W wyniku wiadomość, którą wyślemy do smart kontraktu, jest zapisana w zmiennej message_to_send, o funkcjach hashu, ed25519_sign_uint można przeczytać w dokumentacji Fift.

Aby uruchomić test, ponownie wywołujemy.

message_to_send @ 
recv_external 
code 
storage @
c7
runvmctx

W ten sposób powinien wyglądać plik z testami na tym etapie.

Uruchomimy test i on się nie powiedzie, więc zmienimy smart kontrakt, aby mógł odbierać wiadomości w tym formacie i weryfikować podpis.

Na początku zczytujemy 512 bitów podpisu z wiadomości i zapisujemy je w zmiennej, następnie zczytujemy 32 bity zmiennej licznika.

Ponieważ mamy funkcję odczytu danych z pamięci smart kontraktu, będziemy jej używać.

Następnie sprawdzamy licznik przesłany z pamięci oraz sprawdzamy podpis. Jeśli coś się zgadza, zgłaszamy wyjątek z odpowiednim kodem.

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

Odpowiedni commit tutaj.

Uruchomimy testy i zobaczymy, że drugi test się nie powiódł. Z dwóch powodów: braku bitów w wiadomości oraz braku bitów w pamięci, dlatego kod zawodzi podczas parsowania. Musimy dodać podpis wiadomości, którą wysyłamy, oraz skopiować pamięć z ostatniego testu.

W drugim teście dodamy podpis wiadomości i zmienimy pamięć smart kontraktu. W ten sposób aktualnie wygląda plik z testami.

Napiszemy czwarty test, w którym wysyłamy wiadomość podpisaną obcym kluczem prywatnym. Stworzymy jeszcze jeden klucz prywatny i zapiszemy go w pliku not-owner.pk. Tym kluczem prywatnym podpiszemy wiadomość. Uruchomimy testy i upewnimy się, że wszystkie testy przechodzą. Commit do tej pory.

Teraz w końcu możemy przejść do realizacji logiki smart kontraktu.
W recv_external() Będziemy przyjmować dwa typy wiadomości.

Ponieważ nasz kontrakt będzie kumulować przegrane graczy, te pieniądze muszą być przekazywane twórcy loterii. Adres portfela twórcy loterii zapisywany jest w pamięci przy tworzeniu kontraktu.

Na wszelki wypadek potrzebujemy możliwości zmiany adresu, na który przesyłane są grammy przegranych. Musimy również mieć możliwość przesyłania gramów z loterii na adres właściciela.

Zaczniemy od pierwszego. Najpierw napiszemy test, który sprawdzi, czy po wysłaniu wiadomości smart kontrakt zapisał nowy adres w pamięci. Zwrócimy uwagę, że w wiadomości, oprócz licznika i nowego adresu, przesyłamy również akcja 7-bitową liczbę całkowitą nieujemną, w zależności od niej będziemy wybierać, jak przetwarzać wiadomość w smart kontrakcie.

<b 0 32 u, 1 @ 7 new_owner_wc i, new_owner_account_id 256 b> wiadomość_do_podpisania !

W teście widać, jak przebiega deserializacja pamięci smart kontraktu. magazyn W Fift. Deserializacja zmiennych jest opisana w dokumentacji Fift.

Link do commita z dodanym testem.

Uruchamiamy test i upewniamy się, że kończy się niepowodzeniem. Teraz dodamy logikę zmiany adresu właściciela loterii.

W smart kontrakcie kontynuujemy parsowanie message, odczytując w akcja. Przypomnijmy, że będziemy mieli dwa akcja: zmiana adresu oraz wysyłka gramów.

Potem odczytujemy nowy adres właściciela kontraktu i zapisujemy w magazynie.
Uruchamiamy testy i widzimy, że trzeci test kończy się niepowodzeniem. Kończy się niepowodzeniem, ponieważ kontrakt teraz dodatkowo parsuje 7 bitów z wiadomości, których brakuje w teście. Dodamy do wiadomości nieistniejący akcja. Uruchomimy testy i zobaczymy, że wszystkie przechodzą. Tutaj commit ze zmianami. Świetnie.

Teraz napiszemy logikę wysyłki określonej liczby gramów na wcześniej zapisany adres.

Najpierw napiszemy test. Napiszemy dwa testy: jeden gdy brak balansu, drugi gdy wszystko powinno przejść pomyślnie. Testy można zobaczyć w tym commicie.

Teraz dopiszemy kod. Najpierw napiszemy dwie pomocnicze metody. Pierwsza metoda get, aby sprawdzić aktualny balans smart kontraktu.

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

A druga do wysyłania gramów do innego smart kontraktu. Tę metodę całkowicie skopiowałem z innego 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 <= wskaźnik formatu int_msg_info$0 
    ;;  .store_uint(1, 1) ;; 1 <= ihr wyłączony
    ;;  .store_uint(1, 1) ;; 1 <= odbicie = true
    ;;  .store_uint(0, 1) ;; 0 <= odbicie = false
    ;;  .store_uint(4, 5)  ;; 00100 <= flaga adresu, anycast = false, 8-bitowa sieć robocza
        .store_uint (196, 9)
        .store_int(wc, 8)
        .store_uint(addr, 256)
        .store_grams(grams)
        .store_uint(0, 107) ;; 106 zer + 0 jako wskaźnik, że nie ma komórki z danymi.
        .end_cell(); 
    send_raw_message(msg, 3); ;; tryb, 2 do ignorowania błędów, 1 gdy nadawca pokrywa opłaty, 64 do zwracania wartości wiadomości przychodzącej
}

Dodamy te dwie metody do smart kontraktu i napiszemy logikę. Najpierw parsujemy ilość gramów z wiadomości. Następnie sprawdzamy balans, jeśli jest za mało, wyrzucamy wyjątek. Jeśli wszystko jest w porządku, wysyłamy gramów na zapisany adres i aktualizujemy licznik.

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

W ten sposób tak wygląda aktualnie smart kontrakt. Uruchomimy testy i upewnimy się, że przechodzą.

Zresztą, za każdą przetworzoną wiadomość smart kontraktu pobierana jest opłata. Aby smart kontrakt mógł wykonać żądanie wiadomości, po podstawowych weryfikacjach należy wywołać accept_message().

Teraz zajmiemy się wewnętrznymi wiadomościami. W praktyce będziemy tylko odbierać grammy i odsyłać do gracza podwójną kwotę w przypadku wygranej oraz jedną trzecią właścicielowi w przypadku przegranej.

Najpierw napiszemy prosty test. W tym celu potrzebujemy adresu testowego smart kontraktu, z którego będziemy rzekomo wysyłać grammy do smart kontraktu.

Adres smart kontraktu składa się z dwóch liczb, 32-bitowa liczba całkowita odpowiada za workchain, a 256-bitowa, nieskrócona, to unikalny numer konta w tym workchain. Na przykład, -1 i 12345, ten adres zapiszemy w pliku.

Skopiowałem funkcję do zapisywania adresu z 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

Rozłóżmy, jak działa ta funkcja, co da nam zrozumienie, jak działa Fift. Uruchamiamy Fift w trybie interaktywnym.

~\/TON\/build\/crypto\/fift -i 

Na początku wkładamy na stos -1, 12345 i nazwę przyszłego pliku "sender.addr":

-1 12345 "sender.addr" 

Kolejnym krokiem jest wykonanie funkcji -rot, która przemieszcza stos, w taki sposób, że na górze stosu znajduje się unikalny numer smart kontraktu:

"sender.addr" -1 12345

256 u>B konwertuje 256-bitową, nieskróconą liczbę całkowitą na bajty.

"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039

swap zamienia miejscami dwa górne elementy stosu.

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -1

32 i>B konwertuje 32-bitową liczbę całkowitą na bajty.

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFF

B+ łączy dwa ciągi bajtów.

 "sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF

Znowu swap.

BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" 

I w końcu wykonywana jest operacja zapisu bajtów do pliku B>file. Po tym nasz stos jest pusty. Zatrzymujemy (prawdopodobnie od liczby. W bieżącym folderze utworzono plik sender.addr. Przeniesiemy plik do utworzonego folderu test\/addresses\/.

Napiszemy prosty test, który będzie wysyłał grammy do smart kontraktu. Oto commit.

Teraz zajmiemy się logiką loterii.

Pierwsze, co robimy, to sprawdzamy wiadomość bounced czy nie, jeśli bounced, to ignorujemy. bounced oznacza, że kontrakt zwróci grammy w przypadku wystąpienia jakiegokolwiek błędu. Nie będziemy zwracać grammy, jeśli nagle wystąpi błąd.

Sprawdzamy, czy saldo jest mniejsze niż pół grama, w takim przypadku po prostu przyjmujemy wiadomość i ignorujemy.

Następnie analizujemy adres smart kontraktu, z którego przyszła wiadomość.

Odczytujemy dane z magazynu, a następnie usuwamy stare zakłady z historii, jeśli ich liczba przekracza dwadzieścia. Dla ułatwienia napisałem trzy dodatkowe funkcje. pack_order(), unpack_order(), remove_old_orders().

Następnie sprawdzamy, czy saldo jest niewystarczające do wypłaty, w takim przypadku traktujemy to jako doładowanie i zapisujemy uzupełnienie w orders.

Następnie dochodzimy do sedna smart kontraktu.

Najpierw, jeśli gracz przegrał, zapisujemy go w historii zakładów, a jeśli suma przekracza 3 gramy, wysyłamy 1/3 właścicielowi smart kontraktu.

Jeśli gracz wygrał, wysyłamy podwójną kwotę na adres gracza, a następnie zapisujemy informacje o zakładzie w historii.

() 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) { ;; ignoruj bounced
        return ();
    }
    if (order_amount < 500000000) { ;; po prostu odbierz gramy bez zmiany stanu 
        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));
}

I to wszystko. Odpowiedni commit.

Teraz zostaje nam wykonać proste zadanie – stworzymy metody get, aby z zewnętrznego świata można było uzyskać informacje o stanie kontraktu (w rzeczywistości odczytywać dane z magazynu smart kontraktu).

Dodamy metody get. O tym, jak uzyskać informacje o smart kontrakcie, napiszemy poniżej.

Zapomniałem dodać kod, który obsłuży pierwszy żądanie, które ma miejsce przy publikacji smart kontraktu. Odpowiedni commit. I jeszcze poprawiłem błąd związany z wysłaniem 1/3 kwoty na konto właściciela.

Następnie pozostaje opublikować smart kontrakt. Utwórzmy folder requests.

Bazowałem na kodzie publikacji simple-wallet-code.fc który można znaleźć w oficjalnym repozytorium.

Na co warto zwrócić uwagę. Tworzymy magazyn smart kontraktu oraz wiadomość wejściową. Następnie generowany jest adres smart kontraktu, czyli adres jest znany jeszcze przed publikacją w TON. Następnie na ten adres należy wysłać kilka gramów, a dopiero później przesłać plik z samym smart kontraktem, ponieważ sieć pobiera prowizję za przechowywanie smart kontraktów i operacje w nich (walidatorzy, którzy przechowują i wykonują smart kontrakty). Kod można zobaczyć tutaj.

Następnie wykonujemy kod publikacji i otrzymujemy lottery-query.boc plik i adres smart kontraktu.

~\/TON\/build\/crypto\/fift -s requests\/new-lottery.fif 0

Nie zapominaj, aby zapisać wygenerowane pliki: lottery-query.boc, lottery.addr, lottery.pk.

Wśród innych rzeczy w logach wykonania zobaczymy adres smart kontraktu.

new wallet address = 0:044910149dbeaf8eadbb2b28722e7d6a2dc6e264ec2f1d9bebd6fb209079bc2a 
(Zapisywanie adresu do pliku lottery.addr)
Adres, który nie może być wykorzystany (do inicjacji): 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd
Adres, który może być wykorzystany (do późniejszego dostępu): kQAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KpFY

Dla ciekawości zrobimy zapytanie do TON

$ .\/lite-client\/lite-client -C ton-lite-client-test1.config.json 
getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

I zobaczymy, że konto z takim adresem jest puste.

stan konta jest pusty

Wysyłamy na adres 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 gramy i po kilku sekundach wykonujemy tę samą komendę. Do wysyłania gramów używam oficjalnego portfela, a testowe gramy można poprosić u kogoś z czatu, o którym wspomnę na końcu artykułu.

> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Sprawdza, co w sieci pojawił się nie zainicjowany (state:account_uninit) smart kontrakt z takim adresem i bilansem 1 000 000 000 nanogram.

stan konta to (konto
  addr:(addr_std
    anycast:nic nie workchain_id:0 adres: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)) ostatnia zapłata:1583257959
    wymagana płatność:nic)
  storage:(account_storage last_trans_lt:3825478000002
    saldo:(currencies
      gramy:(nanograms
        amount:(var_uint len:4 value:2000000000))
      inne:(extra_currencies
        dict:hme_empty))
    stan:account_uninit))
x{C00044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A20259C2F2F4CB3800000DEAC10776091DCD650004_}
ostatnia transakcja lt = 3825478000001 hash = B043616AE016682699477FFF01E6E903878CDFD6846042BA1BFC64775E7AC6C4
saldo konta wynosi 2000000000ng

Teraz opublikujemy smart kontrakt. Uruchomimy lite-client i wykonamy.

> sendfile lottery-query.boc
[ 1][t 2][1583008371.631410122][lite-client.cpp:966][!testnode] wysyłanie zapytania z pliku lottery-query.boc
[ 3][t 1][1583008371.828550100][lite-client.cpp:976][!query] status wiadomości zewnętrznej to 1 

Sprawdzimy, czy kontrakt został opublikowany.

> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Między innymi otrzymamy.

  storage:(account_storage last_trans_lt:3825499000002
    saldo:(currencies
      gramy:(nanograms
        amount:(var_uint len:4 value:1987150999))
      inne:(extra_currencies
        dict:hme_empty))
    stan:(account_active

Widzimy, że account_active.

Odpowiedni commit z zmianami tutaj.

Teraz stworzymy zapytania do interakcji ze smart kontraktem.

Dokładniej, pierwszy dotyczący zmiany adresu zostawimy jako samodzielną pracę, a drugi do wysyłania gramów na adres właściciela zrobimy. W rzeczywistości musimy zrobić to samo, co w teście na wysyłanie gramów.

Oto taka wiadomość, którą będziemy wysyłać do smart kontraktu, gdzie msg_seqno 165, akcja 2 i 9.5 grama do wysłania.

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

Nie zapominamy podpisać wiadomości prywatnym kluczem lottery.pk, który został wygenerowany wcześniej przy tworzeniu smart kontraktu. Oto odpowiedni commit.

Otrzymujemy informacje z smart kontraktu za pomocą metod get

Teraz zajmiemy się uruchamianiem metod get smart kontraktu.

Uruchamiamy lite-client, i uruchamiamy metody get, które napisaliśmy.

$ .\/lite-client\/lite-client -C ton-lite-client-test1.config.json
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd saldo
argumenty:  [ 104128 ] 
wynik:  [ 64633878952 ] 
...

W result zawiera wartość, którą zwraca funkcja balance() z naszego smart kontraktu.
To samo zrobimy jeszcze dla kilku metod.

> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_seqno
...
argumenty:  [ 77871 ] 
wynik:  [ 1 ] 

Zamówimy historię zakładów.

> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_orders
...
argumenty:  [ 67442 ] 
wynik:  [ ([0 1 1583258284 10000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [1 3 1583258347 4000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [2 1 1583259901 50000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308]) ] 

Będziemy używać lite-client i metod GET, aby wyświetlić informacje o inteligentnym kontrakcie na stronie.

Wyświetlamy dane inteligentnego kontraktu na stronie

Napisałem prostą stronę internetową w Pythonie, aby pokazać dane z inteligentnego kontraktu w wygodny sposób. Nie będę się nad tym szczegółowo rozwodzić i opublikuję stronę. jednym commitem.

Zapytania do TON są realizowane z Python z pomocą lite-client,. Dla wygody strona jest pakowana w Docker i publikowana w Google Cloud. Link do strony.

Próbujemy

Teraz spróbujemy wysłać tam gramy na doładowanie z portfela. Wyślemy 40 gramów. I zrobimy kilka zakładów dla zobrazowania. Widzimy, że strona pokazuje historię zakładów, bieżący procent wygranej i inne przydatne informacje.

Widzimy, że pierwszy wygraliśmy, drugi przegraliśmy.

Epilog

Artykuł wyszedł znacznie dłuższy, niż przypuszczałem, może można było krócej, a może akurat dla osoby, która nic nie wie o TON i chce napisać i opublikować nie najprostszy inteligentny kontrakt z możliwością interakcji. Może niektóre rzeczy można było wyjaśnić prościej.

Możliwe, że niektóre aspekty realizacji można było zrobić efektywniej i elegancko, ale w takim przypadku czas przygotowania artykułu wydłużyłby się jeszcze bardziej. Również istnieje prawdopodobieństwo, że gdzieś popełniłem błąd lub czegoś nie zrozumiałem, więc jeśli robicie coś poważnego, należy opierać się na oficjalnej dokumentacji lub oficjalnym repozytorium z kodem TON.

Warto zauważyć, że ponieważ sam TON jest jeszcze w aktywnej fazie rozwoju, mogą wystąpić zmiany, które złamią którykolwiek z kroków w tym artykule (co już miało miejsce, podczas pisania naprawiłem), ale ogólne podejście raczej się nie zmieni.

Nie będę spekulować na temat przyszłości TON. Może platforma stanie się czymś więcej i powinniśmy już teraz poświęcić czas na jej zgłębianie oraz zająć niszę swoimi produktami.

Jest jeszcze Libra od Facebooka, która ma potencjalną bazę użytkowników większą niż TON. O Libra prawie nic nie wiem, ale sądząc po forum, aktywność tam jest znacznie większa niż w społeczności TON. Choć deweloperzy i społeczność TON bardziej przypominają underground, co też jest fajne.

Linki

  1. Oficjalna dokumentacja dotycząca TON: https://test.ton.org
  2. Oficjalne repozytorium TON: https://github.com/ton-blockchain/ton
  3. Oficjalny portfel dla różnych platform: https://wallet.ton.org
  4. Repozytorium inteligentnego kontraktu z tego artykułu: https://github.com/raiym/astonished
  5. Link do strony inteligentnego kontraktu: https://ton-lottery.appspot.com
  6. Repozytorium rozszerzenia dla Visual Studio Code dla FunC: https://github.com/raiym/func-visual-studio-plugin
  7. Czat o TON w Telegramie, który bardzo pomógł mi zrozumieć na początku. Myślę, że nie będzie błędem, jeśli powiem, że są tam wszyscy, którzy pisali coś dla TON. Można tam także poprosić o testowe grammy. https://t.me/tondev_ru
  8. Jeszcze jeden czat o TON, w którym znajdowałem przydatne informacje: https://t.me/TONgramDev
  9. Pierwszy etap konkursu: https://contest.com/blockchain
  10. Drugi etap konkursu: https://contest.com/blockchain-2

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster