Перейти к содержанию

Рекомендации по разработке контрактов

Ниже собраны соглашения по именованию в цепочке, оценке потребления RAM/CPU/NET и минимальным требованиям к безопасности и диагностике ошибок. Они не заменяют предметный анализ угроз и нагрузочного тестирования на целевой сети.

Имена аккаунтов, действий и таблиц

В типе eosio::name (в ABI и в таблицах — как uint64_t) хранится не произвольная строка, а короткий идентификатор фиксированного алфавита, упакованный в 64 бита. Так сравнивают и сериализуют имена аккаунтов, действий и таблиц без динамических строк в ключах.

Как уложить строку в 64 бита. На первые двенадцать позиций отводится по 5 бит на символ (32 значения — условно «псевдо-base32»): строчные латинские az, цифры 15, символ .. Если имя длиннее двенадцати символов, тринадцатый кодируется 4 битами (16 значений — усечённый алфавит: ., 15, aj). Итого максимальная длина — 13 символов; это следствие арифметики 12×5 + 4 = 64, а не отдельное «магическое правило сети».

Аккаунты. Политика регистрации задаётся системным контрактом сети, а не WASM. Часто для обычных аккаунтов требуют ровно 12 символов из описанного набора, имя начинается с az, не оканчивается на . — это соглашение удобочитаемых имён. Более короткие имена (1–12 символов по тем же правилам кодирования) обычно относят к «коротким» / premium-аккаунтам, если сеть это поддерживает. Смысл разделения «12 vs короче» — в правилах регистрации, а не в том, что тринадцатый символ «обязателен»: тринадцатый нужен только если вы сознательно формируете именно 13-символьный идентификатор в пределах типа name.

Действия, таблицы, поля в ABI. Те же ограничения длины (1–13) и алфавита; отдельного требования «ровно двенадцать символов» для них нет — это именно про модель аккаунтов в системном контракте.

В C++ строка проверяется при конструировании name; неверный символ или длина дают ошибку на этапе конструирования / при парсинге литерала:

name n = "mycontract"_n;           // литерал времени компиляции
name m = name("stringfromdata");   // из данных; при несоответствии правилам — assert
std::string s = n.to_string();     // человекочитаемая форма

При вводе имён извне либо проверяйте строку до конструирования name (длина ≤ 13, алфавит az, 15, ., для 13-й позиции — только символы, допустимые для младших 4 бит), либо перехватывайте отказ: в CDT конструктор name из строки вызывает check при нарушении правил.


Планирование ресурсов (RAM, CPU, NET)

Точный ответ «сколько нужно RAM» заранее невозможен без измерений. Зависит от:

  • объёма данных в таблицах;
  • частоты и тяжести действий;
  • модели оплаты (кто платит за RAM строк — контракт или пользователь).

Практический путь:

  1. Прогоните сценарии на тестовой сети с теми же системными контрактами, что и прод.
  2. Замерьте потребление RAM/CPU/NET по логам узла и инструментам сети.
  3. Заложите запас и настройте мониторинг после запуска.

Вопросы при проектировании:

  • Храните ли вы в цепочке только необходимый минимум, а тяжёлые данные — вне сети?
  • Нет ли лишних inline-действий между контрактами, которые можно объединить?
  • Справедливо ли распределена оплата RAM между контрактом и пользователями?

Безопасность контракта

  1. Авторизация — используйте has_auth, require_auth, require_auth2; см. Действия и авторизация.
  2. Ресурсы — понимайте, кто платит за RAM/CPU/NET за каждое действие.
  3. Процесс разработки — закладывайте угрозы и проверки с первого дня.
  4. CI/CD — автоматические тесты при каждом изменении и при обновлении узла.
  5. Аудит — независимый разбор кода, желательно от двух команд.
  6. Bug bounty — поощрение отчётов об уязвимостях.

Коды ошибок uint64_t

Зачем два способа сигнализировать об отказе. Обычный вызов check(условие, "текст") кладёт в след транзакции строку — удобно читать в обозревателе и логах, но длина и содержимое строки ограничены, а локализация и стабильный разбор на стороне клиента затруднены. Перегрузка check(условие, uint64_t) (и низкоуровневый eosio_assert_code) передаёт наружу только число: в trace остаётся компактный код; человекочитаемое сообщение задаётся в клиенте, ABI или документации по таблице «код → смысл». Так делают, когда нужны предсказуемые коды для UI, интеграций или экономии места в данных ошибки.

Как применять на практике. Заведите в контракте именованные константы или enum class со значениями uint64_t в своём диапазоне (см. таблицу ниже). При нарушении инварианта вызывайте, например, check(balance >= amount, err_insufficient_balance);. На стороне кошелька или бэкенда храните словарь соответствия кода строке на нужном языке. При желании смысл кодов можно отразить в ABI (секции, которые генерирует CDT для кодов ошибок конкретного контракта), чтобы автоматизировать подсказки SDK.

Почему указаны диапазоны. Среда исполнения и toolchain резервируют старшие интервалы, чтобы их коды никогда не пересеклись с вашими произвольными числами. Если задать «любой» uint64_t, возможна коллизия с внутренней ошибкой CDT или протокола — клиент ошибочно интерпретирует сбой.

Диапазон значений Кто использует
от 0 до 4_999_999_999_999_999_999 Ваш контракт — бизнес-ошибки и инварианты
от 5×10^18 до 7_999_999_999_999_999_999 CDT: коды, привязанные к конкретному контракту (расшифровка в ABI)
от 8×10^18 до 9_999_999_999_999_999_999 CDT: общие ошибки среды исполнения
от 10^19 до 2^64−1 Зарезервировано протоколом; для пользовательского eosio_assert_code не предназначено

Итог: для ручных кодов ошибок в логике контракта выбирайте значения только из первой строки таблицы; строковые check(..., "…") оставляйте там, где важнее немедленная читаемость без доработки клиента.


См. также