Синтаксис Tolk
Синтаксис Tolk спроектирован так, чтобы быть знакомым разработчикам из мира TypeScript, Swift и Kotlin, при этом сохраняя полный доступ к возможностям TVM. Знание синтаксических конструкций Tolk позволяет быстро писать и читать контракты среднего уровня, не погружаясь в низкоуровневые особенности FunC. Это особенно важно для проектов, которым нужен более тонкий контроль над газом, чем предоставляет Tact, но без сложности FunC.
В этом уроке мы разберём синтаксис Tolk во всех деталях и напишем полноценный смарт-контракт. Tolk сочетает знакомый синтаксис с полным доступом к возможностям TVM.
Объявление функций
Tolk предлагает несколько видов функций:
// Обычная функция
fun add(a: int, b: int): int {
return a + b;
}
// Get-метод (доступен для внешних вызовов)
get fun counter(): int {
return getContractData().beginParse().loadUint(32);
}
// Asm-функция (встраивание TVM-инструкций)
asm fun myCustomOp(x: int, y: int): int {
ADD
}
Точка входа контракта
Главная функция контракта — onInternalMessage:
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) {
// Обработка входящего сообщения
}
Для внешних сообщений:
fun onExternalMessage(inMsg: slice) {
// Обработка внешнего сообщения
acceptMessage();
}
Переменные и типы
val и var
val x = 42; // Неизменяемая, тип int выведен автоматически
var counter = 0; // Изменяемая
counter += 1; // OK
val name: slice = cs.loadBits(256); // Явное указание типа
Система типов
| Тип | Описание | Пример |
|---|---|---|
int | 257-битное целое число | val x: int = 42; |
bool | Логическое значение | val flag: bool = true; |
cell | Ячейка TVM | val c: cell = beginCell().endCell(); |
slice | Указатель чтения | val s: slice = c.beginParse(); |
builder | Конструктор ячейки | val b: builder = beginCell(); |
tuple | Упорядоченная коллекция | val t: tuple = createEmptyTuple(); |
void | Отсутствие значения | Для функций без возврата |
// Вывод типов -- компилятор определяет тип автоматически
val balance = 1000000000; // int
val data = getContractData(); // cell
val cs = data.beginParse(); // slice
val flag = true; // bool
Управление потоком
Условия
if (balance > minBalance) {
sendCoins(recipient, amount);
} else {
throw 101; // Недостаточно средств
}
Циклы
// while
var i = 0;
while (i < 10) {
// ...
i += 1;
}
// do-while
var attempts = 0;
do {
attempts += 1;
// Попытка операции...
} while (!success && attempts < 3);
// repeat -- фиксированное количество итераций
repeat (64) {
// Выполнится ровно 64 раза
}
Обработка ошибок
// throw -- бросить исключение с кодом
throw 401; // Безусловно
// assert -- проверка условия
assert(senderAddr == ownerAddr, 401); // Если false, throw 401
// try-catch -- перехват исключений
try {
val result = riskyOperation();
} catch (exitCode) {
// exitCode -- числовой код ошибки TVM
handleError(exitCode);
}
Операции с Cell
Tolk использует точечный синтаксис для всех операций с ячейками:
Создание ячейки (Builder)
val msg = beginCell()
.storeUint(0x18, 6) // Флаги сообщения
.storeSlice(destAddr) // Адрес получателя
.storeCoins(amount) // Сумма
.storeUint(0, 107) // Служебные поля
.storeSlice(body) // Тело сообщения
.endCell();
Чтение ячейки (Slice)
val cs = data.beginParse();
val op = cs.loadUint(32); // Operation code
val queryId = cs.loadUint(64); // Query ID
val addr = cs.loadAddress(); // Адрес
val coins = cs.loadCoins(); // Монеты (VarUInt16)
val ref = cs.loadRef(); // Ссылка на другую ячейку
Проверки
if (cs.isEndOfSlice()) {
// Все данные прочитаны
}
val bitsLeft = cs.bitsLeft(); // Оставшиеся биты
val refsLeft = cs.refsLeft(); // Оставшиеся ссылки
Строковые литералы
Tolk поддерживает строковые литералы для удобной работы с данными:
// Строковый литерал как slice
val greeting = "Hello, TON!";
// Хранение строки в ячейке
val cell = beginCell()
.storeSlice("increment")
.endCell();
Полный пример: Counter контракт
Объединим все элементы в полноценный контракт:
// Counter -- простой контракт со счётчиком
// Хранит: counter (uint32)
// Операции: increment (op=1), decrement (op=2), set (op=3)
const OP_INCREMENT = 1;
const OP_DECREMENT = 2;
const OP_SET = 3;
fun loadCounter(): int {
val ds = getContractData().beginParse();
return ds.loadUint(32);
}
fun saveCounter(value: int) {
setContractData(
beginCell().storeUint(value, 32).endCell()
);
}
fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) {
if (msgBody.isEndOfSlice()) {
return; // Пустое сообщение -- просто получение TON
}
val op = msgBody.loadUint(32);
val queryId = msgBody.loadUint(64);
if (op == OP_INCREMENT) {
val current = loadCounter();
saveCounter(current + 1);
return;
}
if (op == OP_DECREMENT) {
val current = loadCounter();
assert(current > 0, 400); // Счётчик не может быть отрицательным
saveCounter(current - 1);
return;
}
if (op == OP_SET) {
val newValue = msgBody.loadUint(32);
saveCounter(newValue);
return;
}
throw 0xffff; // Неизвестная операция
}
get fun counter(): int {
return loadCounter();
}
get fun version(): int {
return 1;
}
Tolk код можно скомпилировать прямо в браузере с помощью встроенного редактора на платформе. Попробуйте изменить контракт и посмотреть результат компиляции.
Частые ошибки
- Путают синтаксис Tolk и Tact: хотя оба языка имеют высокоуровневый синтаксис, они компилируются по-разному и имеют разные абстракции.
- Забывают о точке с запятой в конце выражений: в отличие от Tact, Tolk требует явного завершения statements.
- Не используют типизацию: Tolk поддерживает опциональную типизацию, и её отсутствие лишает вас проверок на этапе компиляции.
- Копируют паттерны FunC вместо использования идиоматичного синтаксиса Tolk — например, используют тильда-методы вместо нового синтаксиса.
Проверка знанийIn Tolk, how does the `fun` keyword combined with type annotations (e.g., `fun add(a: int, b: int): int`) differ from FunC's function declaration style, and what practical advantage does this provide?
Итоги
- Функции объявляются через
fun, get-методы черезget fun val— неизменяемая переменная,var— изменяемая- Типы:
int,bool,cell,slice,builder,tuple - Управление потоком:
if/else,while,do/while,repeat - Cell-операции используют точечный синтаксис:
cs.loadUint(32)вместоcs~load_uint(32) throwиassert— механизмы обработки ошибокtry/catch— перехват исключений TVM
В следующем уроке мы разберём оптимизацию газа — критически важный навык для production-контрактов на TON.
Check Your Understanding
Finished the lesson?
Mark it as complete to track your progress
Войдите чтобы оценить урок