Processing messages
Summary: In previous steps, we modified our smart contract interaction with
storage
,get methods
, and learned the basic smart contract development flow.
Now, we are ready to move on to the main functionality of smart contracts — sending and receiving messages. In TON, messages are used not only for sending currency but also as a data-exchange mechanism between smart contracts, making them crucial for smart contract development.
If you are stuck on some of the examples, you can find the original template project with all modifications performed during this guide here.
Internal messages
Before we proceed to implementation, let's briefly describe the main ways and patterns that we can use to process internal messages.
Actors and roles
Since TON implements the actor model, it's natural to think about smart contract relations in terms of roles
, determining who can access smart contract functionality or not. The most common examples of roles are:
anyone
: any contract that doesn't have a distinct role.owner
: a contract that has exclusive access to some crucial parts of the functionality.
Let's examine the recv_internal
function signature to understand how we could use that:
- FunC
- Tolk
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure
my_balance
- smart-contract balance at the beginning of the transaction.msg_value
- funds received with message.in_msg_full
-cell
containing "header" fields of the message.in_msg_body
- slice containing payload of the message.
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice)
myBalance
- balance of smart contract at the beginning of the transaction.msgValue
- funds received with message.msgFull
-cell
containing "header" fields of message.msgBody
- slice containing payload pf the message.
You can find a comprehensive description of sending messages in this section.
What we are specifically interested in is the source address of the message, which we can extract from the msg_full
cell. By obtaining that address and comparing it to a stored one — we can conditionally allow access to crucial parts of our smart contract functionality. A common approach looks like this:
- FunC
- Tolk
;; This is NOT a part of the project, just an example.
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
;; Parse the sender address from in_msg_full
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();
;; check if message was send by owner
if (equal_slices_bits(sender_address, owner_address)) {
;;owner operations
return
} else if (equal_slices_bits(sender_address, other_role_address)){
;;other role operations
return
} else {
;;anyone else operations
return
}
;;no known operation were obtained for presented role
;;0xffff is not standard exit code, but is standard practice among TON developers
throw(0xffff);
}
// This is NOT a part of the project, just an example.
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) {
// Parse the sender address from in_msg_full
var cs: slice = msgFull.beginParse();
val flags = cs.loadMessageFlags();
var sender_address = cs~load_msg_address();
if (isSliceBitsEqual(sender_address, owner_address)) {
// owner operations
return
} else if (isSliceBitsEqual(sender_address, other_role_address)){
// other role operations
return
} else {
// anyone else operations
return
}
throw 0xffff; // if the message contains an op that is not known to this contract, we throw
}
Operations
A common pattern in TON contracts is to include a 32-bit operation code in message bodies, which tells your contract what action to perform:
- FunC
- Tolk
;; This is NOT a part of the project, just an example!
const int op::increment = 1;
const int op::decrement = 2;
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
;; Step 1: Check if the message is empty
if (in_msg_body.slice_empty?()) {
return; ;; Nothing to do with empty messages
}
;; Step 2: Extract the operation code
int op = in_msg_body~load_uint(32);
;; Step 3-7: Handle the requested operation
if (op == op::increment) {
increment(); ;;call to specific operation handler
return;
} else if (op == op::decrement) {
decrement();
;; Just accept the money
return;
}
;; Unknown operation
throw(0xffff);
}
//This is NOT a part of the project, just an example!
const op::increment : int = 1;
const op::decrement : int = 2;
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) {
// Step 1: Check if the message is empty
if (slice.isEndOfSlice()) {
return; // Nothing to do with empty messages
}
// Step 2: Extract the operation code
var op = in_msg_body~load_uint(32);
// Step 3-7: Handle the requested operation
if (op == op::increment) {
increment(); //call to specific operation handler
return;
} else if (op == op::decrement) {
decrement();
// Just accept the money
return;
}
// Unknown operation
throw(0xffff);
}