ProHoster > Blog > Administration > About how to write and publish a smart contract in the Telegram Open Network (TON)
About how to write and publish a smart contract in the Telegram Open Network (TON)
About how to write and publish a smart contract in TON
What is this article about?
In the article, I will talk about how I participated in the first (of two) Telegram blockchain competition, did not take a prize, and decided to record the experience in the article so that it does not sink into oblivion and, perhaps, helps someone.
Since I didnβt want to write abstract code, but to do something working, for the article I wrote an instant lottery smart contract and a website that shows smart contract data directly from TON without using intermediate storage.
The article will be useful for those who want to make their first smart contract in TON, but do not know where to start.
Using the example of a lottery, I will go from setting up the environment to publishing a smart contract, interacting with it, and writing a site for receiving and publishing data.
About participation in the competition
Last October, Telegram announced a blockchain competition with new languages Fift ΠΈ FunC. It was necessary to choose to write any of the five proposed smart contracts. I figured it would be nice to do something out of the ordinary, learn a language and do something, even if I don't have to write anything else in the future. Plus, the topic is constantly on hearing.
It is worth saying that I had no experience in developing smart contracts.
I planned to participate until the very end, as long as it turns out and then write a review article, but I immediately failed at the first one. I wrote wallet with multi-signature on FunC and he generally worked. Took as a basis smart contract on Solidity.
At that time, I thought that this was definitely enough to take at least some prize-winning place. As a result, about 40 out of 60 participants became winners and I was not among them. In general, this is nothing terrible, but one thing strained me. At the time of the announcement of the results, a review with a test for my contract had not been done, I asked the participants in the chat if there were anyone else who did not have it, there were none.
Apparently, having paid attention to my messages, two days later the judges published a comment and I still did not understand, they accidentally missed my smart contract during the judging, or simply considered that it was so bad that it did not need comment. I asked a question on the page, but did not receive a response. Although who judged is not a secret, I considered it superfluous to write personal messages.
A lot of time was spent on understanding, so it was decided to write an article. Since there is not much information yet, the article will help save time for everyone interested.
The concept of smart contracts in TON
Before you write something, you need to figure out which side to approach this thing at all. Therefore, now I will tell you what parts the system consists of. More precisely, what parts do you need to know in order to write at least some kind of working contract.
We will focus on writing a smart contract and working with TON Virtual Machine (TVM), Fift ΠΈ FunC, so the article is more like a description of the development of a conventional program. We will not dwell on how the platform itself works here.
Generally about how it works TVM and language Fift there is good official documentation. During my participation in the competition and now during the writing of the current contract, I often turned to her.
The main language in which smart contracts are written is FunC. There is no documentation on it at the moment, so in order to write something, you need to study examples of smart contracts from the official repository and the language implementation itself there, plus you can watch examples of smart contracts for the past two contests. Links at the end of the article.
Suppose we have already written a smart contract for FunC, after that we compile the code into Fift assembler.
The compiled smart contract remains to be published. To do this, you need to write a function for Fift, which will take the smart contract code and some other parameters as input, and the output will be a file with the extension .boc (which means "bag of cells"), and, depending on how we write it, a private key and an address that is generated based on the smart contract code. Grams can already be sent to a smart contract address that has not yet been published.
To publish a smart contract in TON received .boc the file will need to be sent to the blockchain using a light client (more on that below). But before publishing, you need to transfer grams to the generated address, otherwise the smart contract will not be published. After publication, it will be possible to interact with a smart contract by sending messages to it from outside (for example, using a light client) or from within (for example, one smart contract sends a message to another inside TON).
After we understand how the code is published, it becomes easier further. We roughly know what we want to write and how our program will work. And while writing, we are looking for how it is already implemented in existing smart contracts, or we look into the implementation code Fift ΠΈ FunC in the official repository, or look in the official documentation.
Very often I searched for keywords in the Telegram chat where all the participants of the contest and Telegram employees, including, gathered, it so happened that during the contest everyone gathered there and started discussing Fift and FunC. Link at the end of the article.
It's time to move from theory to practice.
Preparing the environment for working with TON
Everything that will be described in the article I did on MacOS and double-checked in a clean Ubuntu 18.04 LTS on Docker.
The first thing to do is download and install lite-client with which you can send requests to TON.
The instructions on the official website describe the installation process in some detail and clearly and omit some details. Here we follow the instructions along the way, installing the missing dependencies. I did not compile each project myself and installed from the official Ubuntu repository (on MacOS I used brew).
Once all dependencies are installed, you can install lite-client, Fift, FunC.
First, we clone the TON repository along with the dependencies. For convenience, we will do everything in a folder ~/TON.
cd ~/TON
git clone https://github.com/ton-blockchain/ton.git
cd ./ton
git submodule update --init --recursive
The repository also stores implementations Fift ΠΈ FunC.
Now we are ready to build the project. Repository code cloned into folder ~/TON/ton. In ~/TON create a folder build and collect the project in it.
mkdir ~/TON/build
cd ~/TON/build
cmake ../ton
Since we are going to write a smart contract, we need not only lite-clientbut Fift Ρ FunC, so we compile everything. Not a fast process so we are waiting.
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json
If the build was successful, then after launch you will see the light client connection log to the node.
[ 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)
...
You can run the command help and see what commands are available.
help
Let's list the commands that we will use in this article.
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
As I wrote above, the smart contract that we are writing is a lottery.
Moreover, this is not a lottery in which you need to buy a ticket and wait an hour, a day or a month, but an instant one in which the user transfers to the address of the contract N grams, and instantly gets back 2 * N grams or loses. We will make the probability of winning about 40%. If there are not enough grams for payment, then we will consider the transaction as a replenishment.
Moreover, it is important that the bets can be seen in real time and in a convenient way, so that the user can immediately understand whether he won or lost. Therefore, you need to make a website that will show the rates and the result directly from TON.
Writing a smart contract
For convenience, I made code highlighting for FunC, the plugin can be found and installed in the Visual Studio Code search, if you suddenly want to add something, then I posted the plugin in the public domain. Also, someone previously made a plugin for working with Fift, you can also install it and find it in VSC.
Immediately create a repository where we will commit intermediate results.
To make our life easier, we will write a smart contract and test it locally until it is ready. Only after that we will publish it in TON.
The smart contract has two external methods that can be accessed. First, recv_external() this function is executed when a request to the contract comes from the outside world, that is, not from TON, for example, when we ourselves form a message and send it via lite-client. Second, recv_internal() this is when, inside the TON itself, any contract refers to ours. In both cases, you can pass parameters to the function.
Let's start with a simple example that will work if published, but does not contain any functional load.
Here it is necessary to explain what slice. All stored data in TON Blockchain is a collection TVM cell or simply cell, such a cell can store up to 1023 bits of data and up to 4 references to other cells.
TVM cell slice or slice is part of the existing cell is used for its parsing, it will be clear further. The main thing for us is that we can transfer to a smart contract slice and, depending on the type of message, process the data in recv_external() or recv_internal().
impure β a keyword that indicates that the function changes the smart contract data.
Save the contract code in lottery-code.fc and compile.
The value of the flags can be viewed using the command
~/TON/build/crypto/func -help
We have compiled Fift assembler code in 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
It can be run locally, for this we will prepare the environment.
Note that the first line connects Asm.fif, this is code written in Fift for Fift assembler.
Since we want to run and test the smart contract, we will create a file locally lottery-test-suite.fif and copy the compiled code there, replacing the last line in it, which writes the smart contract code into a constant codeto then pass it to the virtual machine:
"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
While it seems clear, now let's add the code that we will use to launch TVM to the same file.
Π c7 we write the context, that is, the data with which the TVM (or network state) will be launched. Even during the competition, one of the developers showed how to create c7 and I copied. In this article, we may need to change rand_seed since the generation of a random number depends on it and does not change, the same number will be returned each time.
recv_internal ΠΈ recv_external constants with a value of 0 and -1 will be responsible for calling the appropriate functions in the smart contract.
We are now ready to create the first test for our empty smart contract. For clarity, for now, we will add all the tests to the same file. lottery-test-suite.fif.
Let's create a variable storage and write an empty cell, this will be the storage of the smart contract.
message this is the message that we will send to the smart contact from the outside. Let's make it empty for now.
Great, we have written the first working version of the smart contract.
Now we need to add functionality. Let us first deal with the messages that come from the outside world to recv_external()
The developer himself chooses the message format that the contract can accept.
But usually
First, we want to protect our contract from the outside world and make it so that only the owner of the contract can send external messages to it.
secondly, when we send a valid message to TON, we want it to happen exactly once and when the same message is sent again, the smart contract rejects it.
Therefore, in almost every contract, these two problems are solved, since our contract accepts external messages, we also need to take care of this.
We'll do it in reverse order. First, we solve the problem with repetition, if the contract has already received such a message and processed it, then it will not execute it a second time. And then we will solve the problem so that only a certain circle of people can send messages to the smart contract.
There are different ways to solve the problem with repeated messages. We'll do it this way. In the smart contract, we initialize the counter of received messages with an initial value of 0. In each message to the smart contract, we will add the current value of the counter. If the value of the counter in the message does not match the value in the smart contract, then we do not process it, if it does, then we process and increase the counter in the smart contract by 1.
We return to lottery-test-suite.fif and add the second test to it. Let's send an invalid number, the code should throw an exception. For example, let's say 166 is stored in the contract data, and we will send 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"
Let's run.
~/TON/build/crypto/fift -s lottery-test-suite.fif
And we will see that the test is executed with an error.
[ 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
At this stage lottery-test-suite.fif should look like here to register:.
Now let's add the counter logic to the smart contract in 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 lies the message we're sending.
The first thing we do is check if there is data in the message, if not, then we just exit.
Next, we parse the message. in_msg~load_uint(32) loads number 165, 32-bit unsigned int from the transmitted message.
Next, we load 32 bits from the smart contract storage. We check that the loaded number matches the passed one, if not, we throw an exception. In our case, since we're passing in a mismatch, an exception should be thrown.
Copy the resulting code to lottery-test-suite.fif, not forgetting to replace the last line.
Checking that the test passes:
~/TON/build/crypto/fift -s lottery-test-suite.fif
Here you can see the corresponding commit with current results.
Note that it is inconvenient to constantly copy the compiled smart contract code to the test file, so we will write a script that will write the code to a constant for us, and we will simply connect the compiled code to our tests using "include".
Create a file in the project folder build.sh with the following content.
Now, it is enough to run our script to compile the contract. But besides that, we need to write it into a constant code. So we will create a new file lotter-compiled-for-test.fif, which we will include in the file lottery-test-suite.fif.
Let's add code to sh script that will simply duplicate the compiled file in lotter-compiled-for-test.fif and change the last line in it.
# 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
Now, to check, run the resulting script and we will generate a file lottery-compiled-for-test.fifwhich we will include in our lottery-test-suite.fif
Π lottery-test-suite.fif remove the contract code and add the line "lottery-compiled-for-test.fif" include.
Run tests to see if they pass.
~/TON/build/crypto/fift -s lottery-test-suite.fif
Great, now to automate the launch of the tests, let's create a file test.sh, which will first execute build.shand then run tests.
We make the test.sh and run to make sure the tests work.
chmod +x ./test.sh
./test.sh
We check that the contract is compiled and the tests are executed.
Great, now on startup test.sh the tests will be compiled and run immediately. Here is a link to commit.
Okay, before we continue let's do one more thing for convenience.
Let's create a folder build where we will store the copied contract and its clone written into a constant lottery-compiled.fif, lottery-compiled-for-test.fif. We will also create a folder test where will the test file be stored lottery-test-suite.fif and potentially other support files. Link to related changes.
Let's continue developing the smart contract.
Next should be a test that checks that the message is received and the counter is updated in the store when we send the correct number. But we will do that later.
Now let's think about what data structure and what data should be stored in a smart contract.
The next step is to write two functions. Let's call the first pack_state(), which will pack the data for subsequent storage in the smart contract storage. The second one, we'll call unpack_state() will read and return data from storage.
_ 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;
}
We add these two functions at the beginning of the smart contract. It turns out this intermediate result.
To save the data, you will need to call the built-in function set_data() and it will write data from pack_state() in the smart contract storage.
Now that we have convenient functions for writing and reading data, we can move on.
We need to check that the incoming message is signed by the owner of the contract (well, or another user who has access to the private key).
When we publish a smart contract, we can initialize it with the data we need in the store, which will be saved for future use. We will write the public key there so that we can verify that the signature of the incoming message was made by the corresponding private key.
Before proceeding, let's create a private key and write it to test/keys/owner.pk. To do this, start Fift in interactive mode and run four commands.
Let's create a folder keys inside a folder test and write the private key there.
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
We see the file in the current folder owner.pk.
We remove the public key from the stack, when we need it we can get it from the private one.
Now we need to write the signature verification. Let's start with a test. First, we read the private key from the file using the function file>B and write it to a variable owner_private_key, then using the function priv>pub convert the private key to public and write the result to owner_public_key.
As a result, the message that we will send to the smart contract is written to the variable message_to_send, about functions hashu, ed25519_sign_uint you can read in the Fift documentation.
Like this the test file should look like at this stage.
Let's run the test and it will fail, so we will change the smart contract so that it can receive messages of this format and verify the signature.
First, we read 512 bits of the signature from the message and write it to a variable, then we read 32 bits of the counter variable.
Since we have a function for reading data from the smart contract storage, we will use it.
Further check of the counter transferred with storage and verification of the signature. If something does not match, then we throw an exception with the corresponding code.
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));
Let's run the tests and see that the second test fails. For two reasons, the lack of bits in the message and the lack of bits in the storage, so the code crashes when parsing. We need to add the signature of the message we are sending and copy the storage from the last test.
In the second test, we will add a message signature and change the storage of the smart contract. Like this looks like a file with tests at the moment.
Let's write the fourth test, in which we will send a message signed by someone else's private key. Let's create another private key and save it to a file not-owner.pk. Let's sign the message with this private key. Let's run the tests and make sure all the tests pass. commit at this moment.
Now we can finally move on to implementing the smart contract logic.
Π recv_external() we will receive two types of messages.
Since our contract will accumulate the losses of the players, this money must be transferred to the creator of the lottery. The wallet address of the lottery creator is written to the vault when the contract is created.
Just in case, we need the ability to change the address to which to send the gram of the losers. We should also be able to send grams from the lottery to the address of the owner.
Let's start with the first one. Let's first write a test that will check that after sending the message, the smart contract saved the new address in the storage. Note that in the message, in addition to the counter and the new address, we also send action 7-bit non-negative integer, depending on it, we will choose how to process the message in the smart contract.
<b 0 32 u, 1 @ 7 u, new_owner_wc @ 32 i, new_owner_account_id @ 256 u, b> message_to_sign !
In the test, you can see how the de-realization of the smart contract storage takes place storage in Fift. Variable deserialization is described in the Fift documentation.
Let's run the test and see if it fails. Now let's add logic to change the address of the lottery owner.
In the smart contract, we continue to parse message, read in action. Recall that we have two action: change address and send grams.
Then we read the new address of the contract owner and save it to the storage.
We run the tests and see that the third test fails. Crashes due to the fact that the contract now additionally parses 7 bits from the message, which are missing in the test. Let's add a non-existent message to the message action. Let's run the tests and see that everything passes. Here commit for changes. Great.
Now let's write the logic for sending the specified number of grams to the previously saved address.
Let's write a test first. We will write two tests, one when the balance is not enough, the second when everything should pass successfully. Tests can be viewed in this commit.
Now let's add some code. First, let's write two helper methods. The first get method is to find out the current balance of the smart contract.
int balance() inline_ref method_id {
return get_balance().pair_first();
}
And the second one is for sending grams to another smart contract. I completely copied this method from another smart contract.
() 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
}
Let's add these two methods to the smart contract and write the logic. First, we parse the number of grams from the message. Then we check the balance, if not enough we throw an exception. If everything is fine, then we send grams to the saved address and update the counter.
Like this looks like a smart contract at the moment. Let's run the tests and make sure they pass.
By the way, a commission is debited for a processed message from a smart contract every time. In order for the smart contract messages to execute the request, after basic checks, you need to call accept_message().
Now let's deal with internal messages. In fact, we will only accept grams and send back a double amount to the player if he wins and a third to the owner if he loses.
Let's write a simple test first. To do this, we need a test address of the smart contract from which we send grams to the smart contract.
The smart contract address consists of two numbers, a 32-bit integer responsible for the workchain and a 256-bit non-negative integer unique account number in this workchain. For example, -1 and 12345, this address will be saved to a file.
I copied the address saving function from 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
Let's take a look at how the function works, this will give an understanding of how Fift works. Launch Fift in interactive mode.
~/TON/build/crypto/fift -i
First we push -1, 12345 and the name of the future file "sender.addr" onto the stack:
-1 12345 "sender.addr"
The next step is to execute the function -rot, which shifts the stack so that the unique number of the smart contract is at the top of the stack:
"sender.addr" -1 12345
256 u>B converts a 256-bit non-negative integer to bytes.
And finally, the bytes are written to the file B>file. After that, our stack is empty. Stopping Fift. File created in current folder sender.addr. Move the file to the created folder test/addresses/.
Let's write a simple test that will send grams to a smart contract. Here is the commit.
Now let's deal with the logic of the lottery.
The first thing we do is check the message bounced or not if bounced, then we ignore it. bounced means that the contract will return grams if some error occurs. We will not return grams if an error occurs, we will not.
We check if the balance is less than half a gram, then we simply accept the message and ignore it.
Next, we parse the address of the smart contract from which the message came.
We read the data from the storage and then delete the old bets from the history if there are more than twenty of them. For convenience, I wrote three additional functions pack_order(), unpack_order(), remove_old_orders().
Then we look if the balance is not enough for the payout, then we consider that this is not a bet, but a replenishment and save the replenishment in orders.
Then finally the essence of the smart contract.
First, if the player has lost, we save it to the history of bets and if the amount is more than 3 grams, we send 1/3 to the owner of the smart contract.
If the player won, then we send a double amount to the player's address and then save the information about the bet in the history.
() 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));
}
Now it remains simple, let's make get methods so that we can get information about the state of the contract from the outside world (in fact, read the data from the smart contract storage).
Add Get Methods. We will write about how to receive information about a smart contract below.
I also forgot to add the code that will process the very first request that occurs when the smart contract is published. Relevant commit. And further corrected bug with sending 1/3 of the amount to the owner's account.
The next step is to publish the smart contract. Let's create a folder requests.
What is worth paying attention to. We form the storage of the smart contract and the entry message. After this, the address of the smart contract is generated, that is, the address is known even before publication in TON. Then you need to send a few grams to this address, and only after that you need to send a file with the smart contract itself, since the network takes a commission for storing the smart contract and operations in it (validators that store and execute smart contracts). The code can be viewed here.
Next, we execute the publication code and get lottery-query.boc file and smart contract address.
And we will see that the account with this address is empty.
account state is empty
We send to the address 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 Gram and after a few seconds we execute the same command. To send grams I use official wallet, and you can ask for test grams from someone in the chat, which I will talk about at the end of the article.
Now let's create requests to interact with the smart contract.
More precisely, we will leave the first one for changing the address as an independent work, and we will do the second one for sending grams to the ownerβs address. In fact, we will need to do the same as in the test for sending grams.
This is the message we will send to the smart contract, where msg_seqno 165, action 2 and 9.5 grams to send.
<b 165 32 u, 2 7 u, 9500000000 Gram, b>
Don't forget to sign the message with your private key lottery.pk, which was generated earlier when creating a smart contract. Here is the relevant commit.
Getting information from a smart contract using get methods
Now let's look at how to run smart contract get methods.
We will use lite-client and get methods to display information about the smart contract on the site.
Display smart contract data on the website
I have written a simple Python website to show smart contract data in a convenient way. Here I will not dwell on it in detail and publish the site in one commit.
Requests to TON are made from Python through lite-client. For convenience, the site is packaged in Docker and published on Google Cloud. Link.
Trying
Now let's try to send grams there for replenishment from purse. We will send 40 grams. And let's make a couple of bets for clarity. We see that the site shows the history of bets, the current winning percentage and other useful information.
The article turned out to be much longer than I expected, maybe it could have been shorter, or maybe just for a person who knows nothing about TON and wants to write and publish a smart contract that is not the easiest to interact with. Perhaps some things could be explained more simply.
Perhaps some points in the implementation could have been done more efficiently and elegantly, but then the preparation of the article would have taken even more time. It is also possible that I made a mistake somewhere or did not understand something, so if you are doing something serious, you need to rely on the official documentation or the official repository with the TON code.
It should be noted that since TON itself is still in the active development stage, there may be changes that will break any of the steps in this article (which happened while I was writing, I have already corrected it), but the general approach is unlikely to change.
I will not talk about the future of TON. Perhaps the platform will become something big and we should take the time to study it and carve a niche with our products now.
There is also Libra from Facebook, which has a larger potential audience of users than TON. I know almost nothing about Libra, judging by the activity forum there is much more activity than in the TON community. Although the developers and the TON community are more like an underground, which is also cool.
Chat about TON in Telegram, which helped a lot to figure it out at the initial stage. I think it will not be a mistake if I say that there is everyone who wrote something for TON. You can also ask for test grams there. https://t.me/tondev_ru