In the wide application of blockchain technology, smart contracts are undoubtedly one of the most promising areas. Smart contracts allow us to execute trusted transactions and agreements in a decentralized environment. TON (The Open Network), as an emerging blockchain platform, has attracted the attention of many developers with its efficient and scalable features. This article will guide you through the basics of writing TON smart contracts by implementing a simple counter contract, teaching you the fundamental techniques of writing smart contracts using the FunC language.
Understanding TON and FunC
Before diving into writing smart contracts, we need to have an understanding of TON and FunC.
Introduction to TON
TON is a blockchain platform developed by the Telegram team, designed to address the scalability and speed issues in existing blockchain systems. TON’s features include:
- High Performance: TON can process millions of transactions, achieving fast confirmation.
- Scalability: TON supports unlimited scaling through sharding technology.
- User-Friendly: TON provides users with an easy-to-use blockchain ecosystem.
FunC Language
FunC is the primary programming language for TON smart contracts. It is a high-level, statically typed, imperative programming language. The design goal of FunC is to provide a concise, efficient, and easy-to-understand language, allowing developers to quickly get started with smart contract writing.
Writing the Counter Contract
Next, we will step by step create a simple counter contract. This contract will allow users to increase the counter’s value and provide methods to query the current value and ID of the counter.
Importing the Standard Library
#include "imports/stdlib.fc";
First, we need to import the standard library, which contains common functions and operations required for contract writing. This line of code is the foundation for writing contracts.
Defining Opcodes
const op::increase = "op::increase"c;
In TON smart contracts, we often need to define opcodes. By prefixing a string with c
, we can convert it into an opcode, which is used for message handling within the contract.
Defining Global Storage Variables
global int ctx_id;
global int ctx_counter;
Every smart contract has its own storage space for persisting data. Here we define two global variables ctx_id
and ctx_counter
, used to store the unique identifier and value of the counter, respectively.
Loading Data
() load_data() impure {
var ds = get_data().begin_parse();
ctx_id = ds~load_uint(32);
ctx_counter = ds~load_uint(32);
ds.end_parse();
}
To operate on the stored data within the contract, we need to load data from persistent storage into global variables. The load_data
function implements this functionality. It first retrieves the stored data, then parses the data, and assigns the parsed values to ctx_id
and ctx_counter
.
Storing Data
() save_data() impure {
set_data(
begin_cell()
.store_uint(ctx_id, 32)
.store_uint(ctx_counter, 32)
.end_cell()
);
}
In contrast to loading data, the save_data
function is used to store the values of global variables in persistent storage. It creates a new cell, stores the variable values in this cell, and then sets it as the contract’s data.
Receiving Internal Messages
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) {
return ();
}
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) {
return ();
}
load_data();
int op = in_msg_body~load_uint(32);
int query_id = in_msg_body~load_uint(64);
if (op == op::increase) {
int increase_by = in_msg_body~load_uint(32);
ctx_counter += increase_by;
save_data();
return ();
}
throw(0xffff);
}
The recv_internal
function is the main function of the contract, which is called when the contract receives a message from other contracts. It performs the following operations:
- Checks if the message body is empty and ignores it if so.
- Checks if the message is bounced and ignores it if so.
- Calls
load_data
to load the stored variables. - Reads the operation code and query ID from the message.
- If the operation code is
op::increase
, it reads the value to increase by, updates the counter, and callssave_data
to save the new counter value. - If the operation code is not known, it throws an exception.
Implementing Get Methods
int get_counter() method_id {
load_data();
return ctx_counter;
}
int get_id() method_id {
load_data();
return ctx_id;
}
Get methods serve as the external interface of the contract, allowing external callers to read contract data. Here, we provide two get methods: get_counter
and get_id
, used to obtain the value and unique identifier of the counter, respectively.
Details of Get Methods
Get methods play an important role in smart contracts as they allow external entities to access the contract’s state in a read-only manner. In TON, get methods are marked with the method_id
keyword, which means they cannot modify the contract’s state but can only return data.
get_counter
Method
int get_counter() method_id {
load_data();
return ctx_counter;
}
This method first calls load_data
to ensure we have the latest stored data. It then returns the current value of ctx_counter
. This method can be called by any external entity that knows the contract’s address to query the current value of the counter.
get_id
Method
int get_id() method_id {
load_data();
return ctx_id;
}
Similar to get_counter
, the get_id
method also loads the data first and then returns the value of ctx_id
. This method allows external entities to obtain the unique identifier of the contract.
Deploying and Interacting with the Contract
After writing the contract, the next step is to deploy it to the TON network. Deploying the contract involves the following steps:
- Compile the contract code: Use the compiler provided by TON to compile the FunC code into bytecode.
- Deploy the contract: Use a TON wallet or other deployment tool to deploy the compiled bytecode to the network.
- Interact with the contract: Use the API provided by TON or client libraries to send messages to the contract, execute the
op::increase
operation, or call the get methods.
Security and Best Practices
Security is of utmost importance when writing smart contracts. Here are some best practices for writing TON smart contracts:
- Validate inputs: Always validate input data from external sources to avoid potential security vulnerabilities.
- Simplify logic: Contract logic should be as simple as possible; complex logic is more prone to errors.
- Use get methods: For read-only operations, use get methods instead of sending messages to save resources.
- Error handling: Properly handle errors and exceptional cases to avoid the contract from stopping responding due to exceptions.
Conclusion
Through the steps above, we have successfully implemented a basic counter contract. Although this contract’s functionality is relatively simple, it covers the core concepts and techniques required for writing TON smart contracts. After mastering these basics, you can continue to explore more complex contract logic and build feature-rich decentralized applications.
In the world of TON, the application prospects of smart contracts are limitless. From simple counters to complex financial protocols, smart contracts can be applied in various scenarios. As the TON ecosystem continues to mature, there will be more tools and resources available for developers, making smart contract development more efficient and convenient.
Finally, we encourage you to continue learning and practicing, turning your ideas into reality. TON provides a platform full of opportunities, allowing you to innovate at the forefront of blockchain technology. Writing smart contracts is just the beginning; let’s build a more open, transparent, and decentralized future together.