ProHoster > Blog > Uprava > O tem, kako napisati in objaviti pametno pogodbo v odprtem omrežju Telegram (TON)
O tem, kako napisati in objaviti pametno pogodbo v odprtem omrežju Telegram (TON)
O tem, kako napisati in objaviti pametno pogodbo v TON
O čem govori ta članek?
V članku bom govoril o tem, kako sem sodeloval v prvem (od dveh) tekmovanju Telegram blockchain, nisem prejel nagrade in sem se odločil, da svojo izkušnjo zapišem v članek, da ne pade v pozabo in morda pomaga nekdo.
Ker nisem želel pisati abstraktne kode, ampak narediti nekaj delujočega, sem za članek napisal pametno pogodbo za takojšnjo loterijo in spletno mesto, ki prikazuje podatke pametne pogodbe neposredno iz TON brez uporabe vmesnega pomnilnika.
Članek bo koristen za tiste, ki želijo skleniti svojo prvo pametno pogodbo v TON, vendar ne vedo, kje začeti.
Če uporabim loterijo kot primer, bom šel od namestitve okolja do objave pametne pogodbe, interakcije z njo in pisanja spletne strani za prejemanje in objavo podatkov.
O sodelovanju na tekmovanju
Oktobra lani je Telegram napovedal tekmovanje v blockchainu z novimi jeziki Fift и FunC. Izbirati je bilo treba med pisanjem katere koli od petih predlaganih pametnih pogodb. Mislil sem, da bi bilo lepo narediti nekaj drugačnega, se naučiti jezika in narediti nekaj, tudi če mi v prihodnosti ne bo treba pisati ničesar drugega. Poleg tega je tema nenehno na ustih.
Treba je povedati, da nisem imel izkušenj z razvojem pametnih pogodb.
Načrtoval sem sodelovati do samega konca, dokler mi ne bo uspelo in nato napisati recenzentski članek, a mi je takoj pri prvem spodletelo. jaz napisal denarnico z vključenim večpodpisom FunC in na splošno je delovalo. Vzel sem ga za osnovo pametna pogodba na Solidity.
Takrat sem mislil, da je to zagotovo dovolj za vsaj kakšno nagradno mesto. Posledično je približno 40 od 60 udeležencev postalo nagrajencev in mene ni bilo med njimi. Na splošno s tem ni nič narobe, zmotila pa me je ena stvar. V času objave rezultatov pregled testa za mojo pogodbo še ni bil opravljen, udeležence klepeta sem vprašal, če je še kdo, ki ga nima, ni ga bilo.
Očitno pozorni na moja sporočila so sodniki čez dva dni objavili komentar in še vedno ne razumem, ali so med ocenjevanjem slučajno spregledali mojo pametno pogodbo ali pa so preprosto mislili, da je tako slaba, da ne potrebuje komentarja. Na strani sem postavil vprašanje, vendar nisem prejel odgovora. Čeprav ni skrivnost, kdo je sodil, se mi je zdelo nepotrebno pisati osebna sporočila.
Veliko časa je bilo porabljenega za razumevanje, zato je bilo odločeno napisati članek. Ker še ni veliko informacij, bo ta članek pomagal prihraniti čas vsem zainteresiranim.
Koncept pametnih pogodb v TON
Preden karkoli napišete, morate ugotoviti, s katere strani se lotiti te stvari. Zato vam bom zdaj povedal, iz katerih delov je sestavljen sistem. Natančneje, katere dele morate poznati, da napišete vsaj kakšno pogodbo o delu.
Osredotočili se bomo na pisanje pametne pogodbe in delo z TON Virtual Machine (TVM), Fift и FunC, zato je članek bolj kot opis razvoja običajnega programa. Tukaj se ne bomo ukvarjali s tem, kako platforma sama deluje.
Na splošno o tem, kako deluje TVM in jezik Fift obstaja dobra uradna dokumentacija. Med sodelovanjem v natečaju in zdaj med pisanjem trenutne pogodbe sem se pogosto obračala nanjo.
Glavni jezik, v katerem so napisane pametne pogodbe, je FunC. O tem trenutno ni nobene dokumentacije, zato, da bi kaj napisali, morate preučiti primere pametnih pogodb iz uradnega repozitorija in implementacijo samega jezika tam, poleg tega pa si lahko pogledate primere pametnih pogodb iz zadnjih dveh tekmovanja. Povezave na koncu članka.
Recimo, da smo že napisali pametno pogodbo za FunC, nato kodo prevedemo v Fift assembler.
Sestavljeno pametno pogodbo je treba še objaviti. Če želite to narediti, morate vpisati funkcijo Fift, ki bo kot vhod vzel kodo pametne pogodbe in nekatere druge parametre, izhod pa bo datoteka s pripono .boc (kar pomeni »vreča celic«), ter glede na to, kako zapišemo, zasebni ključ in naslov, ki se generira na podlagi kode pametne pogodbe. Grame že lahko pošljete na naslov pametne pogodbe, ki še ni objavljena.
Za objavo pametne pogodbe v TON prejeli .boc datoteko bo treba poslati v blockchain z uporabo lahkega odjemalca (več o tem spodaj). Toda pred objavo morate prenesti grame na generirani naslov, sicer pametna pogodba ne bo objavljena. Po objavi lahko komunicirate s pametno pogodbo tako, da ji pošiljate sporočila od zunaj (na primer z uporabo lahkega odjemalca) ali od znotraj (na primer ena pametna pogodba drugi pošlje sporočilo znotraj TON).
Ko razumemo, kako je koda objavljena, postane lažje. Približno vemo, kaj želimo napisati in kako bo naš program deloval. In med pisanjem iščemo, kako je to že implementirano v obstoječih pametnih pogodbah, ali pa pogledamo implementacijsko kodo Fift и FunC v uradnem repozitoriju ali poglejte v uradno dokumentacijo.
Zelo pogosto sem iskal ključne besede v klepetu Telegram, kjer so se zbrali vsi udeleženci tekmovanja in zaposleni v Telegramu, in zgodilo se je, da so se med tekmovanjem vsi zbrali tam in začeli razpravljati o Fiftu in FunC-u. Povezava na koncu članka.
Čas je, da preidemo od teorije k praksi.
Priprava okolja za delo s TON
Naredil sem vse, kar bo opisano v članku o MacOS-u, in dvakrat preveril v čistem Ubuntu 18.04 LTS na Dockerju.
Prva stvar, ki jo morate storiti, je prenesti in namestiti lite-client s katerim lahko pošiljate zahteve na TON.
Navodila na uradni spletni strani opisujejo postopek namestitve precej natančno in jasno ter izpuščajo nekatere podrobnosti. Tukaj sledimo navodilom in med potjo namestimo manjkajoče odvisnosti. Vsakega projekta nisem sam prevedel in namestil iz uradnega repozitorija Ubuntu (na MacOS, ki sem ga uporabljal brew).
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json
Če je bila gradnja uspešna, boste po zagonu videli dnevnik povezave lahkega odjemalca z vozliščem.
[ 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)
...
Lahko zaženete ukaz help in si oglejte, kateri ukazi so na voljo.
help
Naštejmo ukaze, ki jih bomo uporabili v tem č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-методы смартконтракта.
Zdaj smo pripravljeni napisati samo pogodbo.
Реализация
Ideja
Kot sem napisal zgoraj, je pametna pogodba, ki jo pišemo, loterija.
Poleg tega to ni loterija, pri kateri morate kupiti listek in počakati eno uro, dan ali mesec, ampak takojšnja, pri kateri uporabnik nakaže na pogodbeni naslov N gramov in ga takoj dobi nazaj 2 * N gramov ali izgubi. Verjetnost zmage bomo naredili približno 40-odstotno. Če za plačilo ni dovolj gramov, bomo transakcijo obravnavali kot polnjenje.
Poleg tega je pomembno, da so stave vidne v realnem času in v priročni obliki, tako da lahko uporabnik takoj razume, ali je zmagal ali izgubil. Zato morate narediti spletno stran, ki bo prikazovala stave in rezultate neposredno iz TON.
Pisanje pametne pogodbe
Za udobje sem izpostavil kodo za FunC; vtičnik je mogoče najti in namestiti v iskalniku Visual Studio Code; če nenadoma želite nekaj dodati, sem vtičnik dal javno dostopen. Prav tako je nekdo prej naredil plugin za delo s Fiftom, lahko ga tudi namestite in najdete v VSC.
Takoj ustvarimo repozitorij, kamor bomo objavili vmesne rezultate.
Da bi nam olajšali življenje, bomo napisali pametno pogodbo in jo testirali lokalno, dokler ne bo pripravljena. Šele po tem ga bomo objavili v TON.
Pametna pogodba ima dve zunanji metodi, do katerih je mogoče dostopati. Prvič, recv_external() ta funkcija se izvede, ko pride zahteva za pogodbo iz zunanjega sveta, torej ne iz TON-a, na primer, ko sami generiramo sporočilo in ga pošljemo prek lite-klienta. Drugič, recv_internal() to je takrat, ko se znotraj samega TON katera koli pogodba nanaša na našo. V obeh primerih lahko funkciji posredujete parametre.
Začnimo s preprostim primerom, ki bo deloval, če bo objavljen, vendar v njem ni funkcionalne obremenitve.
Tukaj moramo pojasniti, kaj je to slice. Vsi podatki, shranjeni v TON Blockchain, so zbirka TVM cell ali pa preprosto cell, v tako celico lahko shranite do 1023 bitov podatkov in do 4 povezave do drugih celic.
TVM cell slice ali slice ta je del obstoječega cell se uporablja za njegovo razčlenjevanje, bo jasno kasneje. Za nas je glavno, da lahko prestopimo slice in glede na vrsto sporočila obdela podatke v recv_external() ali recv_internal().
impure — ključna beseda, ki označuje, da funkcija spreminja podatke pametne pogodbe.
Shranimo kodo pogodbe lottery-code.fc in sestavite.
V sistem smo prevedli kodo Fift 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
Lahko se lansira lokalno, za to bomo pripravili okolje.
Upoštevajte, da je prva vrstica povezana Asm.fif, to je koda, napisana v Fiftu za zbirnik Fift.
Ker želimo pametno pogodbo izvajati in testirati lokalno, bomo ustvarili datoteko lottery-test-suite.fif in tja kopirajte prevedeno kodo, tako da zamenjate zadnjo vrstico v njej, ki zapiše kodo pametne pogodbe v konstanto codeda ga nato prenesete v 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
Zaenkrat se zdi jasno, zdaj pa v isto datoteko dodamo kodo, ki jo bomo uporabili za zagon TVM.
В c7 zabeležimo kontekst, torej podatke, s katerimi se bo TVM (ali stanje omrežja) zagnal. Že med tekmovanjem je eden od razvijalcev pokazal, kako se ustvarja c7 in sem kopiral. V tem članku bomo morda morali spremeniti rand_seed ker je generiranje naključnega števila odvisno od tega in če se ne spremeni, bo vsakič vrnjeno isto število.
recv_internal и recv_external konstante z vrednostmi 0 in -1 bodo odgovorne za klicanje ustreznih funkcij v pametni pogodbi.
Zdaj smo pripravljeni ustvariti prvi test za našo prazno pametno pogodbo. Zaradi jasnosti bomo za zdaj vse teste dodali v isto datoteko lottery-test-suite.fif.
Ustvarimo spremenljivko storage in vanj vpišite prazno cell, bo to shramba pametnih pogodb.
message To je sporočilo, ki ga bomo od zunaj posredovali pametnemu kontaktu. Zaenkrat ga bomo naredili tudi praznega.
Super, napisali smo prvo delujočo različico pametne pogodbe.
Zdaj moramo dodati funkcionalnost. Najprej se lotimo sporočil, ki prihajajo iz zunanjega sveta recv_external()
Razvijalec sam izbere obliko sporočila, ki jo pogodba lahko sprejme.
Ampak običajno
najprej želimo zaščititi našo pogodbo pred zunanjim svetom in narediti tako, da ji lahko samo lastnik pogodbe pošilja zunanja sporočila.
drugič, ko pošljemo veljavno sporočilo TON-u, želimo, da se to zgodi natanko enkrat in ko pošljemo isto sporočilo znova, ga pametna pogodba zavrne.
Torej skoraj vsaka pogodba rešuje ta dva problema, ker naša pogodba sprejema zunanja sporočila, moramo poskrbeti tudi za to.
Naredili bomo v obratnem vrstnem redu. Najprej rešimo problem s ponavljanjem, če je pogodba že prejela takšno sporočilo in ga obdelala, ga drugič ne bo izvršila. In potem bomo problem rešili tako, da bo le določen krog ljudi lahko pošiljal sporočila na pametno pogodbo.
Težavo s podvojenimi sporočili lahko rešite na različne načine. Evo, kako bomo to storili. V pametni pogodbi inicializiramo števec prejetih sporočil z začetno vrednostjo 0. V vsakem sporočilu v pametno pogodbo bomo dodali trenutno vrednost števca. Če se vrednost števca v sporočilu ne ujema z vrednostjo v pametni pogodbi, je ne obdelamo, če se, jo obdelamo in povečamo števec v pametni pogodbi za 1.
Vrnimo se k lottery-test-suite.fif in mu dodajte drugi test. Če pošljemo napačno številko, bi morala koda sprožiti izjemo. Na primer, naj pogodbeni podatki shranijo 166, mi pa bomo poslali 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"
Zaženimo.
~/TON/build/crypto/fift -s lottery-test-suite.fif
In videli bomo, da je test izveden z napako.
[ 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
Na tej stopnji lottery-test-suite.fif bi moralo izgledati по ссылке.
Zdaj pa pametni pogodbi dodamo logiko števca 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 sporočilo, ki ga pošiljamo.
Najprej preverimo, ali sporočilo vsebuje podatke, če ne, preprosto zapustimo.
Nato razčlenimo sporočilo. in_msg~load_uint(32) naloži številko 165, 32-bitno unsigned int iz poslanega sporočila.
Nato naložimo 32 bitov iz pomnilnika pametnih pogodb. Preverimo, ali se naložena številka ujema s posredovano; če ne, vržemo izjemo. V našem primeru, ker gremo mimo neujemanja, je treba vrniti izjemo.
Kopirajte dobljeno kodo v lottery-test-suite.fif, ne pozabite zamenjati zadnje vrstice.
Preverimo, ali je test uspešen:
~/TON/build/crypto/fift -s lottery-test-suite.fif
Tukaj Ogledate si lahko ustrezno objavo s trenutnimi rezultati.
Upoštevajte, da je neprijetno nenehno kopirati prevedeno kodo pametne pogodbe v datoteko s testi, zato bomo napisali skript, ki bo namesto nas zapisal kodo v konstanto, in bomo preprosto povezali prevedeno kodo z našimi testi z uporabo "include".
Ustvarite datoteko v mapi projekta build.sh z naslednjo vsebino.
Zdaj samo zaženite naš skript, da sestavite pogodbo. Toda poleg tega ga moramo zapisati v konstanto code. Tako bomo ustvarili novo datoteko lotter-compiled-for-test.fif, ki ga bomo vključili v datoteko lottery-test-suite.fif.
Dodajmo skirpt kodo v sh, ki bo preprosto podvojila prevedeno datoteko lotter-compiled-for-test.fif in spremenite zadnjo vrstico v njem.
# 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
Zdaj, da preverimo, zaženimo nastali skript in ustvarjena bo datoteka lottery-compiled-for-test.fif, ki ga bomo vključili v našo lottery-test-suite.fif
В lottery-test-suite.fif izbrišite kodo pogodbe in dodajte vrstico "lottery-compiled-for-test.fif" include.
Izvajamo teste, da preverimo, ali so uspešni.
~/TON/build/crypto/fift -s lottery-test-suite.fif
Super, zdaj za avtomatizacijo zagona testov ustvarimo datoteko test.sh, ki se bo najprej izvršil build.shin nato zaženite teste.
Naredimo to test.sh in ga zaženite, da se prepričate, ali testi delujejo.
chmod +x ./test.sh
./test.sh
Preverimo, ali je pogodba sestavljena in ali so testi izvedeni.
Super, zdaj ob zagonu test.sh Testi bodo sestavljeni in izvedeni takoj. Tukaj je povezava do zavezati.
V redu, preden nadaljujemo, naredimo še eno stvar za udobje.
Ustvarimo mapo build kamor bomo shranili kopirano pogodbo in njen klon, zapisan v konstanto lottery-compiled.fif, lottery-compiled-for-test.fif. Ustvarimo tudi mapo test kje bo shranjena testna datoteka? lottery-test-suite.fif in potencialno druge podporne datoteke. Povezava do ustreznih sprememb.
Nadaljujmo z razvojem pametne pogodbe.
Sledi preizkus, ki preveri, ali je sporočilo prejeto in se števec posodobi v trgovini, ko pošljemo pravilno številko. Ampak to bomo storili kasneje.
Zdaj pa razmislimo o strukturi podatkov in kateri podatki morajo biti shranjeni v pametni pogodbi.
Opisal bom vse, kar hranimo.
`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` переменная типа словарь, хранит последние двадцать ставок.
Nato morate napisati dve funkciji. Pokličimo prvega pack_state(), ki bo zapakiral podatke za kasnejše shranjevanje v shrambo pametnih pogodb. Pokličimo drugo unpack_state() bo prebral in vrnil podatke iz pomnilnika.
_ 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;
}
Ti dve funkciji dodamo na začetek pametne pogodbe. Se bo izšlo Všečkaj to vmesni rezultat.
Za shranjevanje podatkov boste morali poklicati vgrajeno funkcijo set_data() in bo zapisal podatke iz pack_state() v shrambi pametnih pogodb.
Zdaj, ko imamo priročne funkcije za pisanje in branje podatkov, lahko nadaljujemo.
Preveriti moramo, ali je sporočilo, ki prihaja od zunaj, podpisano s strani lastnika pogodbe (ali drugega uporabnika, ki ima dostop do zasebnega ključa).
Ko objavimo pametno pogodbo, jo lahko inicializiramo s podatki, ki jih potrebujemo v pomnilniku, ki bodo shranjeni za prihodnjo uporabo. Tam bomo zabeležili javni ključ, da bomo lahko preverili, ali je bilo dohodno sporočilo podpisano z ustreznim zasebnim ključem.
Preden nadaljujemo, ustvarimo zasebni ključ in ga zapišimo test/keys/owner.pk. Če želite to narediti, zaženite Fift v interaktivnem načinu in izvedite štiri ukaze.
`newkeypair` генерация публичного и приватного ключа и запись их в стек.
`drop` удаления из стека верхнего элемента (в данном случае публичный ключ)
`.s` просто посмотреть что лежит в стеке в данный момент
`"owner.pk" B>file` запись приватного ключа в файл с именем `owner.pk`.
`bye` завершает работу с Fift.
Ustvarimo mapo keys znotraj mape test in tam zapišite zasebni 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
V trenutni mapi vidimo datoteko owner.pk.
Javni ključ odstranimo iz sklada in po potrebi ga lahko pridobimo iz zasebnega.
Zdaj moramo napisati overitev podpisa. Začnimo s testom. Najprej preberemo zasebni ključ iz datoteke s funkcijo file>B in ga zapišite v spremenljivko owner_private_key, nato pa uporabite funkcijo priv>pub pretvorite zasebni ključ v javni ključ in zapišite rezultat owner_public_key.
Posledično se sporočilo, ki ga bomo poslali pametni pogodbi, zabeleži v spremenljivki message_to_send, o funkcijah hashu, ed25519_sign_uint lahko bere v dokumentaciji Fift.
Tukaj tako Datoteka s testi naj bi na tej stopnji izgledala takole.
Zaženimo test in ne bo uspel, zato bomo spremenili pametno pogodbo, da bo lahko prejemala sporočila v tej obliki in preverjala podpis.
Najprej preštejemo 512 bitov podpisa sporočila in ga zapišemo v spremenljivko, nato preštejemo 32 bitov spremenljivke števca.
Ker imamo funkcijo za branje podatkov iz shrambe pametnih pogodb, jo bomo uporabili.
Sledi preverjanje števca, prenesenega s shrambo, in preverjanje podpisa. Če nekaj ne ustreza, vržemo izjemo z ustrezno kodo.
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));
Opravimo teste in vidimo, da drugi test ne uspe. Iz dveh razlogov ni dovolj bitov v sporočilu in ni dovolj bitov v pomnilniku, zato se koda pri razčlenjevanju zruši. Sporočilu, ki ga pošiljamo, moramo dodati podpis in kopirati shrambo iz zadnjega testa.
V drugem preizkusu bomo dodali podpis sporočila in spremenili shrambo pametne pogodbe. Tukaj tako datoteka s testi trenutno izgleda takole.
Napišimo še četrti test, v katerem bomo poslali sporočilo, podpisano z zasebnim ključem nekoga drugega. Ustvarimo še en zasebni ključ in ga shranimo v datoteko not-owner.pk. S tem zasebnim ključem bomo podpisali sporočilo. Opravimo teste in poskrbimo, da bodo vsi testi uspešni. Zaveži se ta trenutek.
Zdaj lahko končno nadaljujemo z implementacijo logike pametne pogodbe.
В recv_external() sprejeli bomo dve vrsti sporočil.
Ker bo naša pogodba kopičila izgube igralcev, je treba ta denar nakazati ustvarjalcu loterije. Naslov denarnice ustvarjalca loterije se zabeleži v shrambo, ko je pogodba ustvarjena.
Za vsak slučaj potrebujemo možnost spremembe naslova, na katerega pošiljamo grame poražencev. Prav tako bi morali imeti možnost pošiljanja gramov iz loterije na naslov lastnika.
Začnimo s prvim. Najprej napišimo test, ki bo preveril, ali je pametna pogodba po poslanem sporočilu shranila nov naslov v shrambo. Opozarjamo, da v sporočilu poleg števca in novega naslova posredujemo tudi action 7-bitno celo nenegativno število, odvisno od njega bomo izbrali način obdelave sporočila v pametni pogodbi.
<b 0 32 u, 1 @ 7 u, new_owner_wc @ 32 i, new_owner_account_id @ 256 u, b> message_to_sign !
V preizkusu lahko vidite, kako je shranjevanje pametnih pogodb deserializirano storage v Fiftu. Deserializacija spremenljivk je opisana v dokumentaciji Fift.
Izvedimo test in se prepričajmo, da ne uspe. Zdaj pa dodamo logiko za spremembo naslova lastnika loterije.
V pametni pogodbi nadaljujemo z razčlenjevanjem message, preberite action. Naj vas spomnimo, da bomo imeli dva action: spremenite naslov in pošljite gram.
Nato preberemo nov naslov pogodbenega lastnika in ga shranimo v hrambo.
Izvedemo teste in vidimo, da tretji test ne uspe. Sesuje se zaradi tega, ker pogodba zdaj dodatno razčleni 7 bitov iz sporočila, ki manjkajo v testu. Sporočilu dodajte neobstoječega action. Opravimo teste in preverimo, ali vse poteka. Tukaj zavezati se spremembam. Super.
Zdaj pa napišimo logiko za pošiljanje določenega števila gramov na predhodno shranjen naslov.
Najprej napišimo test. Napisali bomo dva testa, enega, ko ni dovolj ravnovesja, drugega, ko bi moralo vse uspešno prestati. Teste si lahko ogledate v tej obvezi.
Zdaj pa dodamo kodo. Najprej napišimo dve pomožni metodi. Prva metoda pridobivanja je ugotoviti trenutno stanje pametne pogodbe.
int balance() inline_ref method_id {
return get_balance().pair_first();
}
In drugi je za pošiljanje gramov v drugo pametno pogodbo. To metodo sem popolnoma kopiral iz druge pametne pogodbe.
() 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 ti dve metodi v pametno pogodbo in napišimo logiko. Najprej razčlenimo število gramov iz sporočila. Nato preverimo stanje, če ni dovolj, vržemo izjemo. Če je vse v redu, potem pošljemo grame na shranjen naslov in posodobimo števec.
Tukaj tako trenutno izgleda kot pametna pogodba. Opravimo teste in poskrbimo, da bodo uspešni.
Mimogrede, vsakič za obdelano sporočilo se od pametne pogodbe odšteje provizija. Da bi sporočila pametne pogodbe izvedla zahtevo, morate po osnovnih preverjanjih poklicati accept_message().
Zdaj pa preidimo na interna sporočila. Pravzaprav bomo sprejeli le grame in vrnili dvojno količino igralcu, če zmaga, in tretjino lastniku, če izgubi.
Najprej napišimo preprost test. Za to potrebujemo testni naslov pametne pogodbe, s katere domnevno pošiljamo grame v pametno pogodbo.
Naslov pametne pogodbe je sestavljen iz dveh številk, 32-bitnega celega števila, odgovornega za delovno verigo, in 256-bitne nenegativne cele številke edinstvene številke računa v tej delovni verigi. Na primer -1 in 12345, to je naslov, ki ga bomo shranili v datoteko.
Funkcijo za shranjevanje naslova sem kopiral 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
Poglejmo, kako funkcija deluje, tako bomo razumeli, kako deluje Fift. Zaženite Fift v interaktivnem načinu.
~/TON/build/crypto/fift -i
Najprej potisnemo -1, 12345 in ime prihodnje datoteke "sender.addr" na sklad:
-1 12345 "sender.addr"
Naslednji korak je izvedba funkcije -rot, ki premakne sklad tako, da je na vrhu sklada edinstvena številka pametne pogodbe:
"sender.addr" -1 12345
256 u>B pretvori 256-bitno nenegativno celo število v bajte.
In končno se bajti zapišejo v datoteko B>file. Po tem je naš sklad prazen. Ustavimo se Fift. V trenutni mapi je bila ustvarjena datoteka sender.addr. Premaknimo datoteko v ustvarjeno mapo test/addresses/.
Napišimo preprost test, ki bo poslal grame v pametno pogodbo. Tukaj je zaveza.
Zdaj pa poglejmo logiko loterije.
Prva stvar, ki jo naredimo, je, da preverimo sporočilo bounced ali ne če bounced, potem ga ignoriramo. bounced pomeni, da bo pogodba vrnila grame, če pride do napake. Gramov ne bomo vrnili, če nenadoma pride do napake.
Preverimo, če je stanje manjše od pol grama, potem sporočilo preprosto sprejmemo in ga ignoriramo.
Nato razčlenimo naslov pametne pogodbe, iz katere je prišlo sporočilo.
Podatke preberemo iz pomnilnika in nato izbrišemo stare stave iz zgodovine, če jih je več kot dvajset. Za udobje sem napisal tri dodatne funkcije pack_order(), unpack_order(), remove_old_orders().
Nato pogledamo, če stanje ni dovolj za plačilo, potem menimo, da to ni stava, ampak dopolnitev in shranimo dopolnitev v orders.
Potem pa končno bistvo pametne pogodbe.
Najprej, če igralec izgubi, to shranimo v zgodovino stav in če je količina večja od 3 gramov, pošljemo 1/3 lastniku pametne pogodbe.
Če igralec zmaga, potem na njegov naslov pošljemo dvojni znesek in nato podatke o stavi shranimo v zgodovino.
() 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));
}
Zdaj je vse, kar ostane, preprosto, ustvarimo get-methode, da lahko pridobimo informacije o stanju pogodbe iz zunanjega sveta (pravzaprav preberemo podatke iz njihovega pametnega pomnilnika pogodb).
Dodajmo get metode. Kako do informacij o pametni pogodbi, bomo pisali v nadaljevanju.
Pozabil sem dodati tudi kodo, ki bo obdelala prvo zahtevo, ki se pojavi ob objavi pametne pogodbe. Ustrezna potrditev. In dalje popravljeno napaka s pošiljanjem 1/3 zneska na račun lastnika.
Naslednji korak je objava pametne pogodbe. Ustvarimo mapo requests.
Nekaj, na kar je vredno pozornosti. Generiramo shrambo pametne pogodbe in vhodno sporočilo. Po tem se generira naslov pametne pogodbe, torej je naslov znan že pred objavo v TON. Nato morate na ta naslov poslati več gramov in šele nato poslati datoteko s samo pametno pogodbo, saj omrežje vzame provizijo za shranjevanje pametne pogodbe in operacije v njej (validatorji, ki shranjujejo in izvajajo pametne pogodbe). Kodo si lahko ogledate tukaj.
Nato izvedemo kodo za objavo in dobimo lottery-query.boc datoteka in naslov pametne pogodbe.
In videli bomo, da je račun s tem naslovom prazen.
account state is empty
Pošljemo na naslov 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 Gram in po nekaj sekundah izvedemo isti ukaz. Za pošiljanje gramov uporabljam uradna denarnica, lahko pa koga iz klepeta prosite za testne grame, o katerih bom govoril na koncu članka.
Zdaj pa ustvarimo zahteve za interakcijo s pametno pogodbo.
Natančneje, prvega bomo pustili za spremembo naslova kot samostojno delo, drugega pa bomo naredili za pošiljanje gramov na naslov lastnika. Pravzaprav bomo morali narediti isto kot pri testu za pošiljanje gramov.
To je sporočilo, ki ga bomo poslali pametni pogodbi, kjer msg_seqno 165, action 2 in 9.5 grama za pošiljanje.
<b 165 32 u, 2 7 u, 9500000000 Gram, b>
Ne pozabite podpisati sporočila s svojim zasebnim ključem lottery.pk, ki je bil ustvarjen prej pri ustvarjanju pametne pogodbe. Tukaj je ustrezna potrditev.
Prejemanje informacij iz pametne pogodbe z uporabo metod get
Zdaj pa poglejmo, kako zagnati metode pridobivanja pametne pogodbe.
Kosilo lite-client in zaženi metode get, ki smo jih napisali.
Uporabili bomo lite-client in pridobili metode za prikaz informacij o pametni pogodbi na spletnem mestu.
Prikaz podatkov pametnih pogodb na spletni strani
Napisal sem preprosto spletno mesto v Pythonu za prikaz podatkov iz pametne pogodbe na priročen način. Tukaj se ne bom podrobno zadrževal in bom objavil spletno mesto v eni objavi.
Zahteve za TON so podane iz Python s pomočjo lite-client. Zaradi udobja je spletno mesto zapakirano v Docker in objavljeno v Google Cloud. Povezava.
Poskušam
Zdaj pa poskusimo tja poslati grame za dopolnitev denarnica. Poslali bomo 40 gramov. In dajmo nekaj stav za jasnost. Vidimo, da spletno mesto prikazuje zgodovino stav, trenutni odstotek zmag in druge koristne informacije.
Članek se je izkazal za veliko daljšega, kot sem pričakoval, morda bi lahko bil krajši ali pa samo za osebo, ki ne ve nič o TON in želi napisati in objaviti ne tako preprosto pametno pogodbo z možnostjo interakcije z to. Morda bi lahko nekatere stvari razložili bolj preprosto.
Morda bi nekatere vidike implementacije lahko naredili bolj učinkovito in elegantno, a bi potem priprava članka vzela še več časa. Možno je tudi, da sem se kje zmotil ali česa nisem razumel, zato se morate, če delate kaj resnega, zanesti na uradno dokumentacijo ali uradno skladišče s kodo TON.
Upoštevati je treba, da ker je TON sam še vedno v aktivni fazi razvoja, lahko pride do sprememb, ki bodo prekinile katerega od korakov v tem članku (kar se je zgodilo, ko sem pisal, je že popravljeno), vendar je splošni pristop malo verjetno se bo spremenilo.
Ne bom govoril o prihodnosti TON. Morda bo platforma postala nekaj velikega in bi morali porabiti čas za njeno preučevanje in zdaj zapolniti nišo z našimi izdelki.
Obstaja tudi Libra iz Facebooka, ki ima potencialno občinstvo uporabnikov večje od TON. O Libri ne vem skoraj nič, po forumu sodeč je tam veliko več aktivnosti kot v skupnosti TON. Čeprav so razvijalci in skupnost TON bolj podzemni, je tudi kul.
Klepetajte o TON v Telegramu, kar je res pomagalo ugotoviti to na začetni stopnji. Mislim, da ne bo napaka, če rečem, da so tam vsi, ki so kaj napisali za TON. Tam lahko zahtevate tudi testne grame. https://t.me/tondev_ru