Sobre o que é este artigo?
No artigo, falarei sobre como participei da primeira (das duas) competições de Telegram blockchain, não recebi o prêmio e decidi registrar a experiência no artigo para que não caísse no esquecimento e possivelmente ajudasse alguém.
Como eu não queria escrever código abstrato, mas fazer alguma coisa funcionar, escrevi para o artigo um contrato inteligente, uma loteria instantânea e um site que mostra dados inteligentes do contrato diretamente da TON sem usar armazenamentos intermediários.
Este artigo será útil para quem deseja fazer seu primeiro contrato inteligente em TON, mas não sabe por onde começar.
Usando o exemplo de uma loteria, passarei da criação de um ambiente para a publicação de um contrato inteligente, interagindo com ele e escrevendo um site para receber e publicar dados.
Sobre a participação na competição
Em outubro do ano passado, o Telegram anunciou um concurso de blockchain com novos idiomas Fift
e FunC
. Foi necessário optar por escrever qualquer um dos cinco contratos inteligentes propostos. Eu pensei que seria bom fazer algo incomum, aprender um idioma e fazer algo, mesmo que no futuro você não precise escrever mais nada. Além disso, o tópico está sendo ouvido constantemente.
Vale dizer que eu não tinha experiência no desenvolvimento de contratos inteligentes.
Planejei participar até o final, até o momento, e depois de escrever um artigo de revisão, mas me envolvi imediatamente na primeira etapa. Eu escrevi uma carteira com várias assinaturas FunC
e geralmente funcionava. Baseado em um contrato inteligente para 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 , .
- : 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
- Outro bate-papo sobre o TON no qual encontrei informações úteis: https://t.me/TONgramDev
- A primeira etapa da competição: https://contest.com/blockchain
- A segunda etapa da competição: https://contest.com/blockchain-2