ProHoster > Blog > uprava > O tome kako napisati i objaviti pametni ugovor u Telegram Open Network (TON)
O tome kako napisati i objaviti pametni ugovor u Telegram Open Network (TON)
O tome kako napisati i objaviti pametni ugovor u TON-u
O čemu je ovaj članak?
U članku ću govoriti o tome kako sam sudjelovao u prvom (od dva) Telegram blockchain natjecanju, nisam uzeo nagradu i odlučio svoje iskustvo zabilježiti u članku kako ne bi palo u zaborav i možda pomoglo netko.
Budući da nisam želio pisati apstraktni kod, već učiniti nešto što radi, za članak sam napisao pametni ugovor za trenutnu lutriju i web stranicu koja prikazuje podatke pametnog ugovora izravno iz TON-a bez korištenja međupohrane.
Članak će biti koristan onima koji žele napraviti svoj prvi pametni ugovor u TON-u, ali ne znaju odakle početi.
Koristeći lutriju kao primjer, prijeći ću od instaliranja okruženja do objave pametnog ugovora, interakcije s njim i pisanja web stranice za primanje i objavljivanje podataka.
O sudjelovanju u natječaju
Prošlog listopada Telegram je najavio blockchain natjecanje s novim jezicima Fift и FunC. Bilo je potrebno odabrati pisanje bilo kojeg od pet predloženih pametnih ugovora. Mislio sam da bi bilo lijepo raditi nešto drugačije, naučiti jezik i napraviti nešto, čak i ako ne budem morao pisati ništa drugo u budućnosti. Osim toga, tema je stalno na usnama.
Vrijedno je reći da nisam imao iskustva s razvojem pametnih ugovora.
Planirao sam sudjelovati do samog kraja dok ne budem mogao i onda napisati recenzentski članak, ali nisam uspio odmah na prvom. ja napisao novčanik s uključenim višestrukim potpisom FunC i općenito je djelovalo. Uzeo sam to kao osnovu pametni ugovor na Solidityju.
Tada sam mislio da je to sigurno dovoljno da zauzmem barem neko nagradno mjesto. Kao rezultat toga, oko 40 od 60 sudionika postali su dobitnici nagrada, a ja nisam bio među njima. Općenito, nema ništa loše u tome, ali me jedna stvar zasmetala. U trenutku objave rezultata nije bio obavljen pregled testa za moj ugovor, pitao sam sudionike chata ima li još netko da ga nema, nije bilo.
Očito obraćajući pažnju na moje poruke, dva dana kasnije suci su objavili komentar i još mi nije jasno je li im slučajno promakao moj pametni ugovor tijekom ocjenjivanja ili su jednostavno mislili da je toliko loš da ne treba komentar. Postavio sam pitanje na stranici, ali nisam dobio odgovor. Iako nije tajna tko je presudio, smatrao sam nepotrebnim pisati osobne poruke.
Potrošeno je puno vremena na razumijevanje, pa je odlučeno napisati članak. Budući da još nema puno informacija, članak će pomoći uštedjeti vrijeme svima zainteresiranima.
Koncept pametnih ugovora u TON-u
Prije nego bilo što napišete, morate shvatiti s koje strane pristupiti ovoj stvari. Stoga ću vam sada reći od kojih se dijelova sastoji sustav. Točnije, koje dijelove trebate znati da biste napisali barem nekakav ugovor o djelu.
Usredotočit ćemo se na pisanje pametnog ugovora i rad s njim TON Virtual Machine (TVM), Fift и FunC, pa je članak više poput opisa razvoja običnog programa. Ovdje se nećemo zadržavati na tome kako sama platforma funkcionira.
Općenito o tome kako to radi TVM i jezik Fift postoji dobra službena dokumentacija. Tijekom sudjelovanja u natječaju i sada dok pišem trenutni ugovor često sam joj se obraćala.
Glavni jezik na kojem su napisani pametni ugovori je FunC. O tome trenutno ne postoji nikakva dokumentacija, 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 prošla dva natjecanja. 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 objavu. Da biste to učinili, morate napisati 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ćica ćelija”), te, ovisno o tome kako to napišemo, privatni ključ i adresa, koja se generira na temelju koda pametnog ugovora. Grame već možete poslati na adresu pametnog ugovora koji još nije objavljen.
Za objavu pametnog ugovora u TON primljeno .boc datoteka će se morati poslati u blockchain pomoću laganog klijenta (više o tome u nastavku). Ali prije objave morate prenijeti gramove na generiranu adresu, inače pametni ugovor neće biti objavljen. Nakon objave, možete komunicirati s pametnim ugovorom tako da mu šaljete poruke izvana (na primjer, korištenjem laganog klijenta) ili iznutra (na primjer, jedan pametni ugovor šalje drugom poruku unutar TON-a).
Jednom kada shvatimo kako se kod objavljuje, postaje lakše. Otprilike znamo što želimo napisati i kako će naš program raditi. I dok pišemo, tražimo kako je to već implementirano u postojećim pametnim ugovorima ili gledamo implementacijski kod Fift и FunC u službenom repozitoriju, ili pogledajte u službenoj dokumentaciji.
Vrlo često sam tražio ključne riječi u Telegram chatu gdje su se okupljali svi sudionici natjecanja i zaposlenici Telegrama, i dogodilo se da su se tijekom natjecanja svi skupili i počeli raspravljati o Fiftu i FunC-u. Link na kraju članka.
Vrijeme je da prijeđemo s teorije na praksu.
Priprema okruženja za rad s TON-om
Napravio sam sve što će biti opisano u članku o MacOS-u i dva puta provjerio u čistom Ubuntu 18.04 LTS na Dockeru.
Prvo što trebate učiniti je preuzeti i instalirati lite-client s kojim možete slati zahtjeve na TON.
Upute na službenim stranicama dosta detaljno i jasno opisuju postupak instalacije te izostavljaju neke detalje. Ovdje slijedimo upute, usput instalirajući ovisnosti koje nedostaju. Nisam sam kompajlirao svaki projekt i instalirao iz službenog repozitorija Ubuntu (na MacOS-u koji sam koristio brew).
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json
Ako je izgradnja bila uspješna, nakon pokretanja vidjet ćete zapisnik veze laganog 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 vidjeti koje su naredbe dostupne.
help
Nabrojimo 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.
Provedba
Ideja
Kao što sam već napisao, pametni ugovor koji pišemo je lutrija.
Štoviše, ovo nije lutrija u kojoj treba kupiti listić i čekati sat, dan ili mjesec, već instant lutrija u kojoj korisnik prebacuje na ugovornu adresu N grama, i odmah ga dobije natrag 2 * N grama ili gubi. Napravit ćemo vjerojatnost dobitka na oko 40%. Ako nema dovoljno grama za plaćanje, transakciju ćemo smatrati dopunom.
Štoviše, važno je da se oklade mogu vidjeti u stvarnom vremenu iu prikladnom obliku, tako da korisnik može odmah shvatiti je li dobio ili izgubio. Stoga morate napraviti web stranicu koja će prikazivati oklade i rezultate izravno iz TON-a.
Pisanje pametnog ugovora
Radi praktičnosti, istaknuo sam kôd za FunC; dodatak se može pronaći i instalirati u pretraživanju Visual Studio Code; ako odjednom želite nešto dodati, učinio sam dodatak javno dostupnim. Također, netko je prije napravio plugin za rad s Fiftom, također ga možete instalirati i pronaći u VSC-u.
Kreirajmo odmah spremište u koje ćemo upisati međurezultate.
Kako bismo si olakšali život, napisat ćemo pametni ugovor i testirati ga lokalno dok ne bude spreman. Tek nakon toga ćemo ga objaviti u TON-u.
Pametni ugovor ima dvije vanjske metode kojima se može pristupiti. Prvi, recv_external() ova funkcija se izvršava kada zahtjev na ugovor dolazi iz vanjskog svijeta, odnosno ne iz TON-a, na primjer, kada mi sami generiramo poruku i šaljemo je preko lite-klijenta. Drugi, recv_internal() to 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 se objavi, ali u njemu nema funkcionalnog opterećenja.
Ovdje moramo objasniti što je to slice. Svi podaci pohranjeni u TON Blockchainu su zbirka TVM cell ili jednostavno cell, u takvu ćeliju možete pohraniti do 1023 bita podataka i do 4 poveznice na druge ćelije.
TVM cell slice ili slice ovo je dio postojećeg cell koristi se za njegovu analizu, bit će jasno kasnije. Glavno nam je da možemo transferirati 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.
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 prvi red povezuje Asm.fif, ovo je kod napisan u Fiftu za Fift asembler.
Budući da želimo pokrenuti i testirati pametni ugovor lokalno, izradit ćemo datoteku lottery-test-suite.fif i tamo kopirajte kompilirani kod, zamjenjujući zadnji redak u njemu, koji zapisuje kod pametnog ugovora u konstantu codeda biste ga zatim prenijeli na virtualni stroj:
"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, a sada u istu datoteku dodajmo kod koji ćemo koristiti za pokretanje TVM-a.
В c7 bilježimo kontekst, odnosno podatke s kojima će se pokrenuti TVM (ili stanje mreže). Još tijekom natjecanja jedan od programera pokazao je kako se stvara c7 i kopirao sam. U ovom ćemo članku možda morati promijeniti rand_seed budući da generiranje slučajnog broja ovisi o njemu i ako se ne promijeni, svaki će se put vratiti isti broj.
recv_internal и recv_external konstante s vrijednostima 0 i -1 bit će odgovorne za pozivanje odgovarajućih funkcija u pametnom ugovoru.
Sada smo spremni za izradu 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 u nju upišite prazan cell, ovo će biti pohrana pametnog ugovora.
message Ovo je poruka koju ćemo prenijeti pametnom kontaktu izvana. Također ćemo ga za sada učiniti praznim.
Odlično, napisali smo prvu radnu verziju pametnog ugovora.
Sada moramo dodati funkcionalnost. Najprije se pozabavimo porukama koje dolaze iz vanjskog svijeta recv_external()
Programer sam odabire 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 samo vlasnik ugovora može slati vanjske poruke njemu.
drugo, kada pošaljemo valjanu poruku TON-u, želimo da se to dogodi točno jednom, a kada ponovno pošaljemo istu poruku, pametni ugovor je odbija.
Dakle, gotovo svaki ugovor rješava ova dva problema, budući da naš ugovor prihvaća vanjske poruke, moramo voditi računa i o tome.
Učinit ćemo to obrnutim redoslijedom. Prvo riješimo problem s ponavljanjem, ako je ugovor već primio takvu poruku i obradio ju, neće je izvršiti drugi put. A onda ćemo problem riješiti tako da samo određeni krug ljudi može slati poruke na pametni ugovor.
Postoje različiti načini rješavanja problema s dupliciranim porukama. Evo kako ćemo to učiniti. U pametnom ugovoru inicijaliziramo brojač primljenih poruka s početnom vrijednošću 0. U svakoj poruci u pametni ugovor dodat ćemo trenutnu vrijednost brojača. Ako vrijednost brojača u poruci ne odgovara vrijednosti u pametnom ugovoru, tada je ne obrađujemo; ako jest, tada je obrađujemo i povećavamo brojač u pametnom ugovoru za 1.
Vratimo se na lottery-test-suite.fif i tome dodajte drugi test. Ako pošaljemo netočan broj, kôd bi trebao izbaciti iznimku. Na primjer, neka podaci o ugovoru pohrane 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"
Idemo lansirati.
~/TON/build/crypto/fift -s lottery-test-suite.fif
I vidjet ćemo da je test izvršen s 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 trebao bi izgledati по ссылке.
Dodajmo sada 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 provjeriti sadrži li poruka podatke, ako ne, jednostavno izlazimo.
Zatim analiziramo poruku. in_msg~load_uint(32) učitava broj 165, 32-bitni unsigned int iz prenesene poruke.
Zatim učitavamo 32 bita iz pohrane pametnog ugovora. Provjeravamo odgovara li učitani broj proslijeđenom; ako ne, bacamo iznimku. U našem slučaju, budući da prolazimo ne-usklađenost, treba baciti iznimku.
Kopirajte dobiveni kod u lottery-test-suite.fif, ne zaboravljajući zamijeniti posljednji redak.
Provjeravamo je li test prošao:
~/TON/build/crypto/fift -s lottery-test-suite.fif
Upravo ovdje Možete vidjeti odgovarajuću predaju s trenutnim rezultatima.
Imajte na umu da je nezgodno stalno kopirati kompajlirani kod pametnog ugovora u datoteku s testovima, pa ćemo napisati skriptu koja će umjesto nas zapisivati kod u konstantu, a kompajlirani kod ćemo jednostavno povezati s našim testovima pomoću "include".
Stvorite datoteku u mapi projekta build.sh sa sljedećim sadržajem.
Sada samo pokrenite našu skriptu za sastavljanje ugovora. Ali osim ovoga, trebamo ga zapisati u konstantu code. Stoga ćemo stvoriti novu datoteku lotter-compiled-for-test.fif, koje ćemo uključiti u datoteku lottery-test-suite.fif.
Dodajmo skirpt kod u sh, koji će jednostavno duplicirati kompajliranu datoteku lotter-compiled-for-test.fif i promijenite posljednji redak 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 se generirati lottery-compiled-for-test.fif, koje ćemo uključiti u naš lottery-test-suite.fif
В lottery-test-suite.fif obrišite šifru ugovora i dodajte redak "lottery-compiled-for-test.fif" include.
Provodimo testove kako bismo provjerili prolaze li.
~/TON/build/crypto/fift -s lottery-test-suite.fif
Sjajno, sada da bismo automatizirali pokretanje testova, kreirajmo datoteku test.sh, koji će se prvi izvršiti build.sh, a zatim pokrenite testove.
Učinimo to test.sh i pokrenite ga da provjerite rade li testovi.
chmod +x ./test.sh
./test.sh
Provjeravamo da je ugovor sastavljen i da li su testovi izvršeni.
Odlično, sada na pokretanju test.sh Testovi će se odmah sastaviti i pokrenuti. Ovdje je poveznica na počiniti.
U redu, prije nego što nastavimo, učinimo još jednu stvar radi praktičnosti.
Kreirajmo mapu build gdje ćemo pohraniti kopirani ugovor i njegov klon zapisan u konstantu lottery-compiled.fif, lottery-compiled-for-test.fif. Kreirajmo i mapu test gdje će testna datoteka biti pohranjena? lottery-test-suite.fif i potencijalno druge prateće datoteke. Link na relevantne promjene.
Nastavimo razvijati pametni ugovor.
Zatim bi trebao postojati test koji provjerava je li poruka primljena i ažurira li se brojač u trgovini kada pošaljemo točan broj. Ali to ćemo učiniti kasnije.
Razmislimo sada o tome koju strukturu podataka i koji podaci trebaju biti pohranjeni u pametnom ugovoru.
Opisat ć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 trebate napisati dvije funkcije. Pozovimo prvu pack_state(), koji će pakirati podatke za naknadno spremanje u pohranu pametnog ugovora. Nazovimo drugu unpack_state() čitat će i vraćati podatke iz pohrane.
_ 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. Sredit će se ovdje je međurezultat.
Za spremanje podataka morat ćete pozvati ugrađenu funkciju set_data() i zapisivat će podatke iz pack_state() u pohrani pametnih ugovora.
Sada kada imamo prikladne funkcije za pisanje i čitanje podataka, možemo krenuti dalje.
Moramo provjeriti je li poruku koja dolazi izvana potpisao vlasnik ugovora (ili drugi korisnik koji ima pristup privatnom ključu).
Kada objavimo pametni ugovor, možemo ga inicijalizirati podacima koji su nam potrebni u pohrani, a koji će biti spremljeni za buduću upotrebu. Tamo ćemo zabilježiti javni ključ kako bismo mogli provjeriti je li dolazna poruka potpisana odgovarajućim privatnim ključem.
Prije nastavka stvorimo privatni ključ i upišimo ga test/keys/owner.pk. Da bismo to učinili, pokrenimo Fift u interaktivnom načinu rada i izvršimo četiri naredbe.
`newkeypair` генерация публичного и приватного ключа и запись их в стек.
`drop` удаления из стека верхнего элемента (в данном случае публичный ключ)
`.s` просто посмотреть что лежит в стеке в данный момент
`"owner.pk" B>file` запись приватного ключа в файл с именем `owner.pk`.
`bye` завершает работу с Fift.
Kreirajmo mapu keys unutar mape test i tamo napiš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 datoteku u trenutnoj mapi owner.pk.
Javni ključ uklanjamo sa steka i po potrebi ga možemo dobiti iz privatnog.
Sada moramo napisati ovjeru potpisa. Počnimo s testom. Prvo čitamo privatni ključ iz datoteke pomoću funkcije file>B i zapišite ga u varijablu owner_private_key, zatim pomoću funkcije priv>pub pretvoriti privatni ključ u javni ključ i upisati rezultat owner_public_key.
Kao rezultat toga, poruka koju ćemo poslati pametnom ugovoru bilježi se u varijabli message_to_send, o funkcijama hashu, ed25519_sign_uint možeš čitati u dokumentaciji Fift.
Ovdje tako Datoteka s testovima trebala bi izgledati ovako u ovoj fazi.
Pokrenimo test i neće uspjeti, pa ćemo promijeniti pametni ugovor tako da može primati poruke ovog formata i verificirati potpis.
Prvo izbrojimo 512 bita potpisa iz poruke i zapišemo ga u varijablu, zatim izbrojimo 32 bita varijable brojača.
Budući da imamo funkciju za čitanje podataka iz pohrane pametnih ugovora, koristit ćemo se njome.
Sljedeća je provjera brojača prenesenog sa pohranom i provjera potpisa. Ako nešto ne odgovara, onda bacamo iznimku 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 pada. Iz dva razloga, nema dovoljno bitova u poruci i nema dovoljno bitova u pohrani, pa se kod ruši prilikom parsiranja. Moramo dodati potpis poruci koju šaljemo i kopirati pohranu iz posljednjeg testa.
U drugom testu ćemo dodati potpis poruke i promijeniti pohranu pametnog ugovora. Ovdje tako datoteka s testovima trenutno izgleda ovako.
Napišimo četvrti test, u kojem ćemo poslati poruku potpisanu tuđim privatnim ključem. Stvorimo još jedan privatni ključ i spremimo ga u datoteku not-owner.pk. Poruku ćemo potpisati ovim privatnim ključem. Pokrenimo testove i pobrinimo se da svi testovi prođu. Počiniti u ovom trenutku.
Sada konačno možemo prijeći na implementaciju logike pametnog ugovora.
В recv_external() prihvatit ćemo dvije vrste poruka.
Budući da će naš ugovor akumulirati gubitke igrača, ovaj novac mora biti prebačen tvorcu lutrije. Adresa novčanika kreatora lutrije bilježi se u pohrani prilikom kreiranja ugovora.
Za svaki slučaj treba nam mogućnost promjene adrese na koju šaljemo grame gubitnika. Također bismo trebali moći slati grame s lutrije na adresu vlasnika.
Počnimo s prvim. Napišimo prvo test koji će provjeriti je li pametni ugovor nakon slanja poruke spremio novu adresu u pohranu. Napominjemo da u poruci osim šaltera i nove adrese prenosimo i action 7-bitni cijeli broj koji nije negativan, ovisno o njemu ćemo odabrati 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 pohrana pametnog ugovora deserijalizirana storage u Petici. Deserijalizacija varijabli opisana je u Fift dokumentaciji.
Pokrenimo test i uvjerimo se da ne uspijeva. Sada dodajmo logiku da promijenimo adresu vlasnika lutrije.
U pametnom ugovoru nastavljamo analizirati message, čitaj u action. Podsjetimo, imat ćemo dva action: promijeni adresu i pošalji grame.
Zatim čitamo novu adresu vlasnika ugovora i spremamo je u pohranu.
Pokrećemo testove i vidimo da treći test pada. Ruši se zbog činjenice da ugovor sada dodatno analizira 7 bitova iz poruke, koji nedostaju u testu. Dodajte nepostojeći u poruku action. Provedimo testove i vidimo da sve prolazi. ovdje posvetiti se promjenama. Sjajno.
Sada napišimo logiku za slanje navedenog broja grama na prethodno spremljenu adresu.
Prvo, napišimo test. Napisat ćemo dva testa, jedan kada nema dovoljno ravnoteže, drugi kada sve treba uspješno proći. Testovi se mogu pogledati u ovom obvezi.
Sada dodajmo kod. Prvo, napišimo dvije pomoćne metode. Prva metoda dobivanja je saznati trenutno stanje pametnog ugovora.
int balance() inline_ref method_id {
return get_balance().pair_first();
}
A drugi je za slanje grama na drugi pametni ugovor. U potpunosti 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šimo logiku. Prvo analiziramo broj grama iz poruke. Zatim provjeravamo ravnotežu, ako nije dovoljna bacamo iznimku. Ako je sve u redu, šaljemo grame na spremljenu adresu i ažuriramo brojač.
Ovdje tako izgleda kao pametni ugovor u ovom trenutku. Pokrenimo testove i uvjerimo se da prolaze.
Inače, provizija se odbija od pametnog ugovora svaki put za obrađenu poruku. Kako bi poruke pametnog ugovora izvršile zahtjev, nakon osnovnih provjera potrebno je nazvati accept_message().
Sada prijeđimo na interne poruke. Zapravo, prihvatit ćemo samo grame i vratiti dvostruki iznos igraču ako pobijedi i trećinu vlasniku ako izgubi.
Prvo, napišimo jednostavan test. Da bismo to učinili, potrebna nam je testna adresa pametnog ugovora s koje navodno šaljemo gramove pametnom ugovoru.
Adresa pametnog ugovora sastoji se od dva broja, 32-bitnog cijelog broja odgovornog za radni lanac i 256-bitnog nenegativnog cijelog broja jedinstvenog broja računa u ovom radnom lancu. Na primjer, -1 i 12345, ovo je adresa koju ćemo spremiti u datoteku.
Kopirao sam funkciju za spremanje 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 radi, to će vam dati razumijevanje kako radi Fift. Pokrenite Fift u interaktivnom načinu rada.
~/TON/build/crypto/fift -i
Prvo gurnemo -1, 12345 i naziv 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 stoga nalazi jedinstveni broj pametnog ugovora:
"sender.addr" -1 12345
256 u>B pretvara 256-bitni nenegativan cijeli broj u bajtove.
I na kraju se bajtovi zapisuju u datoteku B>file. Nakon ovoga naš stog je prazan. Zaustavljamo se Fift. Datoteka je stvorena u trenutnoj mapi sender.addr. Premjestimo datoteku u stvorenu mapu test/addresses/.
Napišimo jednostavan test koji će poslati gramove na pametni ugovor. Evo obveze.
Pogledajmo sada logiku lutrije.
Prvo što radimo je provjeriti poruku bounced ili ne ako bounced, onda ga ignoriramo. bounced znači da će ugovor vratiti grame ako se dogodi neka greška. Nećemo vratiti grame ako iznenada dođe do pogreške.
Provjeravamo, ako je stanje manje od pola grama, tada jednostavno prihvaćamo poruku i ignoriramo je.
Zatim analiziramo adresu pametnog ugovora s kojeg je poruka stigla.
Očitavamo podatke iz pohrane i brišemo stare oklade iz povijesti ako ih ima više od dvadeset. Radi praktičnosti, napisao sam tri dodatne funkcije pack_order(), unpack_order(), remove_old_orders().
Zatim gledamo ako saldo nije dovoljan za isplatu, tada smatramo da ovo nije oklada, već nadopuna i spremamo nadopunu u orders.
Zatim konačno bit pametnog ugovora.
Prvo, ako igrač izgubi, to spremamo u povijest klađenja, a ako je iznos veći od 3 grama, šaljemo 1/3 vlasniku pametnog ugovora.
Ako igrač pobijedi, tada šaljemo dvostruki iznos na adresu igrača, a potom podatke o okladi spremamo u povijest.
() 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 sve što preostaje je jednostavno, kreirajmo get-metode kako bismo mogli dobiti informacije o stanju ugovora iz vanjskog svijeta (u stvari, pročitati podatke iz njihove pohrane pametnih ugovora).
Dodajmo get metode. U nastavku ćemo pisati o tome kako dobiti informacije o pametnom ugovoru.
Također sam zaboravio dodati kod koji će obraditi prvi zahtjev koji se pojavi prilikom objave pametnog ugovora. Odgovarajuće preuzimanje. I dalje ispravljeno bug sa slanjem 1/3 iznosa na račun vlasnika.
Sljedeći korak je objava pametnog ugovora. Kreirajmo mapu requests.
Nešto vrijedno pažnje. Generiramo pohranu pametnog ugovora i ulaznu poruku. Nakon toga se generira adresa pametnog ugovora, odnosno adresa je poznata i prije objave u TON-u. Zatim je potrebno poslati nekoliko grama na ovu adresu, a tek nakon toga poslati datoteku sa samim pametnim ugovorom, budući da mreža uzima proviziju za pohranu pametnog ugovora i operacije u njemu (validatori koji pohranjuju i izvršavaju pametne ugovore ). Kod možete pogledati ovdje.
Zatim izvršavamo kod za objavljivanje i dobivamo lottery-query.boc datoteku i adresu pametnog ugovora.
Šaljemo na adresu 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 Gram i nakon nekoliko sekundi izvršavamo istu naredbu. Za slanje grama koristim službeni novčanik, a za probne grame možete pitati nekoga s chata o čemu ću govoriti na kraju članka.
Kreirajmo sada zahtjeve za interakciju s pametnim ugovorom.
Točnije, prvu za promjenu adrese ostavit ćemo kao samostalan rad, a drugu za slanje grama na adresu vlasnika. Zapravo, morat ćemo učiniti isto što i 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 potpisati poruku svojim privatnim ključem lottery.pk, koji je generiran ranije prilikom izrade pametnog ugovora. Ovdje je odgovarajući commit.
Primanje informacija iz pametnog ugovora korištenjem 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 mjestu.
Prikaz podataka pametnog ugovora na web stranici
Napisao sam jednostavnu web stranicu u Pythonu za prikaz podataka iz pametnog ugovora na prikladan način. Ovdje se neću detaljno zadržavati na tome i objavit ću stranicu u jednom predanju.
Zahtjevi za TON šalju se od Python uz pomoć lite-client. Radi praktičnosti, stranica je upakirana u Docker i objavljena na Google Cloudu. Veza.
Težak
Sada pokušajmo tamo poslati grame za nadopunu torbica. Poslat ćemo 40 grama. I uložimo nekoliko oklada radi jasnoće. Vidimo da stranica prikazuje povijest oklada, trenutni postotak dobitaka i druge korisne informacije.
Članak je ispao puno duži nego što sam očekivao, možda je mogao biti i 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 s mogućnošću interakcije s to. Možda su se neke stvari mogle jednostavnije objasniti.
Možda su neki aspekti implementacije mogli biti odrađeni učinkovitije i elegantnije, ali bi onda za pripremu članka trebalo još više vremena. Također je moguće da sam negdje pogriješio ili nešto nisam razumio, pa ako radite nešto ozbiljno, morate se osloniti na službenu dokumentaciju ili službeni repozitorij s kodom TON.
Treba napomenuti da budući 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 vjerojatno da će se promijeniti.
Neću govoriti o budućnosti TON-a. Možda će platforma postati nešto veliko i trebali bismo provesti vrijeme proučavajući je i ispuniti nišu s našim proizvodima sada.
Tu je i Libra s Facebooka, koja ima potencijalnu publiku korisnika veću od TON. O Libri ne znam skoro ništa, sudeći po forumu tamo je puno više aktivnosti nego u TON zajednici. Iako su programeri i zajednica TON-a više nalik undergroundu, što je također cool.
Razgovarajte o TON-u u Telegramu, što je stvarno pomoglo da to shvatite u početnoj fazi. Mislim da neće pogriješiti ako kažem da su tu svi koji su nešto napisali za TON. Tamo možete zatražiti i testne grame. https://t.me/tondev_ru