Comment rédiger et publier un contrat intelligent dans le Telegram Open Network (TON)

De quoi parle l'article?


Dans l'article, je parlerai de la façon dont j'ai participé au premier (sur deux) concours de télégramme blockchain, qui n'a pas remporté le prix et a décidé d'enregistrer l'expérience dans l'article afin qu'il n'aille pas dans l'oubli et puisse éventuellement aider quelqu'un.


Comme je ne voulais pas écrire de code abstrait, mais faire quelque chose de fonctionnel, j'ai écrit pour un article un contrat intelligent, une loterie instantanée et un site qui montre des données de contrat intelligent directement à partir de TON sans utiliser de stockages intermédiaires.


Cet article sera utile à ceux qui souhaitent faire leur premier contrat intelligent dans TON, mais ne savent pas par où commencer.


En utilisant l'exemple de la loterie, je passerai de la mise en place de l'environnement à la publication d'un contrat intelligent, l'interaction avec celui-ci et l'écriture d'un site web pour recevoir et publier des données.


À propos de la participation au concours


En octobre de l'année dernière, Telegram a annoncé un concours de blockchain avec de nouvelles langues Fiftet FunC. Il était nécessaire de choisir d'écrire l'un des cinq contrats intelligents proposés. J'ai pensé que ce serait bien de faire quelque chose d'inhabituel, d'apprendre une langue et de faire quelque chose, même si à l'avenir vous n'avez rien d'autre à écrire. De plus, le sujet est constamment entendu.


Il vaut la peine de dire que je n'avais pas d'expérience dans l'élaboration de contrats intelligents.


J’avais prévu de participer jusqu’à la fin, jusqu’à présent, il s’agit de rédiger un article critique, mais j’ai bien compris dès la première étape. J'ai écrit un portefeuille multi-signatures FunCet cela a généralement fonctionné. Basé sur un contrat intelligent pour Solidity .


, - . 40 60 . , , . , , .


, , , - , , . , . — , .


, . , .


- TON


- . , - -.


- FunC Fift, Fift- TON Virtual Machine (TVM). . .


, TVM Fift . .


- — FunC. , - - , - . .


- FunC, Fift-.


- . Fift, - , .boc ( "bag of cells"), , , , -.


-, .


TON , - , .


- TON .boc - ( ). - , (, -) (, - TON).


, , . , . -, Fift FunC , .


Telegram-, Telegram , Fift FunC. .


.


TON


MacOS Ubuntu 18.04 LTS Docker.


lite-client , TON.


. . Ubuntu ( MacOS 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 

lite-client, Fift FunC.


TON . ~/TON.


cd ~/TON
git clone https://github.com/ton-blockchain/ton.git
cd ./ton
git submodule update --init --recursive

Fift FunC.


. ~/TON/ton. ~/TON build .


mkdir ~/TON/build 
cd ~/TON/build
cmake ../ton

- lite-client, Fift FunC, . .


cmake --build . --target lite-client
cmake --build . --target fift
cmake --build . --target func

lite-client .


wget https://test.ton.org/ton-lite-client-test1.config.json

TON


lite-client.


cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json

, .


[ 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)
...

help .


help

, .


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- . 

-.




, - .


, , - N , 2 * N . 40%. , .


, . -, TON.


-


FunC, Visual Studio Code, -, . - Fift, VSC.


.


- , . TON.


- . , recv_external() , TON, lite-client. , recv_internal() TON - . .


, , .


() recv_internal(slice in_msg) impure {
    ;; TODO: implementation 
}

() recv_external(slice in_msg) impure {
    ;; TODO: implementation  
}

slice. TON Blockchain TVM cell cell, 1023 4 .


TVM cell slice slice cell , . , - slice recv_external() recv_internal().


impure — , , -.


lottery-code.fc .


~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc 


~/TON/build/crypto/func -help

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

, .


, Asm.fif, Fift Fift-.


- lottery-test-suite.fif , - code, :


"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

, , 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 , TVM ( ). c7 . rand_seed , .


recv_internal recv_external 0 -1 -.


-. lottery-test-suite.fif.


storage cell, -.


message , - . .


variable storage 
<b b> storage ! 

variable message 
<b b> message ! 

TVM runvmctx .


message @ 
recv_external 
code 
storage @ 
c7 
runvmctx 

Fift.


.


export FIFTPATH=~/TON/ton/crypto/fift/lib //      
~/TON/build/crypto/fift -s lottery-test-suite.fif 

.


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=0

, - .


-


. , recv_external().


. ,


  • -, -.
  • - , - .

, , . , , .


. . - 0. - . -, . , - 1.


lottery-test-suite.fif . , . , 166, 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"

.


 ~/TON/build/crypto/fift -s lottery-test-suite.fif 

.


[ 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

lottery-test-suite.fif .


- 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 , .


, , .


. in_msg~load_uint(32) 32- unsigned int 165 .


32 -. , , . , , .


.


~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc 

lottery-test-suite.fif, . , .


~/TON/build/crypto/fift -s lottery-test-suite.fif

.


, - . , , "include".


build.sh .


#!/bin/bash

~/TON/build/crypto/func -SPA -R -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc

.


chmod +x ./build.sh

, , lottery-compiled.fif. code.


sh , lottery-compiled-for-test.fif . lottery-test-suite.fif.


# 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

, lottery-compiled-for-test.fif, lottery-test-suite.fif.


lottery-test-suite.fif "lottery-compiled-for-test.fif" include.


, , .


~/TON/build/crypto/fift -s lottery-test-suite.fif

, , test.sh, build.sh, .


touch test.sh
chmod +x test.sh

test.sh.


./build.sh 

echo "\nCompilation completed\n"

export FIFTPATH=~/TON/ton/crypto/fift/lib
~/TON/build/crypto/fift -s lottery-test-suite.fif

, .


./test.sh

, test.sh . .


, .


build lottery-compiled.fif, lottery-compiled-for-test.fif. test lottery-test-suite.fif . .


-.


, , , . .


-. .


`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`   ,    . 

. pack_state(), -. , unpack_state() .


_ 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;
}

-. .


set_data() pack_state() -.


cell packed_state = pack_state(arg_1, .., arg_n); 
set_data(packed_state);

.


, ( , ).


- . , , .


/ test/keys/owner.pk. Fift .


`newkeypair`          . 

`drop`      (    )  

`.s`          

`"owner.pk" B>file`        `owner.pk`. 

`bye`    Fift. 

keys test .


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

owner.pk. , , .


. . file>B owner_private_key. priv>pub 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 !

.


-. , pack_state() 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 !

, .


, , .


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 !  

, , - message_to_send, hashu, ed25519_sign_uint Fift.


.


message_to_send @ 
recv_external 
code 
storage @
c7
runvmctx

.


, -, .


512 , 32 .


- .


. - , .


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));

