FunC Journey: Part 2

Article image

Greetings reader, this is the second part of the journey through the TON ecosystem. In this part, we will continue to study different aspects of FunC.

Article image

Current state of affairs

In the previous part, we got acquainted with smart contracts in the TON network. We figured out why messages are needed, why the c4 register is needed, and also talked about tests. Now let's talk about things that will allow you to create complex smart contracts.

OP and fees

Article image

As we continue our journey through the TON ecosystem, we will learn that there are some rules that are recommended for smart contracts to interact with.

Smart contracts in TON interact with each other by sending the so-called internal messages. For the convenience of interaction between smart contracts, recommendations were developed. The key to these recommendations is adding the query_id and op to the beginning of the message:

  • op identifies the operation to be performed, or the method of the smart contract to be invoked.
  • query_id used in all query-response internal messages to indicate that a response is related to a query (in case we want to send response).

This is how it looks:

() recv_internal (int balance, int msg_value, cell in_msg_full, slice in_msg_body) {
    int op = in_msg_body~load_int(32);
    int query_id = in_msg_body~load_uint(64);
      if (op == 1) {
      } else {
        if (op == 2) {
        } else {
          ;; there might be an exception here 

Article image

The longer you travel through TON, the more you learn about interaction with the environment. The account manager on your team says he knows how to make your smart contracts more efficient.

The TON network has its own currency - Toncoin. Thanks to the Toncoin cryptocurrency, there is a market for computing. Such a marketplace provides an economic incentive for participants to validate and fulfill transactional requests and provide computing resources to the network. Any participant broadcasting a transaction request must pay fees in Toncoin. Smart contracts also use Toncoin, so you need to think about optimizing smart contracts in terms of fees.

In TON the transaction fee consists of:

  • storage_fees - fees for a "place" in the blockchain.
  • in_fwd_fees - fees for importing messages (this is the case when we process external messages).
  • computation_fees - fees for executing TVM instructions.
  • action_fees - fees associated with processing a list of actions (for example, sending messages).
  • out_fwd_fees - fees for importing outgoing messages.

One simple technique to reduce fees is to use the inline keyword for functions that are called once or twice, so that when compiled they will simply be substituted into the function call site.

This is how it looks, if you need to load data, for example, only once:

(slice, slice) load_data () inline {
  var ds = get_data().begin_parse();
  return (ds~load_msg_addr(), ds~load_msg_addr());

Lesson about op.

Testing Op

Being already an experienced TON traveler you know that everything has to be tested....

Article image

To test the work with op, we will need to understand what action the contract performed. Register c5 will help us with this.

c5 - Contains the output actions. This value is a Cell.

Outgoing messages from smart-contracts are written to register c5. Therefore, in the tests, we will send messages from address to address and parse the c5 register to make sure everything is correct.

Example of register c5 subtraction:

(int, cell) extract_single_message(cell actions) impure inline method_id {
    ;; ---------------- Parse actions list
    ;; prev:^(OutList n)
    ;; #0ec3c86d
    ;; mode:(## 8)
    ;; out_msg:^(MessageRelaxed Any)
    ;; = OutList (n + 1);
    slice cs = actions.begin_parse();
    throw_unless(1010, cs.slice_refs() == 2);

    cell prev_actions = cs~load_ref();
    throw_unless(1011, prev_actions.cell_empty?());

    int action_type = cs~load_uint(32);
    throw_unless(1013, action_type == 0x0ec3c86d);

    int msg_mode = cs~load_uint(8);
    throw_unless(1015, msg_mode == 64); 

    cell msg = cs~load_ref();
    throw_unless(1017, cs.slice_empty?());

    return (msg_mode, msg);

Lesson about testing op.


Article image

With the increase in the complexity of smart contracts, the question arises of how data can be stored and processed, for example, with respect to time. This opportunity is provided by hashmaps or as they are also called dictionaries.

Hashmap is a data structure represented by a tree. Hashmap - maps keys to values of arbitrary type, so that quick lookup and modification is possible. More details in clause 3.3. In FunC, hashmaps are represented by a cell.

For the convenience of working with hashmaps, the FunС standard library has many functions. For example, if you want to add data to hashmap, you can use dict_set , which sets the value associated with the key index key n bit depth in the dict dictionary to a slice and returns the resulting dictionary.

dic~udict_set(256, key, in_msg_body);

To work with hashmaps, we need loops:

Loops are often used to work with hashmaps. FunC has three loops: repeat,until,while.

The most convenient hashmap loop is until, since many functions for working with hashmaps return a flag by which it is convenient to check the loop termination condition.

Lesson about hashmaps.

Testing hashmaps

When we test contracts that use some kind of time logic, you need to use the c7 register. Therefore, in tests for a smart contract, we will need to write a helper function that will allow us to work with time.

Article image

c7 — Contains the root of temporary data. It is a Tuple.

The helper function will allow us to put any time we need into the register.The helper function would look like this:

tuple get_c7_now(int now) inline method_id {
    return unsafe_tuple([unsafe_tuple([
        0x076ef1ea,           ;; magic
        0,                    ;; actions
        0,                    ;; msgs_sent
        now,                  ;; unixtime
        1,                    ;; block_lt
        1,                    ;; trans_lt
        239,                  ;; randseed
        unsafe_tuple([1000000000, null()]),  ;; balance_remaining
        null(),               ;; myself
        get_config()          ;; global_config

Since the smart contract has the logic of storing values as well as receiving, we will have to test the data received from previous tests, this cannot be done using the standard FunC library, BUT in toncli this moment is thought out: In description of toncli tests, there are get_prev_c4 / get_prev_c5 functions that allow you to get c4/c5 cells from previous tests.

To make it convenient for us to work with the tuple from the stack, the standard library has functions:

  • first - returns the first element of a tuple.
  • second - returns the second element of a tuple.

Lesson about testing hashmaps.

Further path

Congratulations, we have made significant progress on our journey. In the next part, we will talk about token standards and NFTs in TON.

Other parts: