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

Обзор низкоуровневых комиссий

warning

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

предупреждение

This section describes instructions and manuals for interacting with TON at a low level.

Здесь вы найдете сырые формулы для расчета комиссий и сборов в TON.

Однако большинство из них уже реализованы через коды операций! Поэтому вы используете их вместо ручных вычислений

В этом документе дается общее представление о комиссиях за транзакции в TON и, в частности, о комиссиях за вычисления для кода FunC. Также есть подробная спецификация в техническом документе TVM.

Транзакции и фазы

Как было описано в обзоре TVM, выполнение транзакции состоит из нескольких фаз. Во время этих фаз могут вычитаться соответствующие комиссии. Существует обзор высокоуровневых комиссий.

Плата за хранение

Валидаторы TON взимают плату за хранение со смарт-контрактов.

Плата за хранение взимается с balance смарт-контракта на фазе хранения любой транзакции в связи с оплатой за хранение в зависимости от состояния аккаунта (включая код и данные смарт-контракта, если они есть) до настоящего времени. Даже если контракт получил 1 nanoton, он погасит всю задолженность с момента последней оплаты. В результате смарт-контракт может быть заморожен. Только уникальные ячейки хеша учитываются для комиссий за хранение и пересылку, т. е. 3 одинаковые ячейки хеша считаются как одна. В частности, это дедуплицирует данные: если в разных ветвях есть несколько эквивалентных подячеек, их содержимое сохраняется только один раз.

Важно помнить, что в TON вы платите как за выполнение смарт-контракта, так и за используемое хранилище (см. статью @thedailyton), storage_fee зависит от размера вашего контракта: количества ячеек и суммы битов из этих ячеек. Это означает, что вам придется платить комиссию за хранение за наличие кошелька TON (даже если он очень-очень маленький).

Если вы не использовали свой кошелек TON в течение значительного периода времени (1 год), вам придется заплатить значительно большую комиссию, чем обычно, поскольку кошелек платит комиссию за отправку и получение транзакций.

Примечание:

Когда сообщение возвращается из контракта, контракт выплачивает свою текущую storage_fee

Формула

Вы можете приблизительно рассчитать плату за хранение для смарт-контрактов, используя эту формулу:

storage_fee = ceil(
(account.bits * bit_price
+ account.cells * cell_price)
* time_delta / 2 ^ 16)

Давайте рассмотрим каждое значение более подробно:

  • storage_fee — цена за хранение для time_delta секунд
  • account.cells — количество ячеек, используемых смарт-контрактом
  • account.bits — количество бит, используемых смарт-контрактом
  • cell_price — цена одной ячейки
  • bit_price — цена одного бита

И cell_price, и bit_price можно получить из конфигурации сети параметр 18.

Текущие значения:

  • Воркчейн.
    bit_price_ps:1
    cell_price_ps:500
  • Мастерчейн.
    mc_bit_price_ps:1000
    mc_cell_price_ps:500000

Пример калькулятора

Вы можете использовать этот скрипт JS для расчета стоимости хранения 1 МБ в воркчейне на 1 год

Интерактивный редактор
// Welcome to LIVE editor!
// feel free to change any variables
// Source code uses RoundUp for the fee amount, so does the calculator

function storageFeeCalculator() {
  const size = 1024 * 1024 * 8; // 1MB in bits
  const duration = 60 * 60 * 24 * 365; // 1 Year in secs

  const bit_price_ps = 1;
  const cell_price_ps = 500;

  const pricePerSec =
    size * bit_price_ps + +Math.ceil(size / 1023) * cell_price_ps;

  let fee = Math.ceil((pricePerSec * duration) / 2 ** 16) * 10 ** -9;
  let mb = (size / 1024 / 1024 / 8).toFixed(2);
  let days = Math.floor(duration / (3600 * 24));

  let str = `Storage Fee: ${fee} TON (${mb} MB for ${days} days)`;

  return str;
}
Результат
Loading...

Плата за вычисления

Газ

Все затраты на вычисления номинированы в единицах газа. Цена единиц газа определяется этой конфигурацией цепи (Конфигурация 20 для мастерчейна и Конфигурация 21 для бейсчейна) и может быть изменена только по консенсусу валидаторов. Обратите внимание, что в отличие от других систем, пользователь не может устанавливать собственную цену на газ, и нет рынка комиссий.

Текущие настройки в бейсчейне следующие: 1 единица газа стоит 400 nanoton.

Стоимость инструкций TVM

