So schreiben und veröffentlichen Sie einen intelligenten Vertrag im Telegram Open Network (TON)

Wovon handelt der Artikel?


In dem Artikel werde ich darüber sprechen, wie ich am ersten (von zwei) Blockchain-Telegramm-Wettbewerb teilgenommen habe, den Preis nicht angenommen habe und beschlossen habe, die Erfahrung in dem Artikel aufzuzeichnen, damit er nicht in Vergessenheit gerät und möglicherweise jemandem hilft.


Da ich keinen abstrakten Code schreiben wollte, sondern etwas funktionieren wollte, schrieb ich für den Artikel einen intelligenten Vertrag, eine Sofortlotterie und eine Website, auf der intelligente Vertragsdaten direkt von TON ohne Verwendung von Zwischenspeichern angezeigt werden.


Dieser Artikel ist nützlich für diejenigen, die ihren ersten intelligenten Vertrag in TON abschließen möchten, aber nicht wissen, wo sie anfangen sollen.


Am Beispiel einer Lotterie gehe ich von der Einrichtung einer Umgebung über die Veröffentlichung eines intelligenten Vertrags, die Interaktion mit ihm und das Schreiben einer Website zum Empfangen und Veröffentlichen von Daten über.


Über die Teilnahme am Wettbewerb


Im Oktober letzten Jahres kündigte Telegram einen Blockchain-Wettbewerb mit neuen Sprachen Fiftund an FunC. Es war notwendig, einen der fünf vorgeschlagenen intelligenten Verträge zu schreiben. Ich dachte, es wäre schön, etwas Ungewöhnliches zu tun, eine Sprache zu lernen und etwas zu tun, auch wenn Sie in Zukunft nichts mehr schreiben müssen. Außerdem wird das Thema ständig gehört.


Es ist erwähnenswert, dass ich keine Erfahrung in der Entwicklung intelligenter Verträge hatte.


Ich hatte vor, bis zum Ende teilzunehmen. Bisher stellte sich heraus, dass ich einen Übersichtsartikel geschrieben habe, aber ich habe ihn gleich in der ersten Phase richtig verstanden. Ich habe eine Brieftasche mit mehreren Signaturen geschrieben FunCund es hat im Allgemeinen funktioniert. Basierend auf einem intelligenten Vertrag für 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. Ein weiterer Chat über TON, in dem ich nützliche Informationen gefunden habe: https://t.me/TONgramDev
  9. Die erste Phase des Wettbewerbs: https://contest.com/blockchain
  10. Die zweite Phase des Wettbewerbs: https://contest.com/blockchain-2

All Articles