Типы Solidity: uint, bytes32 и bool
При проектировании реестра недостаточно выбрать поля по удобству интерфейса. Тип сообщает, какие значения программа способна представить и какие операции имеют смысл. Рассмотрим номер курса, отпечаток выпуска, адрес издателя и признак активности. Эти четыре значения образуют маленькую модель, на которой видны основные типы Solidity.
Сразу отделим технический диапазон от бизнес-правила. uint256 способен хранить ноль, но наш номер курса не должен равняться нулю. bytes32 способен содержать любые 32 байта, но нулевой отпечаток зарезервирован как отсутствие публикации. Выбор типа и проверка допустимости решают разные задачи.
Целые числа
uint256 — беззнаковое целое фиксированной разрядности. Оно подходит ключу курса, поскольку номер не отрицателен. Это не произвольное математическое число без границ. Для арифметики нужно учитывать конечный диапазон, хотя номер 17 находится далеко от его верхнего края.
Почему не выбрать uint8, если пока опубликовано меньше двухсот курсов? Такой выбор связывает модель с очень коротким диапазоном. Кроме того, узкий тип не делает любой ABI-параметр автоматически короче: стандартное кодирование имеет собственные правила. Экономия в упакованном состоянии и размер входного аргумента — разные вопросы.
uint256 courseId = 17;
bool active = true;
bytes32 contentHash = bytes32(uint256(1));
Фрагмент иллюстрирует значения локальных переменных. Отпечаток bytes32(uint256(1)) здесь — ненулевое учебное значение для анализа состояния, а не криптографический результат настоящего файла. Для реального выпуска отпечаток вычисляют по определённым байтам и алгоритму. Если заменить его красивым произвольным числом, проверка содержимого потеряет смысл.
Целочисленное деление не хранит дробную часть. Когда позже появятся токены или расчёт цены, нужно будет определить масштаб и округление. Для номера курса деление вообще не требуется. Это ещё один довод выбирать операции по предметному смыслу, а не потому, что тип поддерживает арифметику.
bytes32 не является строкой
Тип bytes32 представляет ровно 32 байта. В интерфейсе их часто записывают как 0x и 64 шестнадцатеричных символа. Видимая строка служит представлением значения, а не способом увеличить его размер. Отпечаток с 63 символами после префикса уже требует проверки формата, а произвольное русское название курса не превращается в подходящие 32 байта автоматически.
string предназначена для динамической последовательности байтов текста с согласованной кодировкой. Размер её содержимого может меняться. Для хранения заголовка «Статический сайт из Markdown» строка выглядит естественно, но базовый реестр намеренно её не хранит. Заголовок относится к внешнему манифесту, а отпечаток связывает реестр с конкретным выпуском этого манифеста.
Такой выбор уменьшает зависимость контракта от редакционного формата. Изменение орфографии заголовка меняет байты выпуска и требует нового отпечатка, но не требует вводить специальную строковую функцию в контракт. Это позволяет сайту развивать описания, сохраняя простую модель реестра.
bool и отсутствие записи
bool имеет два логических значения. Поле active сообщает, можно ли считать текущую запись действующей по правилам нашего издателя. Оно не объясняет причины деактивации и не кодирует всю историю. Если нужна отдельная причина, её следует добавить в другую явно описанную модель, а не приписывать смысл значениям типа, которых у него нет.
Значение false встречается как у никогда не опубликованного курса, так и у деактивированного. Поэтому для различения полезен отпечаток. Отсутствующая запись имеет нули и false; отключённый курс сохраняет ненулевой отпечаток и false. Состояние нулевой отпечаток + true не должно быть достижимо через допустимый publish.
| Отпечаток | Флаг | Значение в базовой модели |
|---|---|---|
| Нули | false | Публикации ещё нет |
| H1 | true | Выпуск объявлен активным |
| H1 | false | Выпуск отключён, указатель сохранён |
| Нули | true | Нарушение ожидаемого свойства |
Составление такой таблицы часто полезнее преждевременного добавления множества enum-значений. Она показывает, каких состояний достаточно, а какое сочетание нужно исключить. Если бизнес-процесс действительно требует «черновик», «ожидает проверки» и «заморожен», понадобится другая модель, введённая отдельно.
Преобразование и потеря информации
Явное преобразование типа может быть полезно, но не доказывает корректность значения. При сужении целого числа возможна потеря старших разрядов по правилам преобразования. Арифметические проверки Solidity 0.8.x не заменяют проверки всех преобразований. Поэтому перед сохранением большого номера в узком поле требуется ограничение диапазона.
Адрес также нельзя рассматривать как номер курса. У него другая роль, представление и смысл проверки. Преобразования, которые заставляют код компилироваться, не создают предметной связи между адресом и ключом реестра. Это относится и к сравнению текстового представления хеша со строкой URL: значения могут быть похожи внешне, но обозначают разные данные.
В веб-клиенте большие целые значения следует сохранять без потери точности. Обычный JavaScript Number не представляет все значения uint256 точно. Даже если courseId 17 безопасен, выбранная библиотека может возвращать bigint, и общие функции должны уважать этот тип. Перевод в JSON обычно требует явно выбранного строкового представления, а не случайного округления.
В результате мы определили четыре типа и несколько независимых требований. Номер должен быть ненулевым, отпечаток должен иметь известный алгоритм и ненулевое значение, издатель задаётся адресом, активность отражает ровно одно свойство. На следующих страницах эта модель превратится в структуру и постоянное состояние. Типы Solidity служат справочником для точных правил преобразования и диапазонов.
Тип результата и подпись поля
Рассмотрим интерфейс, который показывает active как «материал проверен». Тип остаётся корректным, но подпись добавляет смысл, которого контракт не обеспечивает. Издатель мог активировать выпуск без полной редакционной проверки. Поэтому правильная типизация кода не освобождает от точного описания предметной модели.
Точно так же название contentHash не сообщает алгоритм, а номер курса не сообщает его заголовок. Эти сведения находятся в отдельном договоре файла и манифесте. Хороший интерфейс объединяет их с указанием происхождения, не выдавая каждое значение за самостоятельный сертификат.
Попробуйте представить запись с ненулевым hash и false. Если приложение воспринимает false как «курса не существует», оно потеряет различие отзыва и отсутствия. Исправление связано с интерпретацией пары полей, а не с заменой bool на строку. Сначала уточняется модель, затем выбирается представление данных.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.