这篇文章是关于什么的?
在本文中,我将谈论我如何参加第一场(两场)区块链电报比赛,但没有获得奖金并决定在文章中记录经验,以免遗忘并可能帮助他人。
由于我不想编写抽象代码,而是想做点工作,因此,对于本文,我写了一个智能合约,即开型彩票和一个站点,该站点直接从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 , .
- : https://test.ton.org
- TON: https://github.com/ton-blockchain/ton
- : https://wallet.ton.org
- - : https://github.com/raiym/astonished
- -: https://ton-lottery.appspot.com
- Visual Studio Code FunC: https://github.com/raiym/func-visual-studio-plugin
- ON Telegram, . , , - TON. . https://t.me/tondev_ru
- 关于TON的另一个聊天,在其中我找到了有用的信息:https : //t.me/TONgramDev
- 比赛的第一阶段:https : //contest.com/blockchain
- 比赛第二阶段:https://contest.com/blockchain-2