ProHoster > Blog > Adminisztráció > Az intelligens szerződés megírásáról és közzétételéről a Telegram Open Networkben (TON)
Az intelligens szerződés megírásáról és közzétételéről a Telegram Open Networkben (TON)
Arról, hogyan írjunk és tegyünk közzé egy intelligens szerződést TON-ban
Miről szól ez a cikk?
A cikkben arról fogok beszélni, hogyan vettem részt az első (kettőből) Telegram blokklánc versenyen, nem nyertem el díjat, és úgy döntöttem, hogy a tapasztalataimat egy cikkben rögzítem, hogy ne merüljön feledésbe, és talán segítsen. valaki.
Mivel nem absztrakt kódot akartam írni, hanem valami működőképes dolgot, a cikkhez írtam egy okos szerződést egy azonnali lottóhoz, és egy olyan weboldalt, amely közvetlenül a TON-tól mutatja az intelligens szerződések adatait közbenső tárolás nélkül.
A cikk hasznos lesz azoknak, akik szeretnék megkötni első intelligens szerződésüket TON-ban, de nem tudják, hol kezdjék.
Példaként a lottót használva a környezet telepítésétől eljutok az okosszerződés közzétételéig, a vele való interakcióig, valamint az adatok fogadására és közzétételére szolgáló weboldal írására.
A versenyen való részvételről
Tavaly októberben a Telegram blokklánc versenyt hirdetett új nyelvekkel Fift и FunC. Választani kellett az öt javasolt okosszerződés írásából. Arra gondoltam, hogy jó lenne valami mást csinálni, nyelvet tanulni és készíteni valamit, még akkor is, ha a jövőben nem kell mást írnom. Ráadásul a téma folyamatosan a száján van.
Érdemes elmondani, hogy nem volt tapasztalatom intelligens szerződések fejlesztésében.
Terveztem, hogy a legvégéig részt veszek, amíg tudok, majd írok egy ismertető cikket, de az elsőnél rögtön megbuktam. én tárcát írt több aláírás bekapcsolásával FunC és általában működött. Ezt vettem alapul intelligens szerződés a Solidityről.
Akkoriban azt hittem, hogy ez biztosan elég ahhoz, hogy legalább egy díjat elfoglaljak. Ennek eredményeként a 40 résztvevőből körülbelül 60 nyertes lett, én pedig nem voltam köztük. Általában véve nincs ezzel semmi baj, de egy dolog zavart. Eredményhirdetéskor a szerződésem tesztjének áttekintése még nem történt meg, megkérdeztem a chaten résztvevőket, hogy van-e még akinek nem, nem volt.
Látszólag az üzeneteimre figyelve két nappal később a bírók kommentet tettek közzé, és még mindig nem értem, hogy véletlenül kihagyták-e az okos szerződésemet a zsűrizés során, vagy egyszerűen azt gondolták, hogy az olyan rossz, hogy nem kell kommentár. Feltettem egy kérdést az oldalon, de nem kaptam választ. Bár nem titok, hogy ki ítélkezett, feleslegesnek tartottam személyes üzeneteket írni.
Sok időt fordítottak a megértésre, ezért úgy döntöttek, hogy írok egy cikket. Mivel még nincs sok információ, ez a cikk minden érdeklődő számára időt takarít meg.
Az intelligens szerződések fogalma a TON-ban
Mielőtt bármit is írnál, ki kell találnod, melyik oldalról közelítsd meg ezt a dolgot. Ezért most elmondom, milyen részekből áll a rendszer. Pontosabban, hogy milyen részeket kell tudni ahhoz, hogy legalább valamilyen munkaszerződést megírhasson.
Az intelligens szerződés megírására és a vele való együttműködésre összpontosítunk TON Virtual Machine (TVM), Fift и FunC, tehát a cikk inkább egy szokásos program fejlesztésének leírása. Itt nem foglalkozunk azzal, hogy maga a platform hogyan működik.
Általánosságban a működéséről TVM és a nyelv Fift jó hivatalos dokumentáció van. A versenyen való részvétel során és most az aktuális szerződés megírása közben gyakran fordultam hozzá.
Az intelligens szerződések fő nyelve a következő FunC. Jelenleg nincs róla dokumentáció, így ahhoz, hogy valamit írhasson, tanulmányoznia kell a hivatalos adattárból származó intelligens szerződések példáit és magának a nyelvnek az ott való megvalósítását, valamint megnézheti az okos szerződések példáit az elmúlt kettőből. versenyeken. Linkek a cikk végén.
Tegyük fel, hogy már írtunk egy okos szerződést FunC, ezután Fift assemblerbe fordítjuk a kódot.
Az összeállított okosszerződést még közzé kell tenni. Ehhez be kell írni egy függvényt Fift, amely az intelligens szerződés kódját és néhány egyéb paramétert veszi be bemenetként, a kimenet pedig egy kiterjesztésű fájl lesz .boc (ami „sejtzsákot” jelent), és attól függően, hogy hogyan írjuk, egy privát kulcsot és címet, amely az intelligens szerződés kódja alapján jön létre. Egy még nem publikált okosszerződés címére már lehet grammokat küldeni.
Intelligens szerződés közzétételéhez TON-ban kapott .boc a fájlt el kell küldeni a blokkláncnak egy könnyű kliens segítségével (erről bővebben lentebb). De közzététel előtt át kell vinni a grammokat a generált címre, különben az intelligens szerződés nem kerül közzétételre. A közzététel után az intelligens szerződéssel kapcsolatba léphet úgy, hogy üzeneteket küld neki kívülről (például egy könnyű kliens segítségével) vagy belülről (például az egyik intelligens szerződés üzenetet küld a másiknak a TON-on belül).
Ha megértjük a kód közzétételének módját, könnyebbé válik. Nagyjából tudjuk, hogy mit akarunk írni, és hogyan fog működni a programunk. Írás közben pedig utánanézünk, hogy ez hogyan valósul meg már a meglévő okosszerződésekben, vagy belenézünk a megvalósítási kódba Fift и FunC a hivatalos adattárban, vagy nézd meg a hivatalos dokumentációt.
Nagyon gyakran kerestem kulcsszavakat a Telegram chaten, ahol a verseny összes résztvevője és a Telegram munkatársai összegyűltek, és úgy esett, hogy a verseny alatt mindenki ott gyűlt össze, és elkezdett beszélgetni a Fiftről és a FunC-ről. Link a cikk végén.
Ideje áttérni az elméletről a gyakorlatra.
A környezet előkészítése a TON-nal való munkavégzéshez
Mindent megtettem, amit a MacOS-ról szóló cikkben leírunk, és kétszer is ellenőriztem a tiszta Ubuntu 18.04 LTS-ben a Dockeren.
Az első dolog, amit meg kell tennie, a letöltés és telepítés lite-client amellyel kéréseket küldhet a TON-nak.
A hivatalos weboldalon található utasítások meglehetősen részletesen és egyértelműen leírják a telepítési folyamatot, és néhány részletet kihagynak. Itt követjük az utasításokat, és közben telepítjük a hiányzó függőségeket. Nem magam fordítottam le minden projektet, hanem a hivatalos Ubuntu tárolóból telepítettem (MacOS-en brew).
Miután minden függőséget telepített, telepítheti lite-client, Fift, FunC.
Először is klónozzuk a TON tárolót a függőségeivel együtt. A kényelem kedvéért mindent egy mappában fogunk csinálni ~/TON.
cd ~/TON
git clone https://github.com/ton-blockchain/ton.git
cd ./ton
git submodule update --init --recursive
A repository implementációkat is tárol Fift и FunC.
Most készen állunk a projekt összeállítására. A tárkód egy mappába klónozva van ~/TON/ton. -Ban ~/TON hozzon létre egy mappát build és gyűjtsük össze benne a projektet.
mkdir ~/TON/build
cd ~/TON/build
cmake ../ton
Mivel okos szerződést fogunk írni, nem csak lite-clientDe Fift с FunC, szóval állítsunk össze mindent. Ez nem egy gyors folyamat, ezért várunk.
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json
Ha a felépítés sikeres volt, akkor az indítás után megjelenik egy napló a könnyű kliens csomóponthoz való csatlakozásáról.
[ 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)
...
Futtathatja a parancsot help és nézze meg, milyen parancsok állnak rendelkezésre.
help
Soroljuk fel a cikkben használt parancsokat.
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-методы смартконтракта.
Most készen állunk a szerződés megírására.
Реализация
Ötlet
Ahogy fentebb is írtam, az okos szerződés, amit írunk, egy lottó.
Ráadásul ez nem egy lottó, amelyen jegyet kell venni, és várni kell egy órát, napot vagy hónapot, hanem egy azonnali lottó, amelyben a felhasználó átutal a szerződéses címre. N gramm, és azonnal visszakapja 2 * N gramm vagy veszít. A nyerési valószínűséget körülbelül 40%-ra tesszük. Ha nincs elég gramm a fizetéshez, akkor a tranzakciót feltöltésnek tekintjük.
Ezenkívül fontos, hogy a fogadások valós időben és kényelmes formában láthatóak legyenek, így a felhasználó azonnal megértheti, nyert-e vagy veszített. Ezért létre kell hoznia egy webhelyet, amely közvetlenül a TON-tól fogja megjeleníteni a fogadásokat és az eredményeket.
Okos szerződés írása
A kényelem kedvéért kiemeltem a FunC kódját; a bővítmény megtalálható és telepíthető a Visual Studio Code keresőjében; ha hirtelen szeretne valamit hozzáadni, nyilvánosan elérhetővé tettem a bővítményt. Valaki korábban készített egy plugint a Fift-tel való együttműködéshez, azt is telepítheti és megtalálhatja a VSC-ben.
Azonnal hozzunk létre egy adattárat, ahol rögzítjük a köztes eredményeket.
Az életünk megkönnyítése érdekében okos szerződést írunk és helyben teszteljük, amíg készen nem lesz. Csak ezután tesszük közzé a TON-ban.
Az intelligens szerződés két külső módszerrel érhető el. Első, recv_external() ez a funkció akkor hajtódik végre, ha a szerződésre irányuló kérés a külvilágból érkezik, vagyis nem a TON-tól, például amikor mi magunk generálunk egy üzenetet, és azt a lite-kliensen keresztül küldjük el. Második, recv_internal() ilyenkor magán a TON-on belül bármely szerződés a miénkre vonatkozik. Mindkét esetben átadhatunk paramétereket a függvénynek.
Kezdjük egy egyszerű példával, amely működik, ha közzétesszük, de nincs benne funkcionális terhelés.
Itt meg kell magyaráznunk, hogy mi az slice. A TON Blockchainben tárolt összes adat gyűjtemény TVM cell vagy egyszerűen cell, egy ilyen cellában legfeljebb 1023 bit adatot és legfeljebb 4 hivatkozást tárolhat más cellákra.
TVM cell slice vagy slice ez a meglévő része cell elemzésére szolgál, később kiderül. Nekünk az a fő, hogy át tudjuk adni slice és az üzenet típusától függően feldolgozza az adatokat recv_external() vagy recv_internal().
impure — egy kulcsszó, amely azt jelzi, hogy a függvény módosítja az intelligens szerződés adatait.
Mentsük el a szerződés kódját lottery-code.fc és összeállítjuk.
Összeállítottuk a Fift assembler kódot 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
Helyben indítható, erre készítjük fel a környezetet.
Vegye figyelembe, hogy az első sor csatlakozik Asm.fif, ez a Fiftben írt kód a Fift assemblerhez.
Mivel az intelligens szerződést helyben szeretnénk futtatni és tesztelni, létrehozunk egy fájlt lottery-test-suite.fif és másolja oda a lefordított kódot, cserélje le benne az utolsó sort, amely az intelligens szerződés kódját egy konstansba írja codehogy ezután vigye át a virtuális gépre:
"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
Egyelőre egyértelműnek tűnik, most ugyanahhoz a fájlhoz adjuk hozzá azt a kódot, amellyel a TVM-et elindítjuk.
В c7 rögzítjük a kontextust, vagyis azokat az adatokat, amelyekkel a TVM (vagy hálózati állapot) elindul. Még a verseny alatt az egyik fejlesztő megmutatta, hogyan kell alkotni c7 és másoltam. Ebben a cikkben lehet, hogy változtatnunk kell rand_seed mivel egy véletlenszám generálása attól függ, és ha nem változtatjuk meg, akkor minden alkalommal ugyanaz a szám kerül visszaadásra.
recv_internal и recv_external A 0 és -1 értékű konstansok felelősek a megfelelő függvények meghívásáért az intelligens szerződésben.
Most készen állunk az első teszt létrehozására üres intelligens szerződésünkhöz. Az egyértelműség kedvéért egyelőre az összes tesztet ugyanabba a fájlba adjuk lottery-test-suite.fif.
Hozzunk létre egy változót storage és írj bele egy üreset cell, ez lesz az intelligens szerződéses tárhely.
message Ez az az üzenet, amelyet kívülről továbbítunk az intelligens kapcsolattartónak. Egyelőre üressé is tesszük.
Remek, megírtuk az intelligens szerződés első működő verzióját.
Most funkcionalitást kell hozzáadnunk. Először foglalkozzunk a külvilágból érkező üzenetekkel recv_external()
A fejlesztő maga választja ki azt az üzenetformátumot, amelyet a szerződés elfogadhat.
De általában
egyrészt szeretnénk megvédeni a szerződésünket a külvilágtól, és úgy tenni, hogy csak a szerződés tulajdonosa küldhessen neki külső üzeneteket.
másodszor, amikor érvényes üzenetet küldünk a TON-nak, azt akarjuk, hogy ez pontosan egyszer történjen meg, és amikor újra elküldjük ugyanazt az üzenetet, az intelligens szerződés elutasítja azt.
Így szinte minden szerződés megoldja ezt a két problémát, hiszen a szerződésünk fogadja a külső üzeneteket, erre is ügyelnünk kell.
Fordított sorrendben tesszük. Először is oldjuk meg a problémát az ismétléssel, ha a szerződés már kapott ilyen üzenetet és feldolgozta, akkor másodszor nem hajtja végre. És akkor megoldjuk a problémát, hogy csak egy bizonyos kör tudjon üzenetet küldeni az okosszerződésnek.
Az ismétlődő üzenetekkel kapcsolatos probléma többféleképpen is megoldható. Íme, hogyan fogjuk csinálni. Az intelligens szerződésben a fogadott üzenetek számlálóját 0 kezdeti értékkel inicializáljuk. Az intelligens szerződés minden egyes üzenetében hozzáadjuk az aktuális számlálóértéket. Ha az üzenetben szereplő számláló értéke nem egyezik az intelligens szerződésben szereplő értékkel, akkor nem dolgozzuk fel, ha igen, akkor feldolgozzuk, és az intelligens szerződésben szereplő számlálót 1-gyel növeljük.
Térjünk vissza a lottery-test-suite.fif és adjunk hozzá egy második tesztet. Ha hibás számot küldünk, a kódnak kivételt kell dobnia. Például hagyja, hogy a szerződés adatai tárolják a 166-ot, és mi küldjük a 165-öt.
<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"
Indítsuk el.
~/TON/build/crypto/fift -s lottery-test-suite.fif
És látni fogjuk, hogy a tesztet hibával hajtják végre.
[ 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
Ebben a szakaszban lottery-test-suite.fif úgy kell kinéznie по ссылке.
Most adjuk hozzá a számláló logikát az intelligens szerződéshez 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 rejlik az üzenet, amit küldünk.
Először ellenőrizzük, hogy az üzenet tartalmaz-e adatokat, ha nem, akkor egyszerűen kilépünk.
Ezután elemezzük az üzenetet. in_msg~load_uint(32) betölti a 165-ös számot, 32 bites unsigned int a továbbított üzenetből.
Ezután 32 bitet töltünk be az intelligens szerződéses tárolóból. Ellenőrizzük, hogy a betöltött szám megegyezik-e az átadott számmal, ha nem, kivételt dobunk. A mi esetünkben, mivel nem meccset passzolunk, kivételt kell tenni.
Másolja a kapott kódot ide lottery-test-suite.fif, nem felejtve el az utolsó sort kicserélni.
Ellenőrizzük a teszt sikerességét:
~/TON/build/crypto/fift -s lottery-test-suite.fif
Itt van Láthatja a megfelelő véglegesítést az aktuális eredményekkel.
Megjegyzendő, hogy kényelmetlen egy intelligens szerződés lefordított kódját állandóan egy fájlba másolni tesztekkel, ezért írunk egy szkriptet, amely a kódot egy konstansba írja nekünk, és a lefordított kódot egyszerűen csatlakoztatjuk a tesztjeinkhez. "include".
Hozzon létre egy fájlt a projekt mappájában build.sh a következő tartalommal.
Most csak futtassa a szkriptünket a szerződés összeállításához. De ezen kívül konstansba kell írnunk code. Tehát létrehozunk egy új fájlt lotter-compiled-for-test.fif, amelyet belefoglalunk a fájlba lottery-test-suite.fif.
Adjunk hozzá skirpt kódot az sh-hez, ami egyszerűen megkettőzi a lefordított fájlt lotter-compiled-for-test.fif és változtassa meg benne az utolsó sort.
# 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
Most az ellenőrzéshez futtassuk az eredményül kapott szkriptet, és létrejön egy fájl lottery-compiled-for-test.fif, amelyet beépítünk a mi lottery-test-suite.fif
В lottery-test-suite.fif törölje a szerződés kódját és adja hozzá a sort "lottery-compiled-for-test.fif" include.
Teszteket futtatunk annak ellenőrzésére, hogy megfelelnek-e.
~/TON/build/crypto/fift -s lottery-test-suite.fif
Remek, most, hogy automatizáljuk a tesztek indítását, hozzunk létre egy fájlt test.sh, amely először végrehajtódik build.sh, majd futtassa a teszteket.
Meg fog tenni test.sh és futtassa, hogy megbizonyosodjon a tesztek működéséről.
chmod +x ./test.sh
./test.sh
Ellenőrizzük a szerződés összeállítását és a tesztek végrehajtását.
Remek, most indul test.sh A teszteket azonnal összeállítják és lefutják. Itt a link a elkövetni.
Rendben, mielőtt folytatnánk, tegyünk még egy dolgot a kényelem kedvéért.
Hozzunk létre egy mappát build ahol a másolt szerződést és annak konstansba írt klónját tároljuk lottery-compiled.fif, lottery-compiled-for-test.fif. Hozzunk létre egy mappát is test hol lesz tárolva a tesztfájl? lottery-test-suite.fif és potenciálisan más támogató fájlok. Link a vonatkozó változásokhoz.
Folytassuk az intelligens szerződés fejlesztését.
Ezután egy tesztnek kell lennie, amely ellenőrzi, hogy az üzenet megérkezett-e, és a számláló frissül-e az áruházban, amikor a megfelelő számot küldjük. De ezt később megtesszük.
Most gondoljuk át, milyen adatszerkezetet és milyen adatokat kell tárolni az okosszerződésben.
Leírok mindent, amit tárolunk.
`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` переменная типа словарь, хранит последние двадцать ставок.
Ezután két függvényt kell írni. Hívjuk az elsőt pack_state(), amely az intelligens szerződéses tárolóba csomagolja az adatokat későbbi mentéshez. Hívjuk a másodikat unpack_state() beolvassa és visszaadja az adatokat a tárhelyről.
_ 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;
}
Ezt a két funkciót hozzáadjuk az intelligens szerződés elejéhez. Majd sikerülni fog itt van egy köztes eredmény.
Az adatok mentéséhez meg kell hívnia a beépített funkciót set_data() és innen fog adatokat írni pack_state() az intelligens szerződéses tárolóban.
Most, hogy kényelmes funkciókkal rendelkezünk az adatok írására és olvasására, továbbléphetünk.
Ellenőriznünk kell, hogy a kívülről érkező üzenetet a szerződés tulajdonosa (vagy más, a privát kulcshoz hozzáféréssel rendelkező felhasználó) írja-e alá.
Amikor közzéteszünk egy okosszerződést, inicializálhatjuk a tárolóban szükséges adatokkal, amelyeket későbbi felhasználás céljából elmentünk. Ott rögzítjük a nyilvános kulcsot, hogy ellenőrizhessük, hogy a bejövő üzenetet a megfelelő privát kulccsal írták alá.
Mielőtt folytatnánk, hozzunk létre egy privát kulcsot, és írjuk be test/keys/owner.pk. Ehhez indítsuk el a Fiftet interaktív módban, és hajtsunk végre négy parancsot.
`newkeypair` генерация публичного и приватного ключа и запись их в стек.
`drop` удаления из стека верхнего элемента (в данном случае публичный ключ)
`.s` просто посмотреть что лежит в стеке в данный момент
`"owner.pk" B>file` запись приватного ключа в файл с именем `owner.pk`.
`bye` завершает работу с Fift.
Hozzunk létre egy mappát keys a mappán belül test és oda írja be a privát kulcsot.
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
Egy fájlt látunk az aktuális mappában owner.pk.
Eltávolítjuk a nyilvános kulcsot a veremből, és szükség esetén lekérhetjük a privát kulcsból.
Most egy aláírás-ellenőrzést kell írnunk. Kezdjük a teszttel. Először a privát kulcsot olvassuk ki a fájlból a függvény segítségével file>B és írd be egy változóba owner_private_key, majd a funkció használatával priv>pub konvertálja a privát kulcsot nyilvános kulccsá, és írja be az eredményt owner_public_key.
Ennek eredményeként az üzenet, amelyet az intelligens szerződésnek küldünk, egy változóban rögzítésre kerül message_to_send, a funkciókról hashu, ed25519_sign_uint te tudsz olvasni az Fift dokumentációjában.
Itt van A teszteket tartalmazó fájlnak ebben a szakaszban így kell kinéznie.
Futtassuk le a tesztet, és meghiúsul, ezért módosítjuk az intelligens szerződést, hogy az ilyen formátumú üzeneteket fogadhasson, és ellenőrizze az aláírást.
Először megszámoljuk az üzenetből származó aláírás 512 bitjét és beírjuk egy változóba, majd megszámoljuk a számlálóváltozó 32 bitjét.
Mivel van egy funkciónk az intelligens szerződéses tárolóból adatok kiolvasására, ezt fogjuk használni.
A következő lépés a tárolóval átvitt számláló ellenőrzése és az aláírás ellenőrzése. Ha valami nem egyezik, akkor kivételt dobunk a megfelelő kóddal.
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));
Futtassuk le a teszteket, és nézzük meg, hogy a második teszt sikertelen. Két ok miatt nincs elég bit az üzenetben, és nincs elég bit a tárolóban, ezért a kód összeomlik elemzéskor. Hozzá kell adnunk egy aláírást a küldendő üzenethez, és ki kell másolnunk a tárhelyet az utolsó tesztből.
A második tesztben üzenetaláírást adunk hozzá, és megváltoztatjuk az intelligens szerződéses tárolást. Itt van a teszteket tartalmazó fájl jelenleg így néz ki.
Írjunk egy negyedik tesztet, amelyben valaki más privát kulcsával aláírt üzenetet küldünk. Hozzon létre egy másik privát kulcsot, és mentse el egy fájlba not-owner.pk. Ezzel a privát kulccsal írjuk alá az üzenetet. Futtassuk le a teszteket, és győződjön meg arról, hogy minden teszt sikeres. Elkövetni jelenleg.
Most végre továbbléphetünk az intelligens szerződési logika megvalósítására.
В recv_external() kétféle üzenetet fogadunk el.
Mivel szerződésünkben a játékosok veszteségei halmozódnak fel, ezt a pénzt a lottó készítőjének kell átutalni. A szerződés létrejöttekor a tárhelyen rögzítésre kerül a lottó készítőjének pénztárcacíme.
Minden esetre szükségünk van arra, hogy módosítsuk a címet, amelyre a vesztesek grammjait küldjük. A lottóból grammokat is tudjunk küldeni a tulajdonos címére.
Kezdjük az elsővel. Először írjunk egy tesztet, ami ellenőrzi, hogy az üzenet elküldése után az intelligens szerződés elmentette-e az új címet a tárhelyen. Felhívjuk figyelmét, hogy az üzenetben a számláló és az új cím mellett továbbítjuk action 7 bites egész szám, nem negatív szám, ettől függően választjuk ki, hogyan dolgozzuk fel az üzenetet az intelligens szerződésben.
<b 0 32 u, 1 @ 7 u, new_owner_wc @ 32 i, new_owner_account_id @ 256 u, b> message_to_sign !
A tesztben láthatja, hogyan történik a smartcontract tárolás deszerializálása storage a Fiftben. A változók deszerializálását az Fift dokumentáció írja le.
Futtassuk le a tesztet, és győződjünk meg arról, hogy sikertelen. Most adjunk hozzá logikát a lottótulajdonos címének megváltoztatásához.
Az intelligens szerződésben folytatjuk az elemzést message, olvass bele action. Emlékeztessünk, hogy kettőnk lesz action: változtassa meg a címet és küldje el a grammokat.
Ezután elolvassuk a szerződéstulajdonos új címét, és elmentjük a tárhelyen.
Futtatjuk a teszteket, és látjuk, hogy a harmadik teszt sikertelen. Összeomlik amiatt, hogy a szerződés ezentúl 7 bitet is elemel az üzenetből, ami hiányzik a tesztből. Adjon hozzá egy nem létezőt az üzenethez action. Futtassuk le a teszteket, és nézzük meg, hogy minden megy. Itt kötelezze el magát a változások mellett. Nagy.
Most írjuk meg a logikát, hogy a megadott számú grammot elküldjük a korábban elmentett címre.
Először is írjunk egy tesztet. Két tesztet írunk, az egyiket, amikor nincs elég egyensúly, a másodikat, amikor mindennek sikeresnek kell lennie. A tesztek megtekinthetők ebben az elköteleződésben.
Most adjuk hozzá a kódot. Először írjunk két segítő módszert. Az első beszerzési módszer az intelligens szerződés aktuális egyenlegének megállapítása.
int balance() inline_ref method_id {
return get_balance().pair_first();
}
A második pedig egy másik okosszerződéshez való grammok küldésére szolgál. Ezt a módszert teljesen átmásoltam egy másik intelligens szerződésből.
() 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
}
Adjuk hozzá ezt a két metódust az intelligens szerződéshez, és írjuk fel a logikát. Először is elemezzük a grammok számát az üzenetből. Ezután ellenőrizzük az egyenleget, ha nem elég, kivételt dobunk. Ha minden rendben van, akkor elküldjük a grammokat a mentett címre és frissítjük a számlálót.
Itt van úgy néz ki, mint az intelligens szerződés jelenleg. Futtassuk le a teszteket, és győződjünk meg arról, hogy sikeresek.
Az intelligens szerződésből egyébként minden alkalommal jutalékot vonnak le egy feldolgozott üzenetért. Ahhoz, hogy az intelligens szerződéses üzenetek teljesítsék a kérést, az alapvető ellenőrzések után fel kell hívni accept_message().
Most térjünk át a belső üzenetekre. Valójában csak grammokat fogadunk el, és a dupláját küldjük vissza a játékosnak, ha nyer, és a harmadát a tulajdonosnak, ha veszít.
Először is írjunk egy egyszerű tesztet. Ehhez szükségünk van az intelligens szerződés tesztcímére, ahonnan állítólag grammokat küldünk az intelligens szerződésnek.
Az intelligens szerződés címe két számból áll, egy 32 bites egész számból, amely felelős a munkaláncért, és egy 256 bites, nem negatív egész számból álló egyedi számlaszámból ebben a munkaláncban. Például -1 és 12345, ez az a cím, amelyet fájlba mentünk.
A címmentés funkcióját innen másoltam 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
Nézzük meg, hogyan működik a függvény, ez megérti a Fift működését. Indítsa el az Fift alkalmazást interaktív módban.
~/TON/build/crypto/fift -i
Először a -1, 12345 és a jövőbeli "sender.addr" fájl nevét helyezzük a verembe:
-1 12345 "sender.addr"
A következő lépés a funkció végrehajtása -rot, amely oly módon tolja el a veremet, hogy a verem tetején egy egyedi intelligens szerződésszám szerepel:
"sender.addr" -1 12345
256 u>B egy 256 bites, nem negatív egész számot konvertál bájtokká.
És végül a bájtok beírásra kerülnek a fájlba B>file. Ezt követően a veremünk üres. Megállunk Fift. Létrejött egy fájl az aktuális mappában sender.addr. Helyezzük át a fájlt a létrehozott mappába test/addresses/.
Az első dolgunk, hogy ellenőrizzük az üzenetet bounced vagy nem ha bounced, akkor figyelmen kívül hagyjuk. bounced azt jelenti, hogy a szerződés grammokat ad vissza, ha valamilyen hiba történik. Nem adunk vissza grammokat, ha hirtelen hiba történik.
Ellenőrizzük, ha az egyenleg fél grammnál kisebb, akkor egyszerűen elfogadjuk az üzenetet és figyelmen kívül hagyjuk.
Ezután elemezzük annak az intelligens szerződésnek a címét, amelyről az üzenet érkezett.
Kiolvassuk az adatokat a tárhelyről, majd töröljük a régi fogadásokat az előzményekből, ha húsznál több van belőlük. A kényelem kedvéért három további funkciót írtam pack_order(), unpack_order(), remove_old_orders().
Ezután megnézzük, ha az egyenleg nem elegendő a fizetéshez, akkor úgy tekintjük, hogy ez nem fogadás, hanem utánpótlás, és elmentjük az utánpótlást orders.
Aztán végre az okosszerződés lényege.
Először is, ha a játékos veszít, elmentjük a fogadási előzményekben, és ha az összeg meghaladja a 3 grammot, akkor az 1/3-át elküldjük az okosszerződés tulajdonosának.
Ha a játékos nyer, akkor az összeg dupláját küldjük a játékos címére, majd elmentjük a fogadással kapcsolatos információkat a történelemben.
() 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));
}
Most már csak az egyszerű, hozzunk létre get-metódusokat, hogy információkat szerezzünk a szerződés állapotáról a külvilágból (sőt, olvassuk le az adatokat az okos szerződéses tárolójukból).
Azt a kódot is elfelejtettem hozzáadni, amely feldolgozza a legelső kérést, amely egy intelligens szerződés közzétételekor fordul elő. Megfelelő kötelezettségvállalás. És tovább javítva hiba az összeg 1/3-ának a tulajdonos számlájára történő elküldésével.
A következő lépés az intelligens szerződés közzététele. Hozzunk létre egy mappát requests.
Valami, amire érdemes odafigyelni. Intelligens szerződéstárolót és bemeneti üzenetet generálunk. Ezt követően generálódik az intelligens szerződés címe, vagyis a cím már a TON-ban való közzététel előtt ismert. Ezután több grammot kell küldenie erre a címre, és csak ezt követően kell elküldenie egy fájlt magával az intelligens szerződéssel, mivel a hálózat jutalékot vesz fel az intelligens szerződés és a benne végzett műveletek tárolásáért (ellenőrzők, akik az intelligens szerződést tárolják és végrehajtják szerződések). A kód itt megtekinthető.
Ezután végrehajtjuk a közzétételi kódot, és megkapjuk lottery-query.boc intelligens szerződés fájl és cím.
És látni fogjuk, hogy az ezzel a címmel rendelkező fiók üres.
account state is empty
címre küldjük 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 grammot, és néhány másodperc múlva végrehajtjuk ugyanazt a parancsot. Gram küldésére használom hivatalos pénztárca, és kérhetsz valakitől a chaten tesztgrammot, amiről a cikk végén lesz szó.
Megfelelő kötelezettségvállalás változtatásokkal itt.
Most hozzunk létre kéréseket az intelligens szerződéssel való interakcióhoz.
Pontosabban az elsőt a címváltoztatásra hagyjuk önálló munkaként, a másodikat pedig a tulajdonosi címre történő grammküldésre. Valójában ugyanazt kell tennünk, mint a grammküldés tesztjében.
Ezt az üzenetet küldjük az okosszerződésnek, ahol msg_seqno 165, action 2 és 9.5 grammos küldésre.
<b 165 32 u, 2 7 u, 9500000000 Gram, b>
Ne felejtse el aláírni az üzenetet privát kulcsával lottery.pk, amely korábban az intelligens szerződés létrehozásakor keletkezett. Itt van a megfelelő commit.
Intelligens szerződésből származó információk fogadása get metódusokkal
Most nézzük meg, hogyan kell futtatni az intelligens szerződésszerzési módszereket.
Dob lite-client és futtassa az általunk írt get metódusokat.
Lite-klienst használunk, és módszereket kapunk az intelligens szerződéssel kapcsolatos információk megjelenítésére a webhelyen.
Intelligens szerződéses adatok megjelenítése a weboldalon
Írtam egy egyszerű webhelyet Pythonban, hogy kényelmesen jelenítse meg az intelligens szerződés adatait. Itt nem fogok részletesen foglalkozni vele, és közzéteszem az oldalt egy commitban.
A TON-hoz intézett kérések innen származnak Python keresztül lite-client. A kényelem kedvéért a webhelyet a Docker csomagban csomagolják, és a Google Cloudon teszik közzé. Link.
Megpróbálja
Most próbáljunk meg grammokat küldeni oda utánpótlásra pénztárca. 40 grammot küldünk. És tegyünk néhány fogadást az egyértelműség kedvéért. Látjuk, hogy az oldalon a fogadások előzményei, az aktuális nyerési százalék és egyéb hasznos információk láthatók.
Látjukhogy az elsőt megnyertük, a másodikat elveszítettük.
utószó
A cikk sokkal hosszabbnak bizonyult, mint amire számítottam, talán rövidebb is lehetett volna, vagy talán csak egy olyan személy számára, aki semmit sem tud a TON-ról, és szeretne egy nem túl egyszerű intelligens szerződést írni és közzétenni, és képes lenne kapcsolatba lépni azt. Talán néhány dolgot egyszerűbben is meg lehetett volna magyarázni.
Talán a megvalósítás egyes aspektusait hatékonyabban és elegánsabban is meg lehetett volna csinálni, de akkor még több időbe telt volna a cikk elkészítése. Az is lehet, hogy valahol hibáztam, vagy valamit nem értettem, ezért ha valami komoly dolgot csinál, akkor a hivatalos dokumentációra vagy a TON kóddal ellátott hivatalos adattárra kell hagyatkoznia.
Megjegyzendő, hogy mivel maga a TON még a fejlesztés aktív szakaszában van, előfordulhatnak olyan változások, amelyek megszakítják a cikkben szereplő bármely lépést (ami az írás során történt, már javítva lett), de az általános megközelítés nem valószínű, hogy megváltozik.
Nem beszélek a TON jövőjéről. Talán a platform valami nagy lesz, és érdemes időt szánnunk a tanulmányozására, és most egy rést kell betöltenünk termékeinkkel.
A Facebookról is van Libra, amelynek potenciális felhasználói közönsége nagyobb, mint TON. A Mérlegről szinte semmit nem tudok, a fórumból ítélve ott sokkal nagyobb az aktivitás, mint a TON közösségben. Bár a TON fejlesztői és közössége inkább underground, ami szintén menő.
Csevegés a TON-ról a Telegramban, ami valóban segített a kezdeti szakaszban kitalálni. Szerintem nem lesz hiba, ha azt mondom, hogy mindenki ott van, aki írt valamit a TON-nak. Ott is kérhetsz tesztgrammot. https://t.me/tondev_ru
Egy másik beszélgetés a TON-ról, amelyben hasznos információkat találtam: https://t.me/TONgramDev