Обработка сообщений
Резюме: В предыдущих шагах мы изменили взаимодействие нашего смарт-контракт с
хранилищем
,get-методы
, и изучили базовый поток разработки смарт-контрактов.
Теперь мы готовы перейти к основным функциям смарт-контрактов — отправке и получению сообщений. В TON сообщения используются не только для отправки валюты, но и в качестве механизма обмена данными между смарт-контрактами, что делает их ключевыми в разработке смарт-контрактов.
Если вы застряли на некоторых примерах, вы можете найти исходный шаблон проекта со всеми изменениями, внесёнными в этом руководстве, здесь.
Внутренние сообщения
Прежде чем приступить к реализации, давайте кратко опишем основные пути и шаблоны, которые мы можем использовать для обработки внутренних сообщений.
Акторы и роли
Поскольку TON реализует модель акторов, довольно естественно думать об отношениях смарт-контрактов в контексте ролей, определяющих, кому можно обращаться к определённой функциональности смарт-контракта. Самые распространённые примеры ролей:
кто угодно
: любой контракт без специально выделенной роли.владелец
: контракт, который обладает исключительным доступом к некоторым ключевым частям функциональности.
Давайте рассмотрим сигнатуру функции recv_internal
для понимания того, как мы можем использовать это:
- FunC
- Tolk
() recv_internal(int my_balance, int msg_value, cell in_msg_full, ломтик in_msg_body) нарушает
my_balance
- смарт-контрактный баланс при начале транзакции.msg_value
- средства, полученные с сообщен ием.in_msg_full
-ячейка
, включающая "заголовочные" поля сообщения.in_msg_body
- slice содержит тело сообщения.
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice)
myBalance
- баланс смарт-контракта при попрошайничестве транзакции.msgValue
- средства, полученные с сообщением.msgFull
-cell
с полями "header" сообщения.msgBody
- slice захват сообщения payload pf.
Вы можете найти полное описание отправки сообщений в этом разделе.
Из всего этого нам сейчас интересен исходный адрес сообщения, который мы можем извлечь из ячейки msg_full
. Получив этот адрес и сравнив его с хранимым, мы при выполнении требуемого условия можем разрешить доступ к важнейшим частям функциональности нашего смарт-контракта. Обычный подход выглядит следующим образом:
- FunC
- Tolk
;; Это не часть проекта, просто пример.
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
; Разобрать адрес отправителя из in_msg_full
см = in_msg_full. egin_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
}
Операции
Обычный шаблон контрактов в TON — включать 32-битный код операции в тела сообщений, который сообщает вашему контракту, какие действия необходимо выполнить:
- FunC
- Tolk
;; Это не часть проекта, просто пример!
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 {
; Шаг 1: Проверим, является ли сообщение пустым
if (in_msg_body. lice_empty?()) {
return; ;; Ничего не делаем с пустыми сообщениями
}
;; 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);
}
Объединяя оба этих шаблона, вы можете добиться полного описания систем ваших смарт-контрактов, обеспечив безопасное взаимодействие между ними и раскрыв полностью потенциал модели акторов в TON.
Внешние сообщения
Внешние сообщения
— это единственный способ обращения к логике смарт-контракта извне блокчейна. Обычно нет необходимости реализовывать его в смарт-контрактах, потому что в большинстве случаев не требуется, чтобы внешние точки входа были доступны кому-либо, кроме вас. Если вам этого достаточно, то стандартным способом будет делегировать эту ответственность отдельному актору — кошельку, для которого они в основном и были спроектированы.
В разработке внешних endpoints есть несколько стандартных подходов и мер безопасности, которые сейчас для вас могут быть излишне сложными. Поэтому в этом руководстве мы реализуем увеличение раннее добавленного числа ctx_counter_ext
.
Эта реализация небезопасна и может привести к потере средств на вашем контракте. Не разворачивайте такой контракт в мейннете
, особенно с крупной суммой средств на балансе.
Реализация
Давайте изменим наш смарт-контракт по стандартным шагам из раздела Обзор Blueprint, чтобы он мог получать внешние сообщения.
Шаг 1: редактирование кода смарт-контракта
Добавьте функцию recv_external
в смарт-контракт HelloWorld
:
- FunC
- Tolk
() recv_external(slice in_msg) impure {
accept_message();
var (ctx_id, ctx_counter, ctx_counter_ext) = load_data();
var query_id = in_msg~load_uint(64);
var addr = in_msg~load_msg_addr();
var coins = in_msg~load_coins();
var increase_by = in_msg~load_uint(32);
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(coins)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op::increase, 32)
.store_uint(query_id, 64)
.store_uint(increase_by, 32);
send_raw_message(msg.end_cell(), 0);
ctx_counter_ext += increase_by;
save_data(ctx_id, ctx_counter, ctx_counter_ext);
return ();
}
fun acceptExternalMessage(): void
asm "ACCEPT";
fun onExternalMessage(inMsg: slice) {
acceptExternalMessage();
var (ctxId, ctxCounter, ctxCounterExt) = loadData();
var queryId = inMsg.loadUint(64);
var addr = inMsg.loadAddress();
var coins = inMsg.loadCoins();
var increaseBy = inMsg.loadUint(32);
var msg = beginCell()
.storeUint(0x18, 6)
.storeSlice(addr)
.storeCoins(coins)
.storeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.storeUint(OP_INCREASE, 32)
.storeUint(queryId, 64)
.storeUint(increaseBy, 32);
sendRawMessage(msg.endCell(), 0);
ctxCounterExt += increaseBy;
saveData(ctxId, ctxCounter, ctxCounterExt);
return ();
}