На самом низком уровне (выполнение инструкций TVM) стоимость газа для большинства примитивов равна базовой цене газа, вычисляемой как P_b := 10 + b + 5r, где b — длина инструкции в битах, а r — количество ссылок на ячейки, включенных в инструкцию.

Помимо этих основных сборов, появляются следующие сборы:

ИнструкцияЦена газаОписание
Создание ячейки500Операция преобразования строителя в ячейку.
Анализ ячейки в первый раз100Операция преобразования ячеек в срезы первый раз в ходе текущей транзакции.
Повторный анализ ячейки25Операция преобразования ячеек в срезы, которая уже была проанализирована в ходе той же транзакции.
Вызов исключения50
Операция с кортежем1Эта цена будет умножена на количество элементов кортежа.
Неявный переход10Оплачивается, когда выполняются все инструкции в текущей ячейке продолжения. Однако в этой ячейке продолжения есть ссылки, и поток выполнения переходит к первой ссылке.
Неявный обратный переход5Он оплачивается, когда все инструкции в текущем продолжении выполнены, и поток выполнения возвращается к продолжению, из которого было вызвано только что завершенное продолжение.
Перемещение элементов стека1Цена за перемещение элементов стека между продолжениями. Будет взиматься соответствующая цена газа за каждый элемент. Однако перемещение первых 32 элементов бесплатно.

Плата за газ для FunC конструкций

Почти все функции FunC, используемые в этой статье, определены в контракте stdlib.fc стейблкоина (на самом деле stdlib.fc с новыми кодами операций в настоящее время находится в стадии разработки и еще не представлен в репозиториях основной сети, но вы можете использовать stdlib.fc из исходного кода стейблкоина в качестве ссылки), который сопоставляет функции FunC с инструкциями ассемблера Fift. В свою очередь, инструкции ассемблера Fift сопоставляются с инструкциями битовой последовательности в asm.fif. Поэтому, если вы хотите понять, сколько именно будет стоить вызов инструкции, вам нужно найти представление asm в stdlib.fc, затем найти битовую последовательность в asm.fif и вычислить длину инструкции в битах.

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

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

Операции с ячейками

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

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

slice payload_encoding(int a, int b, int c) {
return
begin_cell().store_uint(a,8)
.store_uint(b,8)
.store_uint(c,8)
.end_cell().begin_parse();
}

() send_message(slice destination) impure {
slice payload = payload_encoding(1, 7, 12);
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(destination)
.store_coins(0)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(0x33bbff77, 32) ;; op-code (see smart-contract guidelines)
.store_uint(cur_lt(), 64) ;; query_id (see smart-contract guidelines)
.store_slice(payload)
.end_cell();
send_raw_message(msg, 64);
}

В чем проблема с этим кодом? payload_encoding для генерации битовой строки среза, сначала создайте ячейку с помощью end_cell() (+500 единиц газа). Затем проанализируйте ее begin_parse() (+100 единиц газа). Тот же код можно написать без этих ненужных операций, изменив некоторые часто используемые типы:

;; we add asm for function which stores one builder to the another, which is absent from stdlib
builder store_builder(builder to, builder what) asm(what to) "STB";

builder payload_encoding(int a, int b, int c) {
return
begin_cell().store_uint(a,8)
.store_uint(b,8)
.store_uint(c,8);
}

() send_message(slice destination) impure {
builder payload = payload_encoding(1, 7, 12);
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(destination)
.store_coins(0)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(0x33bbff77, 32) ;; op-code (see smart-contract guidelines)
.store_uint(cur_lt(), 64) ;; query_id (see smart-contract guidelines)
.store_builder(payload)
.end_cell();
send_raw_message(msg, 64);
}

Передавая битовую строку в другой форме (строитель вместо среза), мы существенно снижаем стоимость вычислений за счет очень небольшого изменения кода.

Inline и inline_refs

По умолчанию, когда у вас есть функция FunC, она получает свой собственный id, хранящийся в отдельном листе словаря id->function, и когда вы вызываете ее где-то в программе, происходит поиск функции в словаре и последующий переход. Такое поведение оправдано, если ваша функция вызывается из многих мест в коде, и, таким образом, переходы позволяют уменьшить размер кода (сохраняя тело функции один раз). Однако, если функция используется только один или два раза, часто гораздо дешевле объявить эту функцию как inline или inline_ref. Модификатор inline помещает тело функции прямо в код родительской функции, в то время как inline_ref помещает код функции в ссылку (переход к ссылке все еще намного дешевле, чем поиск и переход к записи словаря).

Словари

Словари в TON представлены как деревья (точнее, DAG) ячеек. Это означает, что если вы ищете, читаете или пишете в словарь, вам нужно разобрать все ячейки соответствующей ветви дерева. Это означает, что

  • а) операции со словарями не фиксированы в стоимости газа (так как размер и количество узлов в ветви зависят от данного словаря и ключа)
  • б) целесообразно оптимизировать использование словаря, используя специальные инструкции, такие как replace вместо delete и add
  • в) разработчик должен знать об операциях итерации (таких как next и prev), а также об операциях min_key/max_key, чтобы избежать ненужной итерации по всему словарю

Операции со стеком

Обратите внимание, что FunC манипулирует записями стека под капотом. Это означает, что код:

(int a, int b, int c) = some_f();
return (c, b, a);

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

Когда количество записей стека значительно (10+), и они активно используются в разных порядках, расходы на операции со стеком могут стать существенными.

Плата за пересылку

Внутренние сообщения определяют ihr_fee в Toncoin, которая вычитается из стоимости, прикрепленной к сообщению, и присуждается валидаторам целевого шардчейна, если они включают сообщение через механизм IHR. fwd_fee — это исходная общая плата за пересылку, уплачиваемая с использованием механизма HR; она автоматически вычисляется из 24 и 25 параметров конфигурации и размера сообщения на момент его генерации. Обратите внимание, что общая стоимость, переносимая вновь созданным внутренним исходящим сообщением, равна сумме стоимости, ihr_fee и fwd_fee. Эта сумма вычитается из баланса исходного аккаунта. Из этих компонентов только значение ihr_fee зачисляется на целевой аккаунт при доставке сообщения. fwd_fee собирается валидаторами на пути HR от источника до пункта назначения, а ihr_fee либо собирается валидаторами целевого шардчейна (если сообщение доставляется через IHR), либо зачисляется на конечный аккаунт.

IHR

подсказка

На данный момент (ноябрь 2024 г.) IHR не реализован, и если вы установите ihr_fee на ненулевое значение, оно всегда будет добавлено к значению сообщения при получении. На данный момент нет практических причин делать это.

Формула

// In_msg and Ext_msg are using the same method of calculation
// It is called import_fee or in_fwd_fee for the Ext_msg
// https://github.com/ton-blockchain/ton/blob/7151ff26279fef6dcfa1f47fc0c5b63677ae2458/crypto/block/transaction.cpp#L2071-L2090

// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
msg_fwd_fees = (lump_price
+ ceil(
(bit_price * msg.bits + cell_price * msg.cells) / 2^16)
);

ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor) / 2^16);

total_fwd_fees = msg_fwd_fees + ihr_fwd_fees; // ihr_fwd_fees - is 0 for external messages
ВАЖНО

fwd_fee = msg_fwd_fees - action_fee = 266669 nanoton = 0,000266669 TON

Плата за действие

Плата за действие вычитается из баланса исходного аккаунта во время обработки списка действий, которая происходит после фазы вычислений. Фактически, единственное действие, за которое вы платите плату за действие, это SENDRAWMSG. Другие действия, такие как RAWRESERVE или SETCODE, не влекут за собой никаких комиссий во время фазы действия.

action_fee = floor((msg_fwd_fees * first_frac)/ 2^16);  //internal

action_fee = msg_fwd_fees; //external

first_frac является частью параметров 24 и 25 (для мастерчейна и воркчейна) блокчейна TON. В настоящее время оба установлены на значение 21845, что означает, что action_fee составляет примерно треть от msg_fwd_fees. В случае внешнего действия сообщения SENDRAWMSG, action_fee равен msg_fwd_fees.

подсказка

Remember that an action register can contain up to 255 actions, which means that all formulas related to fwd_fee and action_fee will be computed for each SENDRAWMSG action, resulting in the following sum:

total_fees = sum(action_fee) + sum(total_fwd_fees);

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

fine_per_cell = floor((cell_price >> 16) / 4)

max_cells = floor(remaining_balance / fine_per_cell)

action_fine = fine_per_cell * min(max_cells, cells_in_msg);

Файл конфигурации платы

Все платы указаны в nanoton или nanoton, умноженных на 2^16, чтобы сохранить точность при использовании целого числа и могут быть изменены. Файл конфигурации отображает текущую стоимость платы.

к сведению

A direct link to the mainnet live config file

В образовательных целях пример старого

Ссылки

См. также