TON (Telegram Open Network) test client and new Fift language for smart contracts

More than a year ago, it became known about the plans of the Telegram messenger to release its own decentralized network Telegram Open Network. Then a voluminous technical document became available, which, presumably, was written by Nikolai Durov and described the structure of the future network. For those who missed it, I recommend that you read my retelling of this document (Part 1, Part 2; the third part, alas, is still gathering dust in drafts).

Since then, there has been no significant news about the status of TON development, until a couple of days ago (in one of unofficial channels) the link to the page did not appear https://test.ton.org/download.htmlwhere are located:
ton-test-liteclient-full.tar.xz — light client sources for the TON test network;
ton-lite-client-test1.config.json — configuration file for connecting to the test network;
README - information about the assembly and launch of the client;
HOWTO — step-by-step instructions on how to create a smart contract using the client;
ton.pdf — updated document (dated March 2, 2019) with a technical overview of the TON network;
tvm.pdf — technical description of TVM (TON Virtual Machine, TON virtual machine);
tblkch.pdf — technical description of the TON blockchain;
fifthbase.pdf — a description of the new Fift language, designed to create smart contracts in TON.

I repeat, there was no official confirmation of the page and all these documents from Telegram, but the volume of these materials makes them quite plausible. Run the published client at one's own risk.

Building a test client

To begin with, let's try to build and run a test client - good, README describes this simple process in detail. I will do this using the example of macOS 10.14.5, I cannot vouch for the success of the assembly on other systems.

  1. Downloading and unpacking source archive. It is important to download the latest version as backwards compatibility is not guaranteed at this stage.

  2. Make sure you have the latest versions of make, cmake (version 3.0.2 or higher), OpenSSL (including C headers), g++, or clang installed on your system. I didn't have to install anything, everything came together right away.

  3. Suppose the sources are unpacked into a folder ~/lite-client. Separately from it, we create an empty folder for the assembled project (for example, ~/liteclient-build), and from it (cd ~/liteclient-build) call the commands:

    cmake ~/lite-client
    cmake --build . --target test-lite-client

    TON (Telegram Open Network) test client and new Fift language for smart contracts

    To build the Fift language interpreter for smart contracts (described below), we also call

    cmake --build . --target fift

  4. Downloading the current configuration file to connect to the test network and put it in the folder with the assembled client.

  5. Finish, you can run the client:

    ./test-lite-client -C ton-lite-client-test1.config.json

If everything is done correctly, then you should see something like this:

TON (Telegram Open Network) test client and new Fift language for smart contracts

As you can see, there are few available commands:
help - display this list of commands;
quit - go out;
time — show the current time on the server;
status - show the status of the connection and the local database;
last — update the state of the blockchain (download the last block). It's important to run this command before any queries to make sure you're seeing the actual state of the network.
sendfile <filename> — upload a local file to the TON network. This is how interaction with the network occurs, including, for example, the creation of new smart contracts and requests to transfer funds between accounts;
getaccount <address> — show the current (at the time of command execution last) the status of the account with the specified address;
privkey <filename> — load the private key from a local file.

If, when starting the client, pass a folder to it using the option -D, then it will add the last block of the masterchain into it:

./test-lite-client -C ton-lite-client-test1.config.json -D ~/ton-db-dir

Now we can move on to more interesting things - learn the Fift language, try to compile a smart contract (for example, create a test wallet), upload it to the network and try transferring funds between accounts.

Fift Language

From document fifthbase.pdf you can find out that the Telegram team created a new stack language to create smart contracts Five (apparently from the numeral fifth, similar to Forth, a language with which Fift has a lot in common).

The document is quite voluminous, 87 pages, and I will not retell its contents in detail within the framework of this article (at least, because I myself have not finished reading it :). I will dwell on the main points and give a couple of code examples in this language.

At a basic level, Fift's syntax is quite simple: its code consists of words, usually separated by spaces or newlines (special case: some words do not require a separator after themselves). Any word is a case-sensitive sequence of characters that corresponds to some definition (roughly speaking, what the interpreter should do when it encounters this word). If there is no word definition, the interpreter tries to parse it as a number and push it onto the stack. By the way, the numbers here - all of a sudden - are 257-bit integers, and there are no fractional ones at all - more precisely, they immediately turn into a pair of integers that form the numerator and denominator of a rational fraction.

