Cara menulis dan menerbitkan kontrak pintar di Telegram Open Network (TON)

Tentang apa artikel ini?


Dalam artikel tersebut, saya akan berbicara tentang bagaimana saya berpartisipasi dalam kompetisi Telegram blockchain pertama, tidak mengambil hadiah dan memutuskan untuk mencatat pengalaman dalam artikel sehingga tidak terlupakan dan mungkin membantu seseorang.


Karena saya tidak ingin menulis kode abstrak, tetapi untuk melakukan sesuatu yang berhasil, untuk artikel saya menulis kontrak pintar lotre instan dan situs yang menunjukkan data kontrak pintar langsung dari TON tanpa menggunakan penyimpanan perantara.


Artikel ini akan berguna bagi mereka yang ingin membuat kontrak pintar pertama mereka di TON, tetapi tidak tahu harus mulai dari mana.


Dengan menggunakan contoh lotere, saya akan beralih dari pengaturan lingkungan ke penerbitan kontrak cerdas, berinteraksi dengannya dan menulis situs web untuk menerima dan mempublikasikan data.


Tentang partisipasi dalam kompetisi


Pada Oktober tahun lalu, Telegram mengumumkan kontes blockchain dengan bahasa baru Fiftdan FunC. Itu perlu untuk memilih untuk menulis salah satu dari lima kontrak pintar yang diusulkan. Saya pikir akan menyenangkan untuk melakukan sesuatu yang tidak biasa, belajar bahasa dan melakukan sesuatu, bahkan jika di masa depan Anda tidak perlu menulis apa pun. Plus, topiknya terus didengar.


Perlu dikatakan bahwa saya tidak memiliki pengalaman dalam mengembangkan kontrak pintar.


Saya berencana untuk berpartisipasi sampai akhir, sejauh ini ternyata dan kemudian menulis artikel ulasan, tetapi saya sudah benar di tahap pertama. Saya menulis dompet multi-tanda tangan FunCdan umumnya berfungsi. Berdasarkan kontrak pintar untuk 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. Obrolan lain tentang TON di mana saya menemukan informasi yang berguna: https://t.me/TONgramDev
  9. Tahap pertama kompetisi: https://contest.com/blockchain
  10. Tahap kedua kompetisi: https://contest.com/blockchain-2

All Articles