如何在电报开放网络(TON)中编写和发布智能合约

这篇文章是关于什么的?


在本文中,我将谈论我如何参加第一场(两场)区块链电报比赛,但没有获得奖金并决定在文章中记录经验,以免遗忘并可能帮助他人。


由于我不想编写抽象代码,而是想做点工作,因此,对于本文,我写了一个智能合约,即开型彩票和一个站点,该站点直接从TON显示智能合约数据,而无需使用中间存储。


对于那些想在TON签第一个智能合约但不知道从哪里开始的人来说,本文将是有用的。


我将以彩票为例,从建立环境到发布智能合约,与之互动以及编写网站来接收和发布数据的过程。


关于参加比赛


去年10月,Telegram宣布了使用新语言Fift的区块链竞赛FunC有必要选择编写五个拟议的智能合约中的任何一个。我认为做一些不同寻常的事情,学习一种语言并做一些事情会很好,即使将来您不必写其他任何东西。另外,这个话题经常被听到。


值得一说的是,我没有开发智能合约的经验。


我计划参加直到最后,到目前为止,事实证明是这样,在写了一篇评论文章之后,但是我在第一阶段就立即参与其中。写了一个多签名的钱包FunC,它通常可以用。基于Solidity智能合约


那时,我算了一下,至少要获得一些奖金,这绝对够了。结果,在60位参与者中,大约有40位成为了赢家,而我却不在其中。一般来说,这没关系,但是有一件事困扰着我。在宣布结果时,还没有对我的合同的测试进行审查,我问了聊天中的参与者是否还有其他人,没有。


在我看来,两天后,法官们注意到我的信息,但发表了评论,但我仍然不明白,他们在评审过程中无意中错过了我的聪明合同,或者只是觉得这很糟糕以至于不需要评论。我在比赛页面上问了一个问题,但没有收到答复。尽管谁判断不是秘密,但他认为没有必要写个人信息。


, . , .


- 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. 关于TON的另一个聊天,在其中我找到了有用的信息:https : //t.me/TONgramDev
  9. 比赛的第一阶段:https : //contest.com/blockchain
  10. 比赛第二阶段:https//contest.com/blockchain-2

All Articles