FunC Adventure: Chapter 2
Current Overview
In the previous section, we introduced smart contracts on the TON network. We explored the purpose of messages, the role of the c4 register, and discussed testing methods. Now, let’s focus on elements that will enable you to build more sophisticated smart contracts.
Operations and Fees
As we advance in our exploration of the TON ecosystem, we’ll uncover important guidelines for smart contract interactions. Smart contracts within TON communicate with each other by sending internal messages. To streamline these interactions, certain recommendations have been established. A crucial aspect of these recommendations is the inclusion of query_id and op at the start of each message:
- op: Specifies the operation to be performed or the method to be invoked within the smart contract.
- query_id: Used in query-response messages to indicate that a response pertains to a specific query (when sending a reply).
Here’s an example of how it appears:
() 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) {
// Handle operation 1
;;
} else {
if (op == 2) {
// Handle operation 2
;;
} else {
// Handle unexpected operations or throw an exception
;;
}
}
}
The more you delve into the TON ecosystem, the more you discover about interacting with its environment. Your team’s account manager suggests he has insights into making your smart contracts more efficient.
The TON network operates using its own cryptocurrency, Toncoin. This digital currency underpins a marketplace for computational resources. Such a marketplace creates economic incentives for participants to validate transactions, fulfill requests, and contribute computing power to the network. Every participant initiating a transaction request must pay fees in Toncoin. Since smart contracts also utilize Toncoin, optimizing them to minimize fees becomes crucial.
In the TON network, transaction fees are composed of several components:
- Storage Fees: Charges for reserving space on the blockchain.
- Inbound Forwarding Fees: Costs associated with processing external messages that are imported into the system.
- Computation Fees: Fees for executing instructions within the TON Virtual Machine (TVM).
- Action Fees: Charges related to handling a list of actions, such as sending messages.
- Outbound Forwarding Fees: Costs for managing outgoing messages.
A straightforward method to lower fees is to employ the inline keyword for functions that are called infrequently, such as once or twice. This approach ensures that, during compilation, these functions are directly inserted at the call site.
The following is an example of how data loading can be handled when it is only required on a one-off basis.
(slice, slice) load_data () inline {
var ds = get_data().begin_parse();
return (ds~load_msg_addr(), ds~load_msg_addr());
}
Understanding Operations (Op)
Testing Operations
As a long-standing member of the TON ecosystem, you are aware that comprehensive testing is essential for all operations.
Testing Operations (Op) with Register c5
To evaluate how operations are executed, we need to determine what actions the smart contract has performed. For this, we utilize register c5.
Register c5 holds the output actions and is represented as a Cell. This register stores outgoing messages from smart contracts. During testing, we will send messages between addresses and examine the c5 register to ensure that all actions are correctly executed.
Please find below an example of the process for extracting a single message from register C5.
(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);
}
In this function:
- Parse Actions List: The function begins by parsing the actions cell into a slice object.
- Validate Structure: It checks the expected number of references and validates the presence of required fields.
- Extract Data: The function extracts and validates the action_type, msg_mode, and msg from the parsed data.
- Return Results: Finally, it returns the message mode and the message itself.
Lesson on Testing Operations
HashMaps
As smart contracts become more intricate, managing data efficiently, especially with respect to time, becomes crucial. This is where hashmaps, also known as dictionaries, come into play.
A hashmap is a data structure typically organized as a tree. It associates keys with values of any type, allowing for fast retrieval and modification. In FunC, hashmaps are implemented as cells.
The FunC standard library offers various functions to work with hashmaps efficiently. For instance, to add data to a hashmap, you can use the dict_set function. This function sets the value linked with a specific key index (of n-bit depth) in the dictionary to a slice and returns the updated dictionary.
Here’s an example of using dict_set:
dic~dict_set(256, key, in_msg_body);
Working with Hashmaps and Loops
To effectively manage hashmaps, loops are essential tools:
Loops are commonly employed when dealing with hashmaps. FunC provides three types of loops: repeat, until, and while.
Among these, the until loop is particularly useful for hashmaps. This is because many hashmap functions return a flag that makes it easy to determine when to end the loop.
Introduction to Hashmaps
Testing Hashmaps
When testing contracts that involve time-based logic, it’s crucial to utilize the c7 register. For effective testing, we need to create a helper function to manage time within the smart contract.
The c7 register holds the root of temporary data and is represented as a Tuple.
The helper function will enable us to set any required time value into this register. Here’s an example of how the helper function might be structured:
tuple get_c7_now(int now) inline method_id {
return unsafe_tuple([unsafe_tuple([
0x076ef1ea, // Magic number
0, // Actions
0, // Messages sent
now, // Unix time
1, // Block timestamp
1, // Transaction timestamp
239, // Random seed
unsafe_tuple([1000000000, null()]), // Remaining balance
null(), // Self reference
get_config() // Global configuration
])]);
}
Since the smart contract includes both value storage and retrieval logic, it’s necessary to test the data from prior tests. This isn’t supported directly by the standard FunC library, but toncli provides a solution: In the toncli test descriptions, the get_prev_c4 and get_prev_c5 functions are available to retrieve the c4 and c5 cells from earlier tests.
To simplify working with tuples from the stack, the standard library includes the following functions:
- first — Retrieves the first element of a tuple.
- second — Retrieves the second element of a tuple.
Lesson on Testing Hashmaps
Next Steps
Congratulations on making substantial progress in your journey. In the upcoming section, we will explore token standards and NFTs within the TON ecosystem.