ProHoster > Блог > Administracija > O tome kako napisati i objaviti pametni ugovor u Telegram otvorenoj mreži (TON)
O tome kako napisati i objaviti pametni ugovor u Telegram otvorenoj mreži (TON)
O tome kako napisati i objaviti pametni ugovor u TON-u
O čemu je ovaj članak?
U članku ću pričati o tome kako sam učestvovao u prvom (od dva) Telegram blockchain natjecanja, nisam dobio nagradu i odlučio da svoje iskustvo zabilježim u članku kako ne bi potonuo u zaborav i, možda, pomogao nekoga.
Pošto nisam želio da pišem apstraktni kod, već da nešto radim, za članak sam napisao pametni ugovor za instant lutriju i web stranicu koja prikazuje podatke pametnog ugovora direktno iz TON-a bez korištenja međuskladišta.
Članak će biti koristan onima koji žele sklopiti svoj prvi pametni ugovor u TON-u, ali ne znaju odakle da počnu.
Koristeći lutriju kao primjer, preći ću od instaliranja okruženja do objavljivanja pametnog ugovora, interakcije s njim i pisanja web stranice za primanje i objavljivanje podataka.
O učešću na takmičenju
Prošlog oktobra, Telegram je najavio blockchain takmičenje sa novim jezicima Fift и FunC. Bilo je potrebno izabrati bilo koji od pet predloženih pametnih ugovora. Mislio sam da bi bilo lijepo napraviti nešto drugačije, naučiti jezik i napraviti nešto, čak i ako neću morati ništa drugo pisati u budućnosti. Osim toga, tema je stalno na usnama.
Vrijedi reći da nisam imao iskustva u razvoju pametnih ugovora.
Planirao sam da učestvujem do samog kraja dok ne budem mogao pa da napišem pregledni članak, ali nisam uspeo odmah u prvom. I napisao novčanik sa uključenim višestrukim potpisom FunC i generalno je funkcionisalo. Uzeo sam to kao osnovu pametni ugovor na Solidityju.
Tada sam mislio da je ovo sasvim dovoljno da zauzmem barem neko nagradno mjesto. Kao rezultat toga, oko 40 od 60 učesnika je postalo nagrađeno, a ja nisam bio među njima. Generalno, u ovome nema ništa loše, ali jedna stvar me je zasmetala. U trenutku objavljivanja rezultata nije urađen pregled testa za moj ugovor, pitao sam učesnike u ćaskanju da li ima još neko ko ga nema, nije bilo.
Očigledno obraćajući pažnju na moje poruke, dva dana kasnije sudije su objavile komentar i još uvijek ne razumijem da li su slučajno propustili moj pametni ugovor tokom suđenja ili su jednostavno pomislili da je toliko loš da mu nije potreban komentar. Postavio sam pitanje na stranici, ali nisam dobio odgovor. Iako nije tajna ko je sudio, smatrao sam da je nepotrebno pisati lične poruke.
Mnogo vremena je potrošeno na razumijevanje, pa je odlučeno da se napiše članak. S obzirom da još nema puno informacija, ovaj će članak svima koji su zainteresirani pomoći uštedjeti vrijeme.
Koncept pametnih ugovora u TON-u
Pre nego što bilo šta napišete, morate da shvatite sa koje strane da pristupite ovoj stvari. Stoga ću vam sada reći od kojih dijelova se sastoji sistem. Tačnije, koje dijelove trebate znati da biste napisali makar kakav ugovor o radu.
Fokusiraćemo se na pisanje pametnog ugovora i rad sa njima TON Virtual Machine (TVM), Fift и FunC, pa je članak više kao opis razvoja redovnog programa. Ovdje se nećemo zadržavati na tome kako sama platforma funkcionira.
Općenito o tome kako funkcionira TVM i jezik Fift postoji dobra zvanična dokumentacija. Dok sam učestvovao na takmičenju i sada dok sam pisao aktuelni ugovor, često sam joj se obraćao.
Glavni jezik na kojem su pametni ugovori napisani je FunC. Trenutno ne postoji dokumentacija o tome, pa da biste nešto napisali morate proučiti primjere pametnih ugovora iz službenog repozitorija i implementaciju samog jezika tamo, plus možete pogledati primjere pametnih ugovora iz prethodna dva takmičenja. Linkovi na kraju članka.
Recimo da smo već napisali pametni ugovor za FunC, nakon toga kompajliramo kod u Fift asembler.
Sastavljeni pametni ugovor ostaje za objavljivanje. Da biste to učinili, morate upisati funkciju Fift, koji će uzeti kod pametnog ugovora i neke druge parametre kao ulaz, a izlaz će biti datoteka s ekstenzijom .boc (što znači „vreća ćelija“), i, u zavisnosti od toga kako to napišemo, privatni ključ i adresa, koji se generišu na osnovu koda pametnog ugovora. Već sada možete poslati gram na adresu pametnog ugovora koji još nije objavljen.
Za objavljivanje pametnog ugovora u TON primljeno .boc fajl će se morati poslati u blockchain pomoću laganog klijenta (više o tome u nastavku). Ali prije objavljivanja, morate prenijeti gram na generiranu adresu, inače pametni ugovor neće biti objavljen. Nakon objavljivanja, možete stupiti u interakciju sa pametnim ugovorom tako što ćete mu poslati poruke izvana (na primjer, korištenjem lakog klijenta) ili iznutra (na primjer, jedan pametni ugovor šalje drugom poruku unutar TON).
Kada shvatimo kako se kod objavljuje, postaje lakše. Otprilike znamo šta želimo napisati i kako će naš program funkcionirati. I dok pišemo, tražimo kako je to već implementirano u postojećim pametnim ugovorima, ili gledamo u implementacijski kod Fift и FunC u zvaničnom repozitoriju, ili pogledajte u službenoj dokumentaciji.
Vrlo često sam tražio ključne riječi u Telegram chatu gdje su se okupili svi učesnici takmičenja i zaposleni u Telegramu, i dogodilo se da su se tokom takmičenja svi okupili i počeli da razgovaraju o Fiftu i FunC-u. Link na kraju članka.
Vrijeme je da pređemo s teorije na praksu.
Priprema okoline za rad sa TON-om
Uradio sam sve što će biti opisano u članku o MacOS-u i još jednom sam to provjerio u čistom Ubuntu 18.04 LTS na Dockeru.
Prva stvar koju trebate učiniti je preuzeti i instalirati lite-client sa kojim možete slati zahtjeve TON-u.
Upute na službenoj web stranici vrlo detaljno i jasno opisuju proces instalacije i izostavljaju neke detalje. Ovdje slijedimo upute, usput instalirajući ovisnosti koje nedostaju. Nisam sam kompajlirao svaki projekat i instalirao ga iz zvaničnog Ubuntu repozitorija (na MacOS-u koji sam koristio brew).
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json
Ako je gradnja bila uspješna, nakon pokretanja vidjet ćete dnevnik veze lakog klijenta s čvorom.
[ 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)
...
Možete pokrenuti naredbu help i pogledajte koje su komande dostupne.
help
Hajde da navedemo naredbe koje ćemo koristiti u ovom članku.
list of available commands:
last Get last block and state info from server
sendfile <filename> Load a serialized message from <filename> and send it to server
getaccount <addr> [<block-id-ext>] Loads the most recent state of specified account; <addr> is in [<workchain>:]<hex-or-base64-addr> format
runmethod <addr> [<block-id-ext>] <method-id> <params>... Runs GET method <method-id> of account <addr> with specified parameters
last получает последний созданный блок с сервера.
sendfile <filename> отправляет в TON файл с сообщением, именно с помощью этой команды публикуется смарт-контракт и запрсосы к нему.
getaccount <addr> загружает текущее состояние смарт-контракта с указанным адресом.
runmethod <addr> [<block-id-ext>] <method-id> <params> запускает get-методы смартконтракта.
Sada smo spremni za pisanje samog ugovora.
Реализация
Ideja
Kao što sam gore napisao, pametni ugovor koji pišemo je lutrija.
Štaviše, ovo nije lutrija u kojoj morate kupiti tiket i čekati sat, dan ili mjesec, već instant u kojoj korisnik prelazi na adresu ugovora N grama, i odmah ga dobije nazad 2 * N grama ili gubi. Napravit ćemo vjerovatnoću pobjede oko 40%. Ako nema dovoljno grama za plaćanje, transakciju ćemo smatrati dopunom.
Osim toga, važno je da se opklade mogu vidjeti u realnom vremenu iu prikladnom obliku, tako da korisnik može odmah shvatiti da li je dobio ili izgubio. Stoga morate napraviti web stranicu koja će prikazivati opklade i rezultate direktno iz TON-a.
Pisanje pametnog ugovora
Radi praktičnosti, istaknuo sam kod za FunC; dodatak se može pronaći i instalirati u Visual Studio Code pretrazi; ako iznenada poželite nešto dodati, učinio sam dodatak javno dostupnim. Takođe, neko je prethodno napravio dodatak za rad sa Fiftom, možete ga takođe instalirati i pronaći u VSC-u.
Kreirajmo odmah spremište u koje ćemo urezati međurezultate.
Da bismo sebi olakšali život, napisat ćemo pametni ugovor i testirati ga lokalno dok ne bude spreman. Tek nakon toga ćemo to objaviti u TON-u.
Pametni ugovor ima dvije eksterne metode kojima se može pristupiti. prvo, recv_external() ova funkcija se izvršava kada zahtjev za ugovorom dolazi iz vanjskog svijeta, odnosno ne iz TON-a, na primjer, kada sami generišemo poruku i šaljemo je preko lite-klijenta. Sekunda, recv_internal() ovo je kada se, unutar samog TON-a, bilo koji ugovor odnosi na naš. U oba slučaja, možete proslijediti parametre funkciji.
Počnimo s jednostavnim primjerom koji će raditi ako je objavljen, ali u njemu nema funkcionalnog opterećenja.
Ovdje moramo objasniti šta je to slice. Svi podaci pohranjeni u TON Blockchain-u su zbirka TVM cell ili jednostavno cell, u takvu ćeliju možete pohraniti do 1023 bita podataka i do 4 veze s drugim ćelijama.
TVM cell slice ili slice ovo je dio postojećeg cell se koristi za raščlanjivanje, kasnije će postati jasno. Najvažnije nam je da možemo da se prebacimo slice i ovisno o vrsti poruke, obraditi podatke u recv_external() ili recv_internal().
impure — ključna riječ koja označava da funkcija mijenja podatke pametnog ugovora.
Sačuvajmo šifru ugovora lottery-code.fc i kompajlirati.
Sastavili smo Fift asemblerski kod 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že se pokrenuti lokalno, za to ćemo pripremiti okruženje.
Imajte na umu da se prva linija povezuje Asm.fif, ovo je kod napisan u Fiftu za Fift asembler.
Budući da želimo pokrenuti i testirati pametni ugovor lokalno, kreirat ćemo datoteku lottery-test-suite.fif i kopirajte prevedeni kod tamo, zamjenjujući zadnji red u njemu, koji upisuje kod pametnog ugovora u konstantu codeda biste ga zatim prebacili na virtuelnu mašinu:
"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
Za sada se čini jasnim, sada dodajmo istoj datoteci kod koji ćemo koristiti za pokretanje TVM-a.
В c7 snimamo kontekst, odnosno podatke s kojima će se TVM (ili stanje mreže) pokrenuti. Čak i tokom takmičenja, jedan od programera je pokazao kako se kreira c7 i kopirao sam. U ovom članku možda ćemo morati promijeniti rand_seed budući da generiranje slučajnog broja ovisi o tome i ako se ne promijeni, isti broj će biti vraćen svaki put.
recv_internal и recv_external konstante sa vrijednostima 0 i -1 bit će odgovorne za pozivanje odgovarajućih funkcija u pametnom ugovoru.
Sada smo spremni za kreiranje prvog testa za naš prazan pametni ugovor. Radi jasnoće, za sada ćemo dodati sve testove u istu datoteku lottery-test-suite.fif.
Kreirajmo varijablu storage i upišite praznu u nju cell, ovo će biti skladište pametnih ugovora.
message Ovo je poruka koju ćemo prenijeti pametnom kontaktu izvana. Za sada ćemo ga također učiniti praznim.
Odlično, napisali smo prvu radnu verziju pametnog ugovora.
Sada moramo dodati funkcionalnost. Prvo se pozabavimo porukama koje dolaze iz vanjskog svijeta u recv_external()
Programer sam bira format poruke koji ugovor može prihvatiti.
Ali obično
prvo, želimo zaštititi naš ugovor od vanjskog svijeta i učiniti ga tako da mu samo vlasnik ugovora može slati eksterne poruke.
drugo, kada pošaljemo ispravnu poruku TON-u, želimo da se to dogodi tačno jednom, a kada ponovo pošaljemo istu poruku, pametni ugovor je odbije.
Dakle, skoro svaki ugovor rješava ova dva problema, budući da naš ugovor prihvata eksterne poruke, moramo se pobrinuti i za to.
Uradićemo to obrnutim redosledom. Prvo, riješimo problem s ponavljanjem; ako je ugovor već primio takvu poruku i obradio je, neće je izvršiti drugi put. A onda ćemo riješiti problem tako da samo određeni krug ljudi može slati poruke pametnom ugovoru.
Postoje različiti načini za rješavanje problema s dupliranim porukama. Evo kako ćemo to uraditi. U pametnom ugovoru inicijaliziramo brojač primljenih poruka sa početnom vrijednošću 0. U svakoj poruci pametnom ugovoru ćemo dodati trenutnu vrijednost brojača. Ako se vrijednost brojača u poruci ne poklapa s vrijednošću u pametnom ugovoru, onda ga ne obrađujemo; ako ima, onda ga obrađujemo i povećavamo brojač u pametnom ugovoru za 1.
Vratimo se na lottery-test-suite.fif i dodajte mu drugi test. Ako pošaljemo pogrešan broj, kod bi trebao izbaciti izuzetak. Na primjer, neka podaci o ugovoru pohranjuju 166, a mi ćemo poslati 165.
<b 166 32 u, b> storage !
<b 165 32 u, b> message !
message @
recv_external
code
storage @
c7
runvmctx
drop
exit_code !
."Exit code " exit_code @ . cr
exit_code @ 33 - abort"Test #2 Not passed"
Hajdemo.
~/TON/build/crypto/fift -s lottery-test-suite.fif
I vidjet ćemo da je test izvršen sa greškom.
[ 1][t 0][1582283084.210902214][words.cpp:3046] lottery-test-suite.fif:67: abort": Test #2 Not passed
[ 1][t 0][1582283084.210941076][fift-main.cpp:196] Error interpreting file `lottery-test-suite.fif`: error interpreting included file `lottery-test-suite.fif` : lottery-test-suite.fif:67: abort": Test #2 Not passed
U ovoj fazi lottery-test-suite.fif trebalo bi da izgleda link.
Sada dodajmo logiku brojača u pametni ugovor lottery-code.fc.
() recv_internal(slice in_msg) impure {
;; TODO: implementation
}
() recv_external(slice in_msg) impure {
if (slice_empty?(in_msg)) {
return ();
}
int msg_seqno = in_msg~load_uint(32);
var ds = begin_parse(get_data());
int stored_seqno = ds~load_uint(32);
throw_unless(33, msg_seqno == stored_seqno);
}
В slice in_msg leži poruka koju šaljemo.
Prvo što radimo je da provjerimo da li poruka sadrži podatke, ako ne, onda jednostavno izlazimo.
Zatim analiziramo poruku. in_msg~load_uint(32) učitava broj 165, 32-bitni unsigned int iz poslate poruke.
Zatim učitavamo 32 bita iz memorije pametnog ugovora. Provjeravamo da li se učitani broj podudara s proslijeđenim; ako ne, bacamo izuzetak. U našem slučaju, pošto prenosimo nepodudaranje, trebalo bi baciti izuzetak.
Kopirajte rezultirajući kod na lottery-test-suite.fif, ne zaboravljajući zamijeniti posljednji red.
Provjeravamo da li je test prošao:
~/TON/build/crypto/fift -s lottery-test-suite.fif
Ovde Možete vidjeti odgovarajuće urezivanje sa trenutnim rezultatima.
Imajte na umu da je nezgodno stalno kopirati kompajlirani kod pametnog ugovora u datoteku sa testovima, pa hajde da napišemo skriptu koja će za nas upisati kod u konstantu, a mi ćemo jednostavno povezati kompajlirani kod sa našim testovima koristeći "include".
Kreirajte datoteku u folderu projekta build.sh sa sljedećim sadržajem.
Sada samo pokrenite našu skriptu da sastavite ugovor. Ali pored ovoga, moramo to zapisati u konstantu code. Tako ćemo kreirati novu datoteku lotter-compiled-for-test.fif, koji ćemo uključiti u datoteku lottery-test-suite.fif.
Dodajmo skirpt kod u sh, koji će jednostavno duplicirati kompajlirani fajl lotter-compiled-for-test.fif i promijenite zadnji red u njemu.
# 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
Sada, da provjerimo, pokrenimo rezultirajuću skriptu i datoteka će biti generirana lottery-compiled-for-test.fif, koje ćemo uključiti u naše lottery-test-suite.fif
В lottery-test-suite.fif obrišite šifru ugovora i dodajte red "lottery-compiled-for-test.fif" include.
Pokrećemo testove kako bismo provjerili da li prolaze.
~/TON/build/crypto/fift -s lottery-test-suite.fif
Odlično, sada da automatiziramo pokretanje testova, napravimo datoteku test.sh, koji će se prvo izvršiti build.sh, a zatim pokrenite testove.
Hajde da to uradimo test.sh i pokrenite ga kako biste bili sigurni da testovi rade.
chmod +x ./test.sh
./test.sh
Provjeravamo da li je ugovor sastavljen i da li su testovi izvršeni.
Odlično, sada na startu test.sh Testovi će biti sastavljeni i pokrenuti odmah. Evo linka za počiniti.
U redu, prije nego što nastavimo, učinimo još jednu stvar radi pogodnosti.
Kreirajmo folder build gdje ćemo pohraniti kopirani ugovor i njegov klon upisan u konstantu lottery-compiled.fif, lottery-compiled-for-test.fif. Kreirajmo i folder test gdje će biti pohranjena test datoteka? lottery-test-suite.fif i potencijalno druge prateće datoteke. Link do relevantnih promjena.
Nastavimo sa razvojem pametnog ugovora.
Zatim bi trebao biti test koji provjerava da li je poruka primljena i da li se brojač ažurira u prodavnici kada pošaljemo tačan broj. Ali to ćemo uraditi kasnije.
Sada razmislimo o tome koju strukturu podataka i koje podatke treba pohraniti u pametni ugovor.
Opisaću sve što skladištimo.
`seqno` 32-х битное целое положительное число счетчик.
`pubkey` 256-ти битное целое положительное число публичный ключ, с помощью которого, мы будем проверять подпись отправленного извне сообщения, о чем ниже.
`order_seqno` 32-х битное целое положительное число хранит счетчик количества ставок.
`number_of_wins` 32-х битное целое положительное число хранит количество побед.
`incoming_amount` тип данных Gram (первые 4 бита отвечает за длину), хранит общее количество грамов, которые были отправлены на контртакт.
`outgoing_amount` общее количество грамов, которое было отправлено победителям.
`owner_wc` номер воркчейна, 32-х битное (в некоторых местах написано, что 8-ми битное) целое число. В данный момент всего два -1 и 0.
`owner_account_id` 256-ти битное целое положительное число, адрес контракта в текущем воркчейне.
`orders` переменная типа словарь, хранит последние двадцать ставок.
Zatim morate napisati dvije funkcije. Pozovimo prvog pack_state(), koji će spakovati podatke za naknadno pohranjivanje u memoriju pametnog ugovora. Pozovimo drugog unpack_state() će čitati i vraćati podatke iz skladišta.
_ 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;
}
Ove dvije funkcije dodajemo na početak pametnog ugovora. To će uspjeti Volim ovo srednji rezultat.
Za spremanje podataka morat ćete pozvati ugrađenu funkciju set_data() i pisaće podatke iz pack_state() u skladištu pametnih ugovora.
Sada kada imamo zgodne funkcije za pisanje i čitanje podataka, možemo krenuti dalje.
Moramo provjeriti da li je poruka koja dolazi izvana potpisana od strane vlasnika ugovora (ili drugog korisnika koji ima pristup privatnom ključu).
Kada objavimo pametni ugovor, možemo ga inicijalizirati s podacima koji su nam potrebni u skladištu, koji će biti sačuvani za buduću upotrebu. Tamo ćemo snimiti javni ključ kako bismo mogli provjeriti da li je dolazna poruka potpisana odgovarajućim privatnim ključem.
Prije nego što nastavimo, napravimo privatni ključ i upišemo ga test/keys/owner.pk. Da bismo to uradili, pokrenimo Fift u interaktivnom režimu i izvršimo četiri komande.
`newkeypair` генерация публичного и приватного ключа и запись их в стек.
`drop` удаления из стека верхнего элемента (в данном случае публичный ключ)
`.s` просто посмотреть что лежит в стеке в данный момент
`"owner.pk" B>file` запись приватного ключа в файл с именем `owner.pk`.
`bye` завершает работу с Fift.
Kreirajmo folder keys unutar foldera test i tamo upišite privatni ključ.
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
Vidimo fajl u trenutnom folderu owner.pk.
Uklanjamo javni ključ iz steka i kada je potrebno možemo ga dobiti iz privatnog.
Sada treba da napišemo verifikaciju potpisa. Počnimo sa testom. Prvo čitamo privatni ključ iz datoteke koristeći funkciju file>B i zapišite ga u varijablu owner_private_key, a zatim pomoću funkcije priv>pub konvertujte privatni ključ u javni ključ i upišite rezultat owner_public_key.
Kao rezultat, poruka koju ćemo poslati pametnom ugovoru se bilježi u varijablu message_to_send, o funkcijama hashu, ed25519_sign_uint možete čitati u dokumentaciji Fifta.
Evo tako Fajl sa testovima bi u ovoj fazi trebao izgledati ovako.
Pokrenimo test i neće uspjeti, pa ćemo promijeniti pametni ugovor tako da može primati poruke ovog formata i provjeravati potpis.
Prvo izbrojimo 512 bita potpisa iz poruke i upišemo ga u varijablu, a zatim izbrojimo 32 bita varijable brojača.
Pošto imamo funkciju za čitanje podataka iz memorije pametnog ugovora, koristit ćemo je.
Sljedeća je provjera šaltera prenesenog sa skladištem i provjera potpisa. Ako se nešto ne podudara, onda bacimo izuzetak s odgovarajućim kodom.
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));
Pokrenimo testove i vidimo da drugi test nije uspio. Iz dva razloga, nema dovoljno bitova u poruci i nema dovoljno bitova u memoriji, pa se kod ruši prilikom raščlanjivanja. Moramo dodati potpis poruci koju šaljemo i kopirati memoriju iz posljednjeg testa.
U drugom testu ćemo dodati potpis poruke i promijeniti pohranu pametnog ugovora. Evo tako fajl sa testovima izgleda kao u ovom trenutku.
Napišimo četvrti test, u kojem ćemo poslati poruku potpisanu tuđim privatnim ključem. Kreirajmo drugi privatni ključ i spremimo ga u datoteku not-owner.pk. Poruku ćemo potpisati ovim privatnim ključem. Pokrenimo testove i uvjerimo se da svi testovi prođu. Počinite u ovom momentu.
Sada konačno možemo preći na implementaciju logike pametnog ugovora.
В recv_external() prihvatit ćemo dvije vrste poruka.
Pošto će naš ugovor akumulirati gubitke igrača, ovaj novac se mora prenijeti kreatoru lutrije. Adresa novčanika kreatora lutrije se bilježi u memoriji prilikom kreiranja ugovora.
Za svaki slučaj treba nam mogućnost promjene adrese na koju šaljemo gram gubitnika. Također bismo trebali moći slati grame sa lutrije na adresu vlasnika.
Počnimo s prvim. Napišimo prvo test koji će provjeriti da je nakon slanja poruke pametni ugovor sačuvao novu adresu u memoriji. Napominjemo da u poruci osim šaltera i nove adrese prenosimo i podatke action 7-bitni cijeli nenegativan broj, ovisno o njemu, birat ćemo kako ćemo obraditi poruku u pametnom ugovoru.
<b 0 32 u, 1 @ 7 u, new_owner_wc @ 32 i, new_owner_account_id @ 256 u, b> message_to_sign !
U testu možete vidjeti kako je smartcontract pohrana deserijalizirana storage Fift. Deserijalizacija varijabli je opisana u dokumentaciji Fift.
Pokrenimo test i uvjerimo se da ne uspije. Sada dodajmo logiku za promjenu adrese vlasnika lutrije.
U pametnom ugovoru nastavljamo raščlanjivanje message, pročitaj action. Podsjetimo da ćemo imati dva action: promijeniti adresu i poslati gram.
Zatim čitamo novu adresu vlasnika ugovora i spremamo je u skladište.
Pokrećemo testove i vidimo da treći test nije uspio. Ruši se zbog činjenice da ugovor sada dodatno analizira 7 bitova iz poruke, koji nedostaju u testu. Dodajte nepostojeću poruku action. Pokrenimo testove i vidimo da li je sve prošlo. ovdje posvetite se promjenama. Odlično.
Sada napišimo logiku slanja navedenog broja grama na prethodno sačuvanu adresu.
Prvo, hajde da napišemo test. Napisaćemo dva testa, jedan kada nema dovoljno balansa, drugi kada sve treba da prođe uspešno. Testovi se mogu pogledati u ovom urezivanju.
Sada dodajmo kod. Prvo, napišimo dvije pomoćne metode. Prva metoda dobivanja je saznanje trenutnog stanja pametnog ugovora.
int balance() inline_ref method_id {
return get_balance().pair_first();
}
A drugi je za slanje grama na drugi pametni ugovor. Potpuno sam kopirao ovu metodu iz drugog pametnog ugovora.
() send_grams(int wc, int addr, int grams) impure {
;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
cell msg = begin_cell()
;; .store_uint(0, 1) ;; 0 <= format indicator int_msg_info$0
;; .store_uint(1, 1) ;; 1 <= ihr disabled
;; .store_uint(1, 1) ;; 1 <= bounce = true
;; .store_uint(0, 1) ;; 0 <= bounced = false
;; .store_uint(4, 5) ;; 00100 <= address flags, anycast = false, 8-bit workchain
.store_uint (196, 9)
.store_int(wc, 8)
.store_uint(addr, 256)
.store_grams(grams)
.store_uint(0, 107) ;; 106 zeroes + 0 as an indicator that there is no cell with the data.
.end_cell();
send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value
}
Dodajmo ove dvije metode pametnom ugovoru i napišemo logiku. Prvo analiziramo broj grama iz poruke. Zatim provjeravamo saldo, ako nije dovoljno bacamo izuzetak. Ako je sve u redu, šaljemo gram na sačuvanu adresu i ažuriramo brojač.
Evo tako izgleda kao pametni ugovor u ovom trenutku. Pokrenimo testove i uvjerimo se da su prošli.
Inače, provizija se oduzima od pametnog ugovora svaki put za obrađenu poruku. Da bi poruke pametnog ugovora izvršile zahtjev, nakon osnovnih provjera morate nazvati accept_message().
Sada pređimo na interne poruke. U stvari, prihvatit ćemo samo grame i vratiti dupli iznos igraču ako pobijedi i trećinu vlasniku ako izgubi.
Prvo, napišimo jednostavan test. Da bismo to učinili, potrebna nam je probna adresa pametnog ugovora sa koje navodno šaljemo gramove na pametni ugovor.
Adresa pametnog ugovora sastoji se od dva broja, 32-bitnog cijelog broja odgovornog za radni lanac i 256-bitnog nenegativnog cijelog jedinstvenog broja računa u ovom radnom lancu. Na primjer, -1 i 12345, ovo je adresa koju ćemo sačuvati u datoteku.
Kopirao sam funkciju za čuvanje adrese iz 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
Pogledajmo kako funkcija funkcionira, ovo će dati razumijevanje kako Fift radi. Pokrenite Fift u interaktivnom načinu.
~/TON/build/crypto/fift -i
Prvo gurnemo -1, 12345 i ime buduće datoteke "sender.addr" na stog:
-1 12345 "sender.addr"
Sljedeći korak je izvršavanje funkcije -rot, koji pomiče stog na takav način da se na vrhu hrpe nalazi jedinstveni broj pametnog ugovora:
"sender.addr" -1 12345
256 u>B pretvara 256-bitni nenegativni cijeli broj u bajtove.
I konačno se bajtovi upisuju u datoteku B>file. Nakon ovoga naš stog je prazan. Stajemo Fift. Fajl je kreiran u trenutnoj fascikli sender.addr. Premjestimo datoteku u kreirani folder test/addresses/.
Napišimo jednostavan test koji će poslati grame pametnom ugovoru. Evo polaganja.
Pogledajmo sada logiku lutrije.
Prvo što radimo je da provjerimo poruku bounced ili ne ako bounced, onda to ignorišemo. bounced znači da će ugovor vratiti gram ako dođe do greške. Nećemo vratiti gram ako se iznenada dogodi greška.
Provjeravamo, ako je stanje manje od pola grama, onda jednostavno prihvaćamo poruku i ignoriramo je.
Zatim analiziramo adresu pametnog ugovora s kojeg je stigla poruka.
Čitamo podatke iz skladišta, a zatim brišemo stare opklade iz historije ako ih ima više od dvadeset. Radi praktičnosti, napisao sam tri dodatne funkcije pack_order(), unpack_order(), remove_old_orders().
Zatim gledamo da li stanje nije dovoljno za plaćanje, onda smatramo da ovo nije opklada, već dopuna i čuvamo dopunu u orders.
Zatim konačno suština pametnog ugovora.
Prvo, ako igrač izgubi, pohranjujemo ga u historiju klađenja, a ako je iznos veći od 3 grama, 1/3 šaljemo vlasniku pametnog ugovora.
Ako igrač dobije, tada šaljemo dupli iznos na adresu igrača, a zatim pohranjujemo podatke o opkladi u historiji.
() recv_internal(int order_amount, cell in_msg_cell, slice in_msg) impure {
var cs = in_msg_cell.begin_parse();
int flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
if (flags & 1) { ;; ignore bounced
return ();
}
if (order_amount < 500000000) { ;; just receive grams without changing state
return ();
}
slice src_addr_slice = cs~load_msg_addr();
(int src_wc, int src_addr) = parse_std_addr(src_addr_slice);
(int stored_seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) = unpack_state();
orders = remove_old_orders(orders, order_seqno);
if (balance() < 2 * order_amount + 500000000) { ;; not enough grams to pay the bet back, so this is re-fill
builder order = pack_order(order_seqno, 1, now(), order_amount, src_wc, src_addr);
orders~udict_set_builder(32, order_seqno, order);
set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins, incoming_amount + order_amount, outgoing_amount, owner_wc, owner_account_id, orders));
return ();
}
if (rand(10) >= 4) {
builder order = pack_order(order_seqno, 3, now(), order_amount, src_wc, src_addr);
orders~udict_set_builder(32, order_seqno, order);
set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins, incoming_amount + order_amount, outgoing_amount, owner_wc, owner_account_id, orders));
if (order_amount > 3000000000) {
send_grams(owner_wc, owner_account_id, order_amount / 3);
}
return ();
}
send_grams(src_wc, src_addr, 2 * order_amount);
builder order = pack_order(order_seqno, 2, now(), order_amount, src_wc, src_addr);
orders~udict_set_builder(32, order_seqno, order);
set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins + 1, incoming_amount, outgoing_amount + 2 * order_amount, owner_wc, owner_account_id, orders));
}
Sada ostaje samo jednostavno, kreirajmo get-metode kako bismo mogli dobiti informacije o stanju ugovora iz vanjskog svijeta (zapravo, pročitati podatke iz njihove memorije pametnih ugovora).
Također sam zaboravio dodati kod koji će obraditi prvi zahtjev koji se pojavi prilikom objavljivanja pametnog ugovora. Odgovarajuće urezivanje. I dalje ispravljeno greška sa slanjem 1/3 iznosa na račun vlasnika.
Sljedeći korak je objavljivanje pametnog ugovora. Kreirajmo folder requests.
Nešto na šta vredi obratiti pažnju. Generiramo skladište pametnog ugovora i ulaznu poruku. Nakon toga se generiše adresa pametnog ugovora, odnosno adresa je poznata i prije objave u TON-u. Zatim na ovu adresu treba poslati nekoliko grama, a tek nakon toga treba poslati datoteku sa samim pametnim ugovorom, jer mreža uzima proviziju za pohranjivanje pametnog ugovora i operacije u njemu (validatori koji pohranjuju i izvršavaju smart ugovor ugovori). Kod možete pogledati ovdje.
Zatim izvršavamo kod za objavljivanje i dobivamo lottery-query.boc fajl pametnog ugovora i adresa.
Šaljemo na adresu 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 grama i nakon nekoliko sekundi izvršimo istu komandu. Za slanje grama koristim službeni novčanik, a možete zamoliti nekoga iz chata za test grame, o čemu ću govoriti na kraju članka.
Sada kreirajmo zahtjeve za interakciju sa pametnim ugovorom.
Tačnije, prvu ćemo ostaviti za promjenu adrese kao samostalan posao, a drugu ćemo uraditi za slanje grama na adresu vlasnika. U stvari, moraćemo da uradimo istu stvar kao u testu za slanje grama.
Ovo je poruka koju ćemo poslati pametnom ugovoru, gdje msg_seqno 165, action 2 i 9.5 grama za slanje.
<b 165 32 u, 2 7 u, 9500000000 Gram, b>
Ne zaboravite da potpišete poruku svojim privatnim ključem lottery.pk, koji je generiran ranije prilikom kreiranja pametnog ugovora. Evo odgovarajućeg urezivanja.
Primanje informacija iz pametnog ugovora pomoću metoda get
Sada pogledajmo kako pokrenuti metode dobivanja pametnog ugovora.
Pokreni lite-client i pokrenite get metode koje smo napisali.
Koristit ćemo lite-client i dobiti metode za prikaz informacija o pametnom ugovoru na web stranici.
Prikazivanje podataka pametnog ugovora na web stranici
Napisao sam jednostavnu web stranicu u Pythonu da prikažem podatke iz pametnog ugovora na zgodan način. Ovdje se neću zadržavati na tome detaljno i objavit ću stranicu u jednom urezivanju.
Zahtjevi prema TON-u su napravljeni od Python uz pomoć lite-client. Radi praktičnosti, stranica je upakovana u Docker i objavljena na Google Cloud-u. Veza.
Pokušavam
Pokušajmo sada poslati grame tamo za dopunu novčanik. Šaljemo 40 grama. I napravimo par opklada radi jasnoće. Vidimo da sajt prikazuje istoriju opklada, trenutni procenat dobitaka i druge korisne informacije.
Članak je ispao mnogo duži nego što sam očekivao, možda je mogao biti kraći, ili možda samo za osobu koja ne zna ništa o TON-u i želi napisati i objaviti ne tako jednostavan pametni ugovor sa mogućnošću interakcije sa to. Možda su se neke stvari mogle jednostavnije objasniti.
Možda su neki aspekti implementacije mogli biti urađeni efikasnije i elegantnije, ali bi tada bilo potrebno još više vremena za pripremu članka. Također je moguće da sam negdje pogriješio ili nešto nisam razumio, pa ako radite nešto ozbiljno, trebate se osloniti na zvaničnu dokumentaciju ili službeni repozitorij sa TON kodom.
Treba napomenuti da s obzirom da je sam TON još uvijek u aktivnoj fazi razvoja, može doći do promjena koje će prekinuti bilo koji od koraka u ovom članku (što se dogodilo dok sam pisao, već je ispravljeno), ali opći pristup je malo je vjerovatno da će se promijeniti.
Neću pričati o budućnosti TON-a. Možda će platforma postati nešto veliko i trebali bismo potrošiti vrijeme proučavajući je i popuniti nišu našim proizvodima sada.
Tu je i Libra iz Facebooka, koja ima potencijalnu publiku korisnika veću od TON-a. Ne znam skoro ništa o Vagi, sudeći po forumu tamo je mnogo više aktivnosti nego u TON zajednici. Iako su programeri i zajednica TON-a više kao underground, što je također cool.
Razgovarajte o TON-u u Telegramu, što je zaista pomoglo da se to shvati u početnoj fazi. Mislim da neće biti greška ako kažem da su svi koji su nešto napisali za TON tu. Tamo možete tražiti i test grama. https://t.me/tondev_ru