Перейти к основному содержимому

Обработка сообщений

Резюме: В предыдущих шагах мы изменили взаимодействие нашего смарт-контракт с хранилищем, get-методы, и изучили базовый поток разработки смарт-контрактов.

Теперь мы готовы перейти к основным функциям смарт-контрактов — отправке и получению сообщений. В TON сообщения используются не только для отправки валюты, но и в качестве механизма обмена данными между смарт-контрактами, что делает их ключевыми в разработке смарт-контрактов.

подсказка

Если вы застряли на некоторых примерах, вы можете найти исходный шаблон проекта со всеми изменениями, внесёнными в этом руководстве, здесь.


Внутренние сообщения

Прежде чем приступить к реализации, давайте кратко опишем основные пути и шаблоны, которые мы можем использовать для обработки внутренних сообщений.

Акторы и роли

Поскольку TON реализует модель акторов, довольно естественно думать об отношениях смарт-контрактов в контексте ролей, определяющих, кому можно обращаться к определённой функциональности смарт-контракта. Самые распространённые примеры ролей:

  • кто угодно: любой контракт без специально выделенной роли.
  • владелец: контракт, который обладает исключительным доступом к некоторым ключевым частям функциональности.

Давайте рассмотрим сигнатуру функции recv_internal для понимания того, как мы можем использовать это:

() 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 содержит тело сообщения.
к сведению

Вы можете найти полное описание отправки сообщений в этом разделе.

Из всего этого нам сейчас интересен исходный адрес сообщения, который мы можем извлечь из ячейки msg_full. Получив этот адрес и сравнив его с хранимым, мы при выполнении требуемого условия можем разрешить доступ к важнейшим частям функциональности нашего смарт-контракта. Обычный подход выглядит следующим образом:

;; Это не часть проекта, просто пример.
() 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);
}

Операции

Обычный шаблон контрактов в TON — включать 32-битный код операции в тела сообщений, который сообщает вашему контракту, какие действия необходимо выполнить:

;; Это не часть проекта, просто пример!
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);
}

Объединяя оба этих шаблона, вы можете добиться полного описания систем ваших смарт-контрактов, обеспечив безопасное взаимодействие между ними и раскрыв полностью потенциал модели акторов в TON.

Внешние сообщения

Внешние сообщения — это единственный способ обращения к логике смарт-контракта извне блокчейна. Обычно нет необходимости реализовывать его в смарт-контрактах, потому что в большинстве случаев не требуется, чтобы внешние точки входа были доступны кому-либо, кроме вас. Если вам этого достаточно, то стандартным способом будет делегировать эту ответственность отдельному актору — кошельку, для которого они в основном и были спроектированы.

В разработке внешних endpoints есть несколько стандартных подходов и мер безопасности, которые сейчас для вас могут быть излишне сложными. Поэтому в этом руководстве мы реализуем увеличение раннее добавленного числа ctx_counter_ext.

осторожно

Эта реализация небезопасна и может привести к потере средств на вашем контракте. Не разворачивайте такой контракт в мейннете, особенно с крупной суммой средств на балансе.

Реализация

Давайте изменим наш смарт-контракт по стандартным шагам из раздела Обзор Blueprint, чтобы он мог получать внешние сообщения.

Шаг 1: редактирование кода смарт-контракта

Добавьте функцию recv_external в смарт-контракт HelloWorld:

/contracts/hello_world.fc
() 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 ();
}

Эта функция при получении внешнего сообщения увеличит нашу переменную ctx_counter_ext и отправит внутреннее сообщение на указанный адрес с операцией increase.

Проверьте, что код смарт-контракта верен, запустив:

npx blueprint build

Ожидаемый вывод должен выглядеть следующим образом:

✅ Compiled successfully! Cell BOC result:

{
"hash": "310e49288a12dbc3c0ff56113a3535184f76c9e931662ded159e4a25be1fa28b",
"hashBase64": "MQ5JKIoS28PA/1YROjU1GE92yekxZi3tFZ5KJb4foos=",
"hex": "b5ee9c7241010e0100d0000114ff00f4a413f4bcf2c80b01020120020d02014803080202ce0407020120050600651b088831c02456f8007434c0cc1caa42644c383c0040f4c7f4cfcc4060841fa1d93beea5f4c7cc28163c00b817c12103fcbc2000153b513434c7f4c7f4fff4600017402c8cb1fcb1fcbffc9ed548020120090a000dbe7657800b60940201580b0c000bb5473e002b70000db63ffe002606300072f2f800f00103d33ffa40fa00d31f30c8801801cb055003cf1601fa027001cb6a82107e8764ef01cb1f12cb3f5210cb1fc970fb0013a012f0020844ca0a"
}

✅ Wrote compilation artifact to build/HelloWorld.compiled.json

Шаг 2: обновить обёртку

Добавьте метод обёртки, который можно будет вызывать через наш класс обёртки для отправки внешних сообщений:

/wrappers/HelloWorld.ts
async sendExternalIncrease(
provider: ContractProvider,
opts: {
increaseBy: number;
value: bigint;
addr: Address;
queryID?: number;
}
) {
const message = beginCell()
.storeUint(opts.queryID ?? 0, 64)
.storeAddress(opts.addr)
.storeCoins(opts.value)
.storeUint(opts.increaseBy, 32)
.endCell()

return await provider.external(message);
}

Шаг 3: обновить тест

Обновите тест, чтобы убедиться, что контракт HelloWorld получил внешнее сообщение и обновил свои счётчики:

/tests/HelloWorld.spec.ts
//@version TypeScript 5.8.3
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { Cell, toNano} from '@ton/core';
import { HelloWorld } from '../wrappers/HelloWorld';
import '@ton/test-utils';
import { compile } from '@ton/blueprint';

describe('HelloWorld', () => {
let code: Cell;

beforeAll(async () => {
code = await compile('HelloWorld');
});

let blockchain: Blockchain;
let deployer: SandboxContract<TreasuryContract>;
let helloWorld: SandboxContract<HelloWorld>;

beforeEach(async () => {
blockchain = await Blockchain.create();

helloWorld = blockchain.openContract(
HelloWorld.createFromConfig(
{
id: 0,
ctxCounter: 0,
ctxCounterExt: 0n,
},
code
)
);

deployer = await blockchain.treasury('deployer');

const deployResult = await helloWorld.sendDeploy(deployer.getSender(), toNano('1.00'));

expect(deployResult.transactions).toHaveTransaction({
from: deployer.address,
to: helloWorld.address,
deploy: true,
success: true,
});
});

it('should receive external message and update counter', async () => {
const [__, counterExtBefore] = await helloWorld.getCounters()
const increase = 5;

const result = await helloWorld.sendExternalIncrease({
increaseBy: increase,
value: toNano(0.05),
addr: deployer.address, // Using deployer address
queryID: 0
});

expect(result.transactions).toHaveTransaction({
from: undefined, // External messages have no 'from' address
to: helloWorld.address,
success: true,
});

const [_, counterExt] = await helloWorld.getCounters()
expect(counterExtBefore + BigInt(increase)).toBe(counterExt);
});
});

Проверьте, что все примеры верны, запустив тестовый скрипт:

npx blueprint test

Ожидаемый вывод должен выглядеть примерно так:

 PASS  tests/HelloWorld.spec.ts
HelloWorld
✓ should receive external message and update counter (251 ms)

Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.841 s, estimated 2 s
Ran all test suites.

Следующий шаг

Теперь, когда вы понимаете, как смарт-контракты отправляют и получают сообщения, вы можете развернуть свой контракт в блокчейне TON.

Развёртывание в сети

Was this article useful?