Words tend to interact with the values ​​at the top of the stack. Separate type of words - prefix - uses not the stack, but the characters following them from the source file. For example, this is how string literals are implemented - the quote character (") is a prefix word that looks for the next (closing) quote, and pushes the string between them onto the stack. One-liners behave in a similar way (//) and multiline (/*) comments.

This is where almost the entire internal structure of the language ends. Everything else (including control constructs) is defined as words (either internal, such as arithmetic operations and defining new words; or defined in the "standard library" Fift.fif, which is in the folder crypto/fift in sources).

A simple example of a Fift program:

{ dup =: x dup * =: y } : setxy
3 setxy x . y . x y + .
7 setxy x . y . x y + .

The first line defines a new word setxy (note the prefix {, which creates a block before the closing } and prefix :, which actually defines the word). setxy takes a number from the top of the stack, defines (or redefines) it as a global constant x, and the square of this number as a constant y (given that the values ​​of constants can be redefined, I would rather call them variables, but I follow the naming in the language).

The next two lines push a number onto the stack, call setxy, then the values ​​of the constants are displayed x, y (the output uses the word .), both constants are pushed onto the stack, summed up, and the result is also displayed. As a result, we will see:

3 9 12 ok
7 49 56 ok

(The line "ok" is output by the interpreter when it finishes processing the current line in interactive input mode)

And here's a complete code example:

"Asm.fif" include

-1 constant wc  // create a wallet in workchain -1 (masterchain)

// Create new simple wallet
<{  SETCP0 DUP IFNOTRET INC 32 THROWIF  // return if recv_internal, fail unless recv_external
    512 INT LDSLICEX DUP 32 PLDU   // sign cs cnt
    c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS  // sign cs cnt cnt' pubk
    s1 s2 XCPU            // sign cs cnt pubk cnt' cnt
    EQUAL 33 THROWIFNOT   // ( seqno mismatch? )
    s2 PUSH HASHSU        // sign cs cnt pubk hash
    s0 s4 s4 XC2PU        // pubk cs cnt hash sign pubk
    CHKSIGNU              // pubk cs cnt ?
    34 THROWIFNOT         // signature mismatch
    ACCEPT
    SWAP 32 LDU NIP 
    DUP SREFS IF:<{
      8 LDU LDREF         // pubk cnt mode msg cs
      s0 s2 XCHG SENDRAWMSG  // pubk cnt cs ; ( message sent )
    }>
    ENDS
    INC NEWC 32 STU 256 STU ENDC c4 POPCTR
}>c
// code
<b 0 32 u, 
   newkeypair swap dup constant wallet_pk 
   "new-wallet.pk" B>file
   B, 
b> // data
// no libraries
<b b{00110} s, rot ref, swap ref, b>  // create StateInit
dup ."StateInit: " <s csr. cr
dup hash dup constant wallet_addr
."new wallet address = " wc . .": " dup x. cr
wc over 7 smca>$ type cr
256 u>B "new-wallet.addr" B>file
<b 0 32 u, b>
dup ."signing message: " <s csr. cr
dup hash wallet_pk ed25519_sign_uint rot
<b b{1000100} s, wc 8 i, wallet_addr 256 u, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
dup ."External message for initialization is " <s csr. cr
2 boc+>B dup Bx. cr
"new-wallet-query.boc" tuck B>file
."(Saved to file " type .")" cr

This scary looking file is for creating a smart contract - it will be placed in the file new-wallet-query.boc after execution. Please note that another assembly language for the TON Virtual Machine is used here (I will not dwell on it in detail), the instructions of which will be placed on the blockchain.

Thus, the assembler for TVM is written in Fift - the source code for this assembler is in the file crypto/fift/Asm.fif and are connected at the beginning of the above piece of code.

What can I say, apparently, Nikolai Durov just loves to create new programming languages ​​🙂

Creation of a smart contract and interaction with TON

So let's say we built the TON client and Fift interpreter as described above and got to know the language. How to create a smart contract now? This is described in the file. HOWTOattached to the source.

Accounts in TON

As I described in TON review, this network contains more than one blockchain - there is one common, so-called. “masterchain”, as well as an arbitrary number of additional “workchains”, identified by a 32-bit number. The masterchain has an identifier of -1, in addition to it, a “base” workchain with an identifier of 0 can also be used. Each workchain can have its own configuration. Internally, each workchain is divided into shardchains, but this is an implementation detail that is not necessary to keep in mind.

Within one workchain, many accounts are stored that have their own account_id identifiers. For masterchain and null workchain, they are 256 bits long. Thus, the account ID is written, for example, like this:

-1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

This is the raw format: first the workchain ID, then a colon, and the account ID in hexadecimal notation.

In addition, there is a shortened format - the workchain number and account address are encoded in binary form, a checksum is added to them, and all this is encoded in Base64:

Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb

Knowing this record format, we can request the current state of some account through the test client using the command

getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

We get an answer like this:

[ 3][t 2][1558746708.815218925][test-lite-client.cpp:631][!testnode]    requesting account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D
[ 3][t 2][1558746708.858564138][test-lite-client.cpp:652][!testnode]    got account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D with respect to blocks (-1,8000000000000000,72355):F566005749C1B97F18EDE013EBA7A054B9014961BC1AD91F475B9082919A2296:1BD5DE54333164025EE39D389ECE2E93DA2871DA616D488253953E52B50DC03F and (-1,8000000000000000,72355):F566005749C1B97F18EDE013EBA7A054B9014961BC1AD91F475B9082919A2296:1BD5DE54333164025EE39D389ECE2E93DA2871DA616D488253953E52B50DC03F
account state is (account
  addr:(addr_std
    anycast:nothing workchain_id:-1 address:x8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D)
  storage_stat:(storage_info
    used:(storage_used
      cells:(var_uint len:1 value:3)
      bits:(var_uint len:2 value:539)
      public_cells:(var_uint len:0 value:0)) last_paid:0
    due_payment:nothing)
  storage:(account_storage last_trans_lt:74208000003
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:7 value:999928362430000))
      other:(extra_currencies
        dict:hme_empty))
    state:(account_active
      (
        split_depth:nothing
        special:nothing
        code:(just
          value:(raw@^Cell 
            x{}
             x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
            ))
        data:(just
          value:(raw@^Cell 
            x{}
             x{0000000D}
            ))
        library:hme_empty))))
