Sobre com escriure i publicar un contracte intel·ligent a TON
De què tracta aquest article?
A l'article us parlaré de com vaig participar en el primer (de dos) concurs de blockchain de Telegram, no em vaig endur cap premi i vaig decidir deixar constància de la meva experiència en un article perquè no s'enfonsi en l'oblit i, potser, ajudés. algú.
Com que no volia escriure codi abstracte, sinó fer alguna cosa que funcionés, per a l'article vaig escriure un contracte intel·ligent per a una loteria instantània i un lloc web que mostra dades de contracte intel·ligent directament des de TON sense utilitzar emmagatzematge intermedi.
L'article serà útil per a aquells que vulguin fer el seu primer contracte intel·ligent a TON, però no saben per on començar.
Utilitzant la loteria com a exemple, passaré d'instal·lar l'entorn a publicar un contracte intel·ligent, interactuar amb ell i escriure un lloc web per rebre i publicar dades.
Sobre la participació en el concurs
L'octubre passat, Telegram va anunciar un concurs de blockchain amb nous idiomes Fift и FunC. Calia triar entre escriure qualsevol dels cinc contractes intel·ligents proposats. Vaig pensar que estaria bé fer alguna cosa diferent, aprendre un idioma i fer alguna cosa, encara que no hagi d'escriure res més en el futur. A més, el tema està constantment als llavis.
Val la pena dir que no tenia experiència desenvolupant contractes intel·ligents.
Tenia previst participar fins al final fins que pogués i després escriure un article de ressenya, però vaig fracassar de seguida al primer. jo amb multisigna activada FunC i en general va funcionar. Ho vaig prendre com a base .
En aquell moment, vaig pensar que definitivament això n'hi havia prou per ocupar almenys algun lloc de premi. Com a resultat, uns 40 de cada 60 participants es van convertir en premiats i jo no vaig estar entre ells. En general, no hi ha res dolent amb això, però una cosa em va molestar. En el moment de l'anunci dels resultats no s'havia fet la revisió de la prova del meu contracte, vaig preguntar als participants al xat si hi havia algú més que no la tingués, no n'hi havia.
Pel que sembla, prestant atenció als meus missatges, dos dies després els jutges van publicar un comentari i encara no entenc si accidentalment van perdre el meu contracte intel·ligent durant el judici o simplement van pensar que era tan dolent que no necessitava cap comentari. Vaig fer una pregunta a la pàgina, però no vaig rebre resposta. Encara que no és cap secret qui va jutjar, vaig considerar innecessari escriure missatges personals.
Es va dedicar molt de temps a la comprensió, així que es va decidir escriure un article. Com que encara no hi ha molta informació, aquest article ajudarà a estalviar temps a tots els interessats.
El concepte de contractes intel·ligents a TON
Abans d'escriure res, heu d'esbrinar des de quin costat us heu d'enfocar. Per tant, ara us explicaré de quines parts consta el sistema. Més precisament, quines parts cal saber per redactar almenys algun tipus de contracte de treball.
Ens centrarem a escriure un contracte intel·ligent i treballar-hi TON Virtual Machine (TVM), Fift и FunC, de manera que l'article s'assembla més a una descripció del desenvolupament d'un programa normal. No ens detenem aquí en com funciona la plataforma.
En general sobre com funciona TVM i llengua Fift hi ha una bona documentació oficial. Mentre participava al concurs i ara mentre escrivia el contracte actual, sovint vaig recórrer a ella.
L'idioma principal en què s'escriuen els contractes intel·ligents és FunC. De moment no hi ha documentació sobre això, per tant, per escriure alguna cosa, cal estudiar exemples de contractes intel·ligents del repositori oficial i la implementació del propi llenguatge allà, a més, podeu mirar exemples de contractes intel·ligents dels dos últims. competicions. Enllaços al final de l'article.
Suposem que ja hem escrit un contracte intel·ligent FunC, després compilem el codi a l'assemblador Fift.
El contracte intel·ligent compilat encara s'ha de publicar. Per fer-ho, heu d'escriure una funció Fift, que prendrà el codi del contracte intel·ligent i alguns altres paràmetres com a entrada, i la sortida serà un fitxer amb l'extensió .boc (que vol dir "bossa de cel·les"), i, segons com l'escrivim, una clau privada i una adreça, que es genera a partir del codi del contracte intel·ligent. Ja podeu enviar grams a l'adreça d'un contracte intel·ligent que encara no s'ha publicat.
Per publicar un contracte intel·ligent en TON rebut .boc el fitxer s'haurà d'enviar a la cadena de blocs mitjançant un client lleuger (més informació a continuació). Però abans de publicar, cal transferir grams a l'adreça generada, en cas contrari, el contracte intel·ligent no es publicarà. Després de la publicació, podeu interactuar amb el contracte intel·ligent enviant-li missatges des de l'exterior (per exemple, amb un client lleuger) o des de dins (per exemple, un contracte intel·ligent envia un missatge a un altre dins de TON).
Un cop entenem com es publica el codi, és més fàcil. Aproximadament sabem què volem escriure i com funcionarà el nostre programa. I mentre escrivim, busquem com això ja s'implementa als contractes intel·ligents existents, o mirem el codi d'implementació Fift и FunC al repositori oficial, o consulta la documentació oficial.
Molt sovint buscava paraules clau al xat de Telegram on es reunien tots els participants de la competició i els empleats de Telegram, i va passar que durant la competició tothom s'hi reunia i començava a parlar de Fift i FunC. Enllaç al final de l'article.
És hora de passar de la teoria a la pràctica.
Preparant l'entorn per treballar amb TON
Vaig fer tot el que es descriurà a l'article sobre MacOS i ho vaig comprovar dues vegades en un net. Ubuntu 18.04 LTS a Docker.
El primer que heu de fer és descarregar i instal·lar lite-client amb el qual podeu enviar peticions a TON.
Les instruccions del lloc web oficial descriuen el procés d'instal·lació de manera força completa i clara, ometent alguns detalls. Aquí seguim les instruccions, instal·lant les dependències que falten al llarg del procés. No he compilat cada projecte jo mateix i l'he instal·lat des del repositori oficial. Ubuntu (a MacOS vaig fer servir brew).
apt -y install git
apt -y install wget
apt -y install cmake
apt -y install g++
apt -y install zlib1g-dev
apt -y install libssl-dev Un cop instal·lades totes les dependències, podeu instal·lar-les lite-client, Fift, FunC.
Primer, clonem el dipòsit TON juntament amb les seves dependències. Per comoditat, ho farem tot en una carpeta ~/TON.
cd ~/TON
git clone https://github.com/ton-blockchain/ton.git
cd ./ton
git submodule update --init --recursiveEl repositori també emmagatzema implementacions Fift и FunC.
Ara estem preparats per muntar el projecte. El codi del repositori es clona en una carpeta ~/TON/ton. En ~/TON crear una carpeta build i recollir-hi el projecte.
mkdir ~/TON/build
cd ~/TON/build
cmake ../tonCom que anem a escriure un contracte intel·ligent, no només necessitem lite-clientSinó Fift с FunC, així que anem a compilar-ho tot. No és un procés ràpid, així que estem esperant.
cmake --build . --target lite-client
cmake --build . --target fift
cmake --build . --target funcA continuació, descarregueu el fitxer de configuració que conté dades sobre el node al qual lite-client connectarà.
wget https://test.ton.org/ton-lite-client-test1.config.jsonFent les primeres peticions a TON
Ara anem a llançar lite-client.
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.jsonSi la compilació va tenir èxit, després del llançament veureu un registre de la connexió del client lleuger al node.
[ 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)
...Podeu executar l'ordre help i veure quines ordres hi ha disponibles.
helpEnumerem les ordres que utilitzarem en aquest article.
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 parameterslast получает последний созданный блок с сервера.
sendfile <filename> отправляет в TON файл с сообщением, именно с помощью этой команды публикуется смарт-контракт и запрсосы к нему.
getaccount <addr> загружает текущее состояние смарт-контракта с указанным адресом.
runmethod <addr> [<block-id-ext>] <method-id> <params> запускает get-методы смартконтракта. Ara estem preparats per redactar el contracte en si.
Implementació
Idea
Com he escrit més amunt, el contracte intel·ligent que estem escrivint és una loteria.
A més, no es tracta d'una loteria en la qual cal comprar un bitllet i esperar una hora, dia o mes, sinó d'una instantània en què l'usuari es trasllada a l'adreça del contracte. N grams i el recupera a l'instant 2 * N grams o perd. Farem que la probabilitat de guanyar un 40%. Si no hi ha prou grams per al pagament, considerarem la transacció com a recàrrega.
A més, és important que les apostes es puguin veure en temps real i d'una forma convenient, de manera que l'usuari pugui comprendre immediatament si ha guanyat o ha perdut. Per tant, heu de fer un lloc web que mostri apostes i resultats directament de TON.
Redacció d'un contracte intel·ligent
Per comoditat, he destacat el codi de FunC; el connector es pot trobar i instal·lar a la cerca de Visual Studio Code; si de sobte voleu afegir alguna cosa, he fet que el connector estigui disponible públicament. A més, algú va crear prèviament un connector per treballar amb Fift, també podeu instal·lar-lo i trobar-lo a VSC.
Creem immediatament un repositori on comprometrem els resultats intermedis.
Per facilitar-nos la vida, redactarem un contracte intel·ligent i el provarem localment fins que estigui llest. Només després el publicarem a TON.
El contracte intel·ligent té dos mètodes externs als quals es pot accedir. Primer, recv_external() aquesta funció s'executa quan una sol·licitud al contracte prové de l'exterior, és a dir, no de TON, per exemple, quan nosaltres mateixos generem un missatge i l'enviem a través del client lite. En segon lloc, recv_internal() és aquí quan, dins de la mateixa TON, qualsevol contracte fa referència al nostre. En ambdós casos, podeu passar paràmetres a la funció.
Comencem amb un exemple senzill que funcionarà si es publica, però no hi ha cap càrrega funcional.
() recv_internal(slice in_msg) impure {
;; TODO: implementation
}
() recv_external(slice in_msg) impure {
;; TODO: implementation
}Aquí hem d'explicar què és slice. Totes les dades emmagatzemades a TON Blockchain són una col·lecció TVM cell o simplement cell, en aquesta cel·la podeu emmagatzemar fins a 1023 bits de dades i fins a 4 enllaços a altres cel·les.
TVM cell slice o slice això forma part de l'existent cell s'utilitza per analitzar-lo, quedarà clar més endavant. El més important per a nosaltres és que ens podem transferir slice i, segons el tipus de missatge, processar les dades recv_external() o recv_internal().
impure — una paraula clau que indica que la funció modifica les dades del contracte intel·ligent.
Desem el codi del contracte lottery-code.fc i compilar.
~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc El significat de les banderes es pot veure mitjançant l'ordre
~/TON/build/crypto/func -helpHem compilat el codi ensamblador de 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>cEs pot posar en marxa localment, per a això prepararem l'entorn.
Tingueu en compte que la primera línia es connecta Asm.fif, aquest és el codi escrit a Fift per a l'assemblador de Fift.
Com que volem executar i provar el contracte intel·ligent localment, crearem un fitxer lottery-test-suite.fif i copieu-hi el codi compilat, substituint-hi l'última línia, que escriu el codi del contracte intel·ligent en una constant codeper transferir-lo a la màquina virtual:
"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
Fins aquí sembla clar, ara afegim al mateix fitxer el codi que farem servir per llançar TVM.
0 tuple 0x076ef1ea , // magic
0 , 0 , // actions msg_sents
1570998536 , // unix_time
1 , 1 , 3 , // block_lt, trans_lt, rand_seed
0 tuple 100000000000000 , dictnew , , // remaining balance
0 , dictnew , // contract_address, global_config
1 tuple // wrap to another tuple
constant c7
0 constant recv_internal // to run recv_internal()
-1 constant recv_external // to invoke recv_external()В c7 registrem el context, és a dir, les dades amb què es posarà en marxa el TVM (o estat de xarxa). Fins i tot durant la competició, un dels desenvolupadors va mostrar com crear c7 i vaig copiar. En aquest article potser hauríem de canviar rand_seed ja que la generació d'un nombre aleatori en depèn i si no es modifica, es retornarà el mateix nombre cada vegada.
recv_internal и recv_external les constants amb valors 0 i -1 seran les encarregades de cridar les funcions corresponents en el contracte intel·ligent.
Ara estem preparats per crear la primera prova per al nostre contracte intel·ligent buit. Per a més claredat, ara per ara afegirem totes les proves al mateix fitxer lottery-test-suite.fif.
Creem una variable storage i escriu-hi un de buit cell, aquest serà l'emmagatzematge del contracte intel·ligent.
message Aquest és el missatge que transmetrem al contacte intel·ligent des de l'exterior. De moment també el deixarem buit.
variable storage
<b b> storage !
variable message
<b b> message ! Després d'haver preparat les constants i variables, iniciem TVM mitjançant l'ordre runvmctx i passar els paràmetres creats a l'entrada.
message @
recv_external
code
storage @
c7
runvmctx Al final ho aconseguirem codi intermedi per Fift.
Ara podem executar el codi resultant.
export FIFTPATH=~/TON/ton/crypto/fift/lib // выполняем один раз для удобства
~/TON/build/crypto/fift -s lottery-test-suite.fif El programa hauria d'executar-se sense errors i a la sortida veurem el registre d'execució:
execute SETCP 0
execute DICTPUSHCONST 19 (xC_,1)
execute DICTIGETJMPZ
execute DROP
execute implicit RET
[ 3][t 0][1582281699.325381279][vm.cpp:479] steps: 5 gas: used=304, max=9223372036854775807, limit=9223372036854775807, credit=0Genial, hem escrit la primera versió de treball del contracte intel·ligent.
Ara hem d'afegir funcionalitats. Primer tractem els missatges que provenen del món exterior recv_external()
El mateix desenvolupador tria el format de missatge que el contracte pot acceptar.
Però normalment
- en primer lloc, volem protegir el nostre contracte del món exterior i fer-lo de manera que només el propietari del contracte li pugui enviar missatges externs.
- en segon lloc, quan enviem un missatge vàlid a TON, volem que això passi exactament una vegada i quan tornem a enviar el mateix missatge, el contracte intel·ligent el rebutja.
Així que gairebé tots els contractes resolen aquests dos problemes, ja que el nostre contracte accepta missatges externs, també ens hem de fer càrrec.
Ho farem en ordre invers. Primer, resolem el problema amb la repetició; si el contracte ja ha rebut aquest missatge i l'ha processat, no l'executarà una segona vegada. I després resoldrem el problema perquè només un cert cercle de persones pugui enviar missatges al contracte intel·ligent.
Hi ha diferents maneres de resoldre el problema amb missatges duplicats. Així és com ho farem. Al contracte intel·ligent, inicialitzem el comptador de missatges rebuts amb el valor inicial 0. En cada missatge al contracte intel·ligent, afegirem el valor del comptador actual. Si el valor del comptador del missatge no coincideix amb el valor del contracte intel·ligent, no el processem; si ho fa, el processem i augmentem el comptador del contracte intel·ligent en 1.
Tornem a lottery-test-suite.fif i afegiu-hi una segona prova. Si enviem un número incorrecte, el codi hauria de llançar una excepció. Per exemple, deixeu que les dades del contracte emmagatzemin 166 i us enviarem 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"Llançarem.
~/TON/build/crypto/fift -s lottery-test-suite.fif I veurem que la prova s'executa amb un error.
[ 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 passedEn aquesta etapa lottery-test-suite.fif hauria de semblar .
Ara afegim la lògica del comptador al contracte intel·ligent 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 rau el missatge que enviem.
El primer que fem és comprovar si el missatge conté dades, si no, simplement sortim.
A continuació analitzem el missatge. in_msg~load_uint(32) carrega el número 165, de 32 bits unsigned int del missatge transmès.
A continuació, carreguem 32 bits de l'emmagatzematge del contracte intel·ligent. Comprovem que el número carregat coincideixi amb el que s'ha passat; si no, llencem una excepció. En el nostre cas, com que estem passant un no partit, s'hauria de llançar una excepció.
Ara compilem.
~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc Copieu el codi resultant a lottery-test-suite.fif, sense oblidar substituir l'última línia.
Comprovem que la prova passa:
~/TON/build/crypto/fift -s lottery-test-suite.fifPodeu veure el commit corresponent amb els resultats actuals.
Tingueu en compte que és inconvenient copiar constantment el codi compilat d'un contracte intel·ligent en un fitxer amb proves, de manera que escriurem un script que escriurà el codi en una constant per a nosaltres, i simplement connectarem el codi compilat a les nostres proves mitjançant "include".
Creeu un fitxer a la carpeta del projecte build.sh amb el següent contingut.
#!/bin/bash
~/TON/build/crypto/func -SPA -R -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fcFem-lo executable.
chmod +x ./build.shAra, només heu d'executar el nostre script per compilar el contracte. Però a més d'això, hem d'escriure-ho en una constant code. Així que crearem un nou fitxer lotter-compiled-for-test.fif, que inclourem a l'expedient lottery-test-suite.fif.
Afegim codi faldilla a sh, que simplement duplicarà el fitxer compilat lotter-compiled-for-test.fif i canvieu-hi l'última línia.
# 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.fifAra, per comprovar-ho, executem l'script resultant i es generarà un fitxer lottery-compiled-for-test.fif, que inclourem al nostre lottery-test-suite.fif
В lottery-test-suite.fif suprimiu el codi del contracte i afegiu la línia "lottery-compiled-for-test.fif" include.
Fem proves per comprovar que aproven.
~/TON/build/crypto/fift -s lottery-test-suite.fifGenial, ara per automatitzar el llançament de proves, creem un fitxer test.sh, que s'executarà primer build.sh, i després executar les proves.
touch test.sh
chmod +x test.shEscrivim a dins
./build.sh
echo "nCompilation completedn"
export FIFTPATH=~/TON/ton/crypto/fift/lib
~/TON/build/crypto/fift -s lottery-test-suite.fifFem-ho test.sh i executeu-lo per assegurar-vos que les proves funcionen.
chmod +x ./test.sh
./test.shComprovem que el contracte es compila i les proves estan executades.
Genial, ara a l'inici test.sh Les proves es compilaran i s'executaran immediatament. Aquí teniu l'enllaç a .
D'acord, abans de continuar, fem una cosa més per comoditat.
Creem una carpeta build on emmagatzemarem el contracte copiat i el seu clon escrit en una constant lottery-compiled.fif, lottery-compiled-for-test.fif. Creem també una carpeta test on s'emmagatzemarà el fitxer de prova? lottery-test-suite.fif i possiblement altres fitxers de suport. .
Continuem desenvolupant el contracte intel·ligent.
A continuació s'hauria de fer una prova que comprove que s'ha rebut el missatge i que el comptador s'actualitza a la botiga quan enviem el número correcte. Però ho farem més tard.
Ara pensem en quina estructura de dades i quines dades s'han d'emmagatzemar al contracte intel·ligent.
Descriuré tot el que emmagatzemem.
`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` переменная типа словарь, хранит последние двадцать ставок. A continuació, heu d'escriure dues funcions. Truquem al primer pack_state(), que empaquetarà les dades per al seu posterior desament a l'emmagatzematge del contracte intel·ligent. Anem a cridar al segon unpack_state() llegirà i retornarà dades de l'emmagatzematge.
_ 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;
}Afegim aquestes dues funcions al començament del contracte intel·ligent. Funcionarà resultat intermedi.
Per desar dades, haureu de trucar a la funció integrada set_data() i escriurà dades des de pack_state() a l'emmagatzematge de contracte intel·ligent.
cell packed_state = pack_state(arg_1, .., arg_n);
set_data(packed_state);Ara que disposem de funcions convenients per escriure i llegir dades, podem seguir endavant.
Hem de comprovar que el missatge que arriba des de l'exterior està signat pel propietari del contracte (o un altre usuari que tingui accés a la clau privada).
Quan publiquem un contracte intel·ligent, podem inicialitzar-lo amb les dades que necessitem a l'emmagatzematge, que es desaran per a un ús futur. Allà gravarem la clau pública per poder comprovar que el missatge entrant estava signat amb la clau privada corresponent.
Abans de continuar, creem una clau privada i escrivim-hi test/keys/owner.pk. Per fer-ho, iniciem Fift en mode interactiu i executem quatre ordres.
`newkeypair` генерация публичного и приватного ключа и запись их в стек.
`drop` удаления из стека верхнего элемента (в данном случае публичный ключ)
`.s` просто посмотреть что лежит в стеке в данный момент
`"owner.pk" B>file` запись приватного ключа в файл с именем `owner.pk`.
`bye` завершает работу с Fift. Creem una carpeta keys dins de la carpeta test i escriu-hi la clau privada.
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
byeVeiem un fitxer a la carpeta actual owner.pk.
Traiem la clau pública de la pila i quan calgui la podem obtenir de la privada.
Ara hem d'escriure una verificació de signatura. Comencem amb la prova. Primer llegim la clau privada del fitxer mitjançant la funció file>B i escriu-lo en una variable owner_private_key, després utilitzant la funció priv>pub convertir la clau privada en una clau pública i escriure el resultat owner_public_key.
variable owner_private_key
variable owner_public_key
"./keys/owner.pk" file>B owner_private_key !
owner_private_key @ priv>pub owner_public_key !Necessitarem les dues claus.
Iniciem l'emmagatzematge del contracte intel·ligent amb dades arbitràries en la mateixa seqüència que a la funció pack_state()i escriu-lo en una variable storage.
variable owner_private_key
variable owner_public_key
variable orders
variable owner_wc
variable owner_account_id
"./keys/owner.pk" file>B owner_private_key !
owner_private_key @ priv>pub owner_public_key !
dictnew orders !
0 owner_wc !
0 owner_account_id !
<b 0 32 u, owner_public_key @ B, 0 32 u, 0 32 u, 0 Gram, 0 Gram, owner_wc @ 32 i, owner_account_id @ 256 u, orders @ dict, b> storage !A continuació, redactarem un missatge signat, només contindrà la signatura i el valor del comptador.
Primer, creem les dades que volem transmetre, després les signem amb una clau privada i finalment generem un missatge signat.
variable message_to_sign
variable message_to_send
variable signature
<b 0 32 u, b> message_to_sign !
message_to_sign @ hashu owner_private_key @ ed25519_sign_uint signature !
<b signature @ B, 0 32 u, b> <s message_to_send ! Com a resultat, el missatge que enviarem al contracte intel·ligent queda registrat en una variable message_to_send, sobre funcions hashu, ed25519_sign_uint pots llegir .
I per fer la prova tornem a trucar.
message_to_send @
recv_external
code
storage @
c7
runvmctxEl fitxer amb proves hauria de tenir aquest aspecte en aquesta fase.
Anem a fer la prova i fallarà, així que canviarem el contracte intel·ligent perquè pugui rebre missatges d'aquest format i verificar la signatura.
En primer lloc, comptem 512 bits de la signatura del missatge i l'escrivim en una variable, després comptem 32 bits de la variable comptador.
Com que tenim una funció per llegir dades de l'emmagatzematge del contracte intel·ligent, la farem servir.
El següent és comprovar el comptador transferit amb l'emmagatzematge i comprovar la signatura. Si alguna cosa no coincideix, llançarem una excepció amb el codi adequat.
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));Compromís rellevant .
Anem a fer les proves i veurem que la segona prova falla. Per dos motius, no hi ha prou bits al missatge i no hi ha prou bits a l'emmagatzematge, de manera que el codi es bloqueja en analitzar. Hem d'afegir una signatura al missatge que estem enviant i copiar l'emmagatzematge de l'última prova.
A la segona prova, afegirem una signatura de missatge i canviarem l'emmagatzematge del contracte intel·ligent. el fitxer amb proves sembla en aquest moment.
Anem a escriure una quarta prova, en la qual enviarem un missatge signat amb la clau privada d'una altra persona. Creem una altra clau privada i desem-la en un fitxer not-owner.pk. Signarem el missatge amb aquesta clau privada. Executem les proves i assegurem-nos que totes les passen. en aquest moment.
Ara per fi podem passar a implementar la lògica del contracte intel·ligent.
В recv_external() acceptarem dos tipus de missatges.
Com que el nostre contracte acumularà les pèrdues dels jugadors, aquests diners s'han de transferir al creador de la loteria. L'adreça de la cartera del creador de la loteria es registra a l'emmagatzematge quan es crea el contracte.
Per si de cas, necessitem la possibilitat de canviar l'adreça a la qual enviem grams dels perdedors. També hauríem de poder enviar grams de la loteria a l'adreça del propietari.
Comencem pel primer. Primer escrivim una prova que comprovarà que després d'enviar el missatge, el contracte intel·ligent ha desat la nova adreça a l'emmagatzematge. Cal tenir en compte que al missatge, a més del comptador i la nova adreça, també transmetem action Un nombre enter de 7 bits no negatiu, segons ell, triarem com processar el missatge al contracte intel·ligent.
<b 0 32 u, 1 @ 7 u, new_owner_wc @ 32 i, new_owner_account_id @ 256 u, b> message_to_sign !A la prova podeu veure com es deserialitza l'emmagatzematge smartcontract storage en Cinquena. La deserialització de variables es descriu a la documentació de Fift.
amb massa afegida.
Anem a fer la prova i ens assegurem que falla. Ara afegim lògica per canviar l'adreça del propietari de la loteria.
Al contracte intel·ligent continuem analitzant message, llegiu-hi action. Us recordem que en tindrem dos action: canviar l'adreça i enviar grams.
A continuació, llegim la nova adreça del titular del contracte i la desem a l'emmagatzematge.
Executem les proves i veiem que la tercera prova falla. Es bloqueja a causa del fet que ara el contracte analitza 7 bits del missatge, que falten a la prova. Afegiu-ne un que no existeixi al missatge action. Anem a fer les proves i veurem que tot passa. comprometre's amb els canvis. Genial.
Ara anem a escriure la lògica per enviar el nombre especificat de grams a l'adreça desada anteriorment.
Primer, escrivim una prova. Escriurem dues proves, una quan no hi ha prou equilibri, la segona quan tot ha de passar amb èxit. Es poden veure les proves .
Ara afegim el codi. Primer, anem a escriure dos mètodes d'ajuda. El primer mètode d'obtenció és esbrinar el saldo actual d'un contracte intel·ligent.
int balance() inline_ref method_id {
return get_balance().pair_first();
}I el segon és per enviar grams a un altre contracte intel·ligent. Vaig copiar completament aquest mètode d'un altre contracte intel·ligent.
() 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
}Afegim aquests dos mètodes al contracte intel·ligent i escrivim la lògica. Primer, analitzem el nombre de grams del missatge. Seguidament comprovem el balanç, si no n'hi ha prou llencem una excepció. Si tot està bé, enviem els grams a l'adreça desada i actualitzem el comptador.
int amount_to_send = message~load_grams();
throw_if(36, amount_to_send + 500000000 > balance());
accept_message();
send_grams(owner_wc, owner_account_id, amount_to_send);
set_data(pack_state(stored_seqno + 1, pubkey, order_seqno, number_of_wins, incoming_amount, outgoing_amount, owner_wc, owner_account_id, orders));sembla el contracte intel·ligent en aquest moment. Anem a fer les proves i ens assegurem que passen.
Per cert, es dedueix una comissió del contracte intel·ligent cada vegada per a un missatge processat. Per tal que els missatges del contracte intel·ligent executin la sol·licitud, després de les comprovacions bàsiques, heu de trucar accept_message().
Ara passem als missatges interns. De fet, només acceptarem grams i tornarem el doble al jugador si guanya i un terç al propietari si perd.
Primer, escrivim una prova senzilla. Per fer-ho, necessitem una adreça de prova del contracte intel·ligent des de la qual suposadament enviem grams al contracte intel·ligent.
L'adreça del contracte intel·ligent consta de dos números, un nombre enter de 32 bits responsable de la cadena de treball i un número de compte únic d'enter no negatiu de 256 bits en aquesta cadena de treball. Per exemple, -1 i 12345, aquesta és l'adreça que guardarem en un fitxer.
Vaig copiar la funció per desar l'adreça .
// ( wc addr fname -- ) Save address to file in 36-byte format
{ -rot 256 u>B swap 32 i>B B+ swap B>file } : save-addressVegem com funciona la funció, això ens donarà una comprensió de com funciona Fift. Inicieu Fift en mode interactiu.
~/TON/build/crypto/fift -i Primer introduïm -1, 12345 i el nom del futur fitxer "sender.addr" a la pila:
-1 12345 "sender.addr" El següent pas és executar la funció -rot, que desplaça la pila de tal manera que a la part superior de la pila hi ha un número de contracte intel·ligent únic:
"sender.addr" -1 12345256 u>B converteix un nombre enter no negatiu de 256 bits en bytes.
"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039swap intercanvia els dos elements superiors de la pila.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -132 i>B converteix un nombre enter de 32 bits en bytes.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFFB+ connecta dues seqüències de bytes.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFFDe nou swap.
BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" I finalment els bytes s'escriuen al fitxer B>file. Després d'això, la nostra pila està buida. Parem Fift. S'ha creat un fitxer a la carpeta actual sender.addr. Mourem el fitxer a la carpeta creada test/addresses/.
Escrivim una prova senzilla que enviarà grams a un contracte intel·ligent. .
Vegem ara la lògica de la loteria.
El primer que fem és comprovar el missatge bounced o no si bounced, llavors ho ignorem. bounced significa que el contracte retornarà grams si es produeix algun error. No retornarem grams si es produeix un error de sobte.
Comprovem, si el saldo és inferior a mig gram, llavors simplement acceptem el missatge i l'ignorem.
A continuació, analitzem l'adreça del contracte intel·ligent del qual prové el missatge.
Llegim les dades de l'emmagatzematge i després eliminem les apostes antigues de l'historial si n'hi ha més de vint. Per comoditat, vaig escriure tres funcions addicionals pack_order(), unpack_order(), remove_old_orders().
A continuació, mirem si el saldo no és suficient per al pagament, llavors considerem que això no és una aposta, sinó una reposició i guardem la reposició en orders.
Aleshores, finalment, l'essència del contracte intel·ligent.
En primer lloc, si el jugador perd, ho guardem a l'historial d'apostes i si l'import supera els 3 grams, enviem 1/3 al propietari del contracte intel·ligent.
Si el jugador guanya, enviarem el doble de la quantitat a l'adreça del jugador i després desem la informació sobre l'aposta a l'historial.
() 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));
}Això és tot. .
Ara tot el que queda és senzill, creem mètodes d'obtenció perquè puguem obtenir informació sobre l'estat del contracte des del món exterior (de fet, llegiu les dades del seu emmagatzematge de contracte intel·ligent).
. A continuació escriurem com rebre informació sobre un contracte intel·ligent.
També he oblidat afegir el codi que processarà la primera sol·licitud que es produeixi en publicar un contracte intel·ligent. . I més enllà error amb l'enviament d'1/3 de l'import al compte del propietari.
El següent pas és publicar el contracte intel·ligent. Creem una carpeta requests.
Vaig prendre com a base el codi de publicació que al repositori oficial.
Alguna cosa a la qual val la pena parar atenció. Generem un emmagatzematge de contracte intel·ligent i un missatge d'entrada. Després d'això, es genera l'adreça del contracte intel·ligent, és a dir, l'adreça es coneix fins i tot abans de la seva publicació a TON. A continuació, heu d'enviar diversos grams a aquesta adreça, i només després d'això heu d'enviar un fitxer amb el contracte intel·ligent en si mateix, ja que la xarxa cobra una comissió per emmagatzemar el contracte intel·ligent i les operacions en ella (validadors que emmagatzemen i executen smart contract). contractes). .
A continuació, executem el codi de publicació i obtenim lottery-query.boc fitxer i adreça del contracte intel·ligent.
~/TON/build/crypto/fift -s requests/new-lottery.fif 0No oblideu desar els fitxers generats: lottery-query.boc, lottery.addr, lottery.pk.
Entre altres coses, veurem l'adreça del contracte intel·ligent als registres d'execució.
new wallet address = 0:044910149dbeaf8eadbb2b28722e7d6a2dc6e264ec2f1d9bebd6fb209079bc2a
(Saving address to file lottery.addr)
Non-bounceable address (for init): 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd
Bounceable address (for later access): kQAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KpFYNomés per diversió, fem una sol·licitud a TON
$ ./lite-client/lite-client -C ton-lite-client-test1.config.json
getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydI veurem que el compte amb aquesta adreça està buit.
account state is emptyEnviem a l'adreça 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 Gram i al cap d'uns segons executem la mateixa ordre. Per enviar grams faig servir , i pots demanar a algú del xat grams de prova, dels quals parlaré al final de l'article.
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydSembla un no inicialitzat (state:account_uninit) un contracte intel·ligent amb la mateixa adreça i un saldo de 1 de nanograms.
account state is (account
addr:(addr_std
anycast:nothing workchain_id:0 address:x044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A)
storage_stat:(storage_info
used:(storage_used
cells:(var_uint len:1 value:1)
bits:(var_uint len:1 value:103)
public_cells:(var_uint len:0 value:0)) last_paid:1583257959
due_payment:nothing)
storage:(account_storage last_trans_lt:3825478000002
balance:(currencies
grams:(nanograms
amount:(var_uint len:4 value:2000000000))
other:(extra_currencies
dict:hme_empty))
state:account_uninit))
x{C00044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A20259C2F2F4CB3800000DEAC10776091DCD650004_}
last transaction lt = 3825478000001 hash = B043616AE016682699477FFF01E6E903878CDFD6846042BA1BFC64775E7AC6C4
account balance is 2000000000ngAra publiquem el contracte intel·ligent. Llencem lite-client i executem.
> sendfile lottery-query.boc
[ 1][t 2][1583008371.631410122][lite-client.cpp:966][!testnode] sending query from file lottery-query.boc
[ 3][t 1][1583008371.828550100][lite-client.cpp:976][!query] external message status is 1 Comprovem que el contracte s'ha publicat.
> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydEntre altres coses aconseguim.
storage:(account_storage last_trans_lt:3825499000002
balance:(currencies
grams:(nanograms
amount:(var_uint len:4 value:1987150999))
other:(extra_currencies
dict:hme_empty))
state:(account_activeHo veiem account_active.
Compromís corresponent amb els canvis .
Ara creem sol·licituds per interactuar amb el contracte intel·ligent.
Més concretament, deixarem la primera per canviar l'adreça com a obra independent, i la segona la farem per enviar grams a l'adreça del propietari. De fet, haurem de fer el mateix que a la prova d'enviament de grams.
Aquest és el missatge que enviarem al contracte intel·ligent, on msg_seqno 165, action 2 i 9.5 grams per enviar.
<b 165 32 u, 2 7 u, 9500000000 Gram, b>No oblidis signar el missatge amb la teva clau privada lottery.pk, que es va generar anteriorment en crear el contracte intel·ligent. .
Rebre informació d'un contracte intel·ligent mitjançant mètodes get
Ara mirem com executar mètodes d'obtenció de contracte intel·ligent.
Llançament lite-client i executeu els mètodes get que hem escrit.
$ ./lite-client/lite-client -C ton-lite-client-test1.config.json
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd balance
arguments: [ 104128 ]
result: [ 64633878952 ]
...В result conté el valor que retorna la funció balance() del nostre contracte intel·ligent.
Farem el mateix per a diversos mètodes més.
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_seqno
...
arguments: [ 77871 ]
result: [ 1 ] Demanem el teu historial d'apostes.
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_orders
...
arguments: [ 67442 ]
result: [ ([0 1 1583258284 10000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [1 3 1583258347 4000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [2 1 1583259901 50000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308]) ] Utilitzarem lite-client i obtindrem mètodes per mostrar informació sobre el contracte intel·ligent al lloc.
Mostrar dades de contracte intel·ligent al lloc web
Vaig escriure un lloc web senzill a Python per mostrar les dades del contracte intel·ligent d'una manera còmoda. Aquí no m'hi aprofundiré en detall i publicaré el lloc .
Les sol·licituds a TON es fan des de Python via lite-client. Per comoditat, el lloc està empaquetat a Docker i publicat a Google Cloud. .
Intentant
Ara intentem enviar-hi grams per a la reposició . Enviarem 40 grams. I fem un parell d'apostes per a la claredat. Veiem que el lloc mostra l'historial d'apostes, el percentatge de guanys actual i altra informació útil.
que vam guanyar el primer, vam perdre el segon.
Paraula posterior
L'article va resultar ser molt més llarg del que esperava, potser podria haver estat més curt, o potser només per a una persona que no sap res de TON i vol escriure i publicar un contracte intel·ligent no tan senzill amb la capacitat d'interactuar amb això. Potser algunes coses es podrien haver explicat de manera més senzilla.
Potser alguns aspectes de la implementació es podrien haver fet de manera més eficient i elegant, però llavors hauria pres encara més temps per preparar l'article. També és possible que m'he equivocat en algun lloc o que no hagi entès alguna cosa, així que si estàs fent alguna cosa seriosa, has de confiar en la documentació oficial o en el repositori oficial amb el codi TON.
Cal tenir en compte que, com que el propi TON encara es troba en l'etapa activa de desenvolupament, es poden produir canvis que trencaran qualsevol dels passos d'aquest article (cosa que va passar mentre escrivia, ja s'ha corregit), però l'enfocament general és poc probable que canviï.
No parlaré del futur de TON. Potser la plataforma esdevindrà alguna cosa gran i hauríem de dedicar temps a estudiar-la i omplir un nínxol amb els nostres productes ara.
També hi ha Libra de Facebook, que té un públic potencial d'usuaris més gran que TON. Gairebé no sé res de Libra, a jutjar pel fòrum hi ha molta més activitat que a la comunitat TON. Tot i que els desenvolupadors i la comunitat de TON s'assemblen més a underground, que també és genial.
Referències
- Documentació oficial de TON:
- Repositori oficial de TON:
- Cartera oficial per a diferents plataformes:
- Repositori de contracte intel·ligent d'aquest article:
- Enllaç al lloc web del contracte intel·ligent:
- Repositori per a l'extensió de Visual Studio Code per FunC:
- Xateja sobre TON a Telegram, que realment va ajudar a esbrinar-ho en l'etapa inicial. Crec que no serà un error si dic que tothom que ha escrit alguna cosa per a TON hi és. També podeu demanar grams de prova allà.
- Una altra xerrada sobre TON en la qual he trobat informació útil:
- Primera fase del concurs:
- Segona fase del concurs:
Font: www.habr.com