.


, . , , . , .


-. .


, . not-owner.pk. . , . .


-. recv_external() .


, . . .


. , , - . , action 7- , , .


<b 0 32 u, 1 @ 7 u, new_owner_wc @  32 i, new_owner_account_id @ 256 u, b> message_to_sign !

storage Fift. Fift. . , .


. - message, action. , action: . .


, . - , 7 , . action. , . . .


. . , . .


. . -.


int balance() inline_ref method_id {
    return get_balance().pair_first();
}

-. -.


() 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
}

- . . , . , .


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));

- . , .


, - . - , accept_message().


-


. .


. - -.


, 32- workchain 256- workchain. , -1 12345, .


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

, , Fift. Fift .


~/TON/build/crypto/fift -i 

-1, 12345 "sender.addr":


-1 12345 "sender.addr" 

-rot, , , -:


"sender.addr" -1 12345

256 u>B 256- .


"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039

swap .


"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -1

32 i>B 32- .


"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFF

B+ .


 "sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF

swap.


BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" 

B>file , , . . Fift. sender.addr. test/addresses/.


, -. .


.


, , bounced , bounced, . bounced , - . , .


, , , .


, - .


. pack_order(), unpack_order(), remove_old_orders().


, , , orders.


-.


3 1/3 -.


, .


() 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));
}

. .


-


-, ( ).


-. - .


, , -. . 1/3 .


- TON


-. requests.


, .


. - . -, TON. . - -, . .


lottery-query.boc .


~/TON/build/crypto/fift -s requests/new-lottery.fif 0

: lottery.addr, lottery.pk.


-.


new wallet address = 0:044910149dbeaf8eadbb2b28722e7d6a2dc6e264ec2f1d9bebd6fb209079bc2a 
(Saving address to file lottery.addr)
Non-bounceable address (for init): 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd
Bounceable address (for later access): kQAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KpFY

TON.


$ ./lite-client/lite-client -C ton-lite-client-test1.config.json 
getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

, .


account state is empty

0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 . , - , .


> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

, - (state:account_uninit) 2 000 000 000 .


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 2000000000ng

-. lite-client .


> 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 

, .


> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

.


  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_active

, account_active.


.



-.


, . , 6.


, msg_seqno 165, action 2 9.5 .


<b 165 32 u, 2 7 u, 9500000000 Gram, b>

lottery.pk, -. .


- -


- -.


lite-client runmethod - -.


$ ./lite-client/lite-client -C ton-lite-client-test1.config.json
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd balance
arguments:  [ 104128 ] 
result:  [ 64633878952 ] 
...

result , balance() -.
.


> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_seqno
...
arguments:  [ 77871 ] 
result:  [ 1 ] 

.


> 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]) ] 

lite-client - - .


-


- Python - . .


TON Python lite-client. Docker Google Cloud. .



64 a . . , , .



. , , TON - .


- . , , -, , .


- - , TON .


, TON , - ( , ).


TON . - . .


Libra Facebook, TON. Libra , TON. TON , .



  1. : https://test.ton.org
  2. TON: https://github.com/ton-blockchain/ton
  3. : https://wallet.ton.org
  4. - : https://github.com/raiym/astonished
  5. -: https://ton-lottery.appspot.com
  6. Visual Studio Code FunC: https://github.com/raiym/func-visual-studio-plugin
  7. ON Telegram, . , , - TON. . https://t.me/tondev_ru
  8. Un autre chat sur TON dans lequel j'ai trouvé des informations utiles: https://t.me/TONgramDev
  9. La première étape du concours: https://contest.com/blockchain
  10. La deuxième étape du concours: https://contest.com/blockchain-2

All Articles