x{CFF8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D2068086C000000000000000451C90E00DC0E35B7DB5FB8C134_}
 x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
 x{0000000D}

We see the structure that is stored in the DHT of the specified workchain. For example, in the field storage.balance is the current balance of the account, in storage.state.code is the code of the smart contract, and in storage.state.data - its current data. Please note that the TON data storage - Cell, cells - is tree-like, each cell can have both its own data and child cells. This is shown as indentation on the last lines.

Building a smart contract

Now let's create such a structure ourselves (it's called BOC - bag of cells) using the Fift language. Fortunately, you don’t have to write a smart contract yourself - in the folder crypto/block from the source archive there is a file new-wallet.fif, which will help us create a new wallet. Copy it to the folder with the assembled client (~/liteclient-buildif you followed the instructions above). I cited its contents above as an example of code in Fift.

We execute this file as follows:

./crypto/fift -I"<source-directory>/crypto/fift" new-wallet.fif

Here <source-directory> must be replaced with the path to the unpacked sources (the “~” symbol cannot be used here, unfortunately, the full path is needed). Instead of using a key -I you can define an environment variable FIFTPATH and put this path into it.

Since we launched Fift with a filename new-wallet.fif, it will execute it and exit. If you omit the file name, you can play with the interpreter interactively.

After execution, something like this should appear in the console:

StateInit: x{34_}
 x{FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{0000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B}

new wallet address = -1 : 4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2 
0f9PzVILj8yglrVn1zS-NSjtxr7QBfaTCp7JrBqnFPIR8nhZ
signing message: x{00000000}

External message for initialization is x{89FEE120E20C7E953E31546F64C23CD654002C1AA919ADD24DB12DDF85C6F3B58AE41198A28AD8DAF3B9588E7A629252BA3DB88F030D00BC1016110B2073359EAC3C13823C53245B65D056F2C070B940CDA09789585935C7ABA4D2AD4BED139281CFA1200000001_}
 x{FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{0000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B}

B5EE9C724104030100000000D60002CF89FEE120E20C7E953E31546F64C23CD654002C1AA919ADD24DB12DDF85C6F3B58AE41198A28AD8DAF3B9588E7A629252BA3DB88F030D00BC1016110B2073359EAC3C13823C53245B65D056F2C070B940CDA09789585935C7ABA4D2AD4BED139281CFA1200000001001020084FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED5400480000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B6290698B
(Saved to file new-wallet-query.boc)

This means that the wallet with the ID -1:4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2 (or, which is the same, 0f9PzVILj8yglrVn1zS-NSjtxr7QBfaTCp7JrBqnFPIR8nhZ) was successfully created. The corresponding code will be in the file new-wallet-query.boc, his address is in new-wallet.addr, and the private key is in new-wallet.pk (be careful - running the script again will overwrite these files).

Of course, the TON network does not yet know about this wallet, it is stored only in the form of these files. Now it needs to be uploaded to the network. True, the problem is that to create a smart contract, you need to pay a commission, and your account balance is still zero.

In working mode, this problem will be solved by buying grams on the exchange (or by transferring from another wallet). Well, in the current test mode, a special smart contract has been launched, from which you can ask for up to 20 grams just like that.

Forming a request to someone else's smart contract

A request to a smart contract that distributes grams left and right, we do this. In the same folder crypto/block find file testgiver.fif:

// "testgiver.addr" file>B 256 B>u@ 
0x8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
dup constant wallet_addr ."Test giver address = " x. cr

0x4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2
constant dest_addr

-1 constant wc
0x00000011 constant seqno

1000000000 constant Gram
{ Gram swap */ } : Gram*/

6.666 Gram*/ constant amount

// b x --> b'  ( serializes a Gram amount )
{ -1 { 1+ 2dup 8 * ufits } until
  rot over 4 u, -rot 8 * u, } : Gram, 

// create a message (NB: 01b00.., b = bounce)
<b b{010000100} s, wc 8 i, dest_addr 256 u, amount Gram, 0 9 64 32 + + 1+ 1+ u, "GIFT" $, b>
<b seqno 32 u, 1 8 u, swap ref, b>
dup ."enveloping message: " <s csr. cr
<b b{1000100} s, wc 8 i, wallet_addr 256 u, 0 Gram, b{00} s,
   swap <s s, b>
dup ."resulting external message: " <s csr. cr
2 boc+>B dup Bx. cr
"wallet-query.boc" B>file

We will also save it to the folder with the assembled client, but we will correct the fifth line - before the line "constant dest_addr". Let's replace it with the address of the wallet that you created before (full, not abbreviated). "-1:" does not need to be written at the beginning, instead put "0x" at the beginning.

You can also change the line 6.666 Gram*/ constant amount is the amount in grams you are requesting (no more than 20). Even if you specify an integer, leave the decimal point.

Finally, we need to fix the line 0x00000011 constant seqno. The first number here is the current sequence number, which is stored in the account issuing grams. Where to get it? As mentioned above, start the client and run:

last
getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

At the very end, the smart contract data will contain

...
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
 x{0000000D}

The number 0000000D (you will have more) and there is a sequence number that must be substituted into testgiver.fif.

That's it, save the file and run (./crypto/fift testgiver.fif). The output will be a file wallet-query.boc. This is the formed message to someone else's smart contract - a request "transfer so many grams to such and such an account."

Using the client, upload it to the network:

> sendfile wallet-query.boc
[ 1][t 1][1558747399.456575155][test-lite-client.cpp:577][!testnode]    sending query from file wallet-query.boc
[ 3][t 2][1558747399.500236034][test-lite-client.cpp:587][!query]   external message status is 1

If now call last, and then again request the status of the account from which we asked for grams, then we should see that its sequence number has increased by one - this means that it sent money to our account.

The last step left is to upload the code of our wallet (its balance has already been replenished, but we won’t be able to manage it without the smart contract code). We carry out sendfile new-wallet-query.boc - and that's it, you have your own wallet in the TON network (albeit only a test one for now).

Create outbound transactions

To transfer money from the balance of the created account, there is a file crypto/block/wallet.fif, which also needs to be placed in the folder with the assembled client.

Similar to the previous steps, you need to correct the amount you are transferring, the recipient address (dest_addr), and your wallet seqno (it is equal to 1 after wallet initialization and increases by 1 after each outgoing transaction - you can see it by querying your account status) . For tests, you can use, for example, my wallet - 0x4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2.

On startup (./crypto/fift wallet.fif) the script will take the address of your wallet (where you transfer from) and its private key from the files new-wallet.addr и new-wallet.pk, and the received message will be written to new-wallet-query.boc.

As before, to directly execute the transaction, we call sendfile new-wallet-query.boc in the client. After that, do not forget to update the state of the blockchain (last) and check that the balance and seqno of our wallet have changed (getaccount <account_id>).

TON (Telegram Open Network) test client and new Fift language for smart contracts

That's all, now we can create smart contracts in TON and send requests to them. As you can see, the current functionality is already enough to, for example, make a friendlier wallet with a graphical interface (however, it is expected that it will become available as part of the messenger anyway).

Only registered users can participate in the survey. Sign in, you are welcome.

Are you interested in continuing articles with analysis of TON, TVM, Fift?

  • Yes, I'm waiting for the completion of a series of articles with a general overview of TON

  • Yes, it is interesting to read more about the Fift language

  • Yes, I want to learn more about TON Virtual Machine and assembler for it

  • No, I'm not interested in any of this.

39 users voted. 12 users abstained.

How do you feel about Telegram's plans to launch TON?

  • I have high hopes for this project.

  • I'm just following it with interest.

  • I am skeptical, I doubt its success

  • I tend to consider this initiative a failure, unnecessary for the broad masses

47 users voted. 12 users abstained.

Source: habr.com

Add a comment