Рекомендации по разработке контрактов¶
Ниже собраны соглашения по именованию в цепочке, оценке потребления RAM/CPU/NET и минимальным требованиям к безопасности и диагностике ошибок. Они не заменяют предметный анализ угроз и нагрузочного тестирования на целевой сети.
Имена аккаунтов, действий и таблиц¶
В типе eosio::name (в ABI и в таблицах — как uint64_t) хранится не произвольная строка, а короткий идентификатор фиксированного алфавита, упакованный в 64 бита. Так сравнивают и сериализуют имена аккаунтов, действий и таблиц без динамических строк в ключах.
Как уложить строку в 64 бита. На первые двенадцать позиций отводится по 5 бит на символ (32 значения — условно «псевдо-base32»): строчные латинские a–z, цифры 1–5, символ .. Если имя длиннее двенадцати символов, тринадцатый кодируется 4 битами (16 значений — усечённый алфавит: ., 1–5, a–j). Итого максимальная длина — 13 символов; это следствие арифметики 12×5 + 4 = 64, а не отдельное «магическое правило сети».
Аккаунты. Политика регистрации задаётся системным контрактом сети, а не WASM. Часто для обычных аккаунтов требуют ровно 12 символов из описанного набора, имя начинается с a–z, не оканчивается на . — это соглашение удобочитаемых имён. Более короткие имена (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, алфавит a–z, 1–5, ., для 13-й позиции — только символы, допустимые для младших 4 бит), либо перехватывайте отказ: в CDT конструктор name из строки вызывает check при нарушении правил.
Планирование ресурсов (RAM, CPU, NET)¶
Точный ответ «сколько нужно RAM» заранее невозможен без измерений. Зависит от:
- объёма данных в таблицах;
- частоты и тяжести действий;
- модели оплаты (кто платит за RAM строк — контракт или пользователь).
Практический путь:
- Прогоните сценарии на тестовой сети с теми же системными контрактами, что и прод.
- Замерьте потребление RAM/CPU/NET по логам узла и инструментам сети.
- Заложите запас и настройте мониторинг после запуска.
Вопросы при проектировании:
- Храните ли вы в цепочке только необходимый минимум, а тяжёлые данные — вне сети?
- Нет ли лишних inline-действий между контрактами, которые можно объединить?
- Справедливо ли распределена оплата RAM между контрактом и пользователями?
Безопасность контракта¶
- Авторизация — используйте
has_auth,require_auth,require_auth2; см. Действия и авторизация. - Ресурсы — понимайте, кто платит за RAM/CPU/NET за каждое действие.
- Процесс разработки — закладывайте угрозы и проверки с первого дня.
- CI/CD — автоматические тесты при каждом изменении и при обновлении узла.
- Аудит — независимый разбор кода, желательно от двух команд.
- 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(..., "…") оставляйте там, где важнее немедленная читаемость без доработки клиента.