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

ABI смарт-контракта: кодирование вызовов Solidity

Сайт не передаёт в EVM текст publish(17,H1) как команду на русском языке. Он формирует байты по договору интерфейса. Такой договор называют ABI — Application Binary Interface. Он описывает функции, аргументы, ответы, события и ошибки, но не выбирает экземпляр и не удостоверяет его происхождение.

На нашем реестре разберём форму вызова и getter. Цель — понять, почему правильное имя функции недостаточно, почему тип uint256 имеет значение и как неверный ABI превращает нормальный ответ в ошибку декодирования.

Сигнатура функции

Для выбора функции используется селектор, связанный с её канонической сигнатурой. Сигнатура включает имя и типы аргументов в установленной форме. Имена локальных параметров не выбирают метод. Поэтому переименование courseId в id без смены типов не равнозначно созданию нового внешнего метода.

Функция: publish(uint256 courseId, bytes32 contentHash)
Каноническая сигнатура: publish(uint256,bytes32)
Сообщение: селектор + закодированные аргументы

Нельзя использовать случайные четыре байта из примера другой функции. Клиентская библиотека вычисляет и кодирует запрос по указанному ABI. Ручной разбор селектора полезен для понимания, но не требуется превращать каждый вызов сайта в самодельный кодировщик.

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

Фрагмент интерфейса чтения

Для getter courses нужен такой смысл описания:

{
  "type": "function",
  "name": "courses",
  "stateMutability": "view",
  "inputs": [{"name":"","type":"uint256"}],
  "outputs": [
    {"name":"contentHash","type":"bytes32"},
    {"name":"active","type":"bool"}
  ]
}

Это объясняющий фрагмент, не полный артефакт компиляции. Он сообщает один входной номер и два выходных поля структуры. Полный ABI также содержит конструктор, издателя, методы, события и ошибки. Для реального клиента следует сохранять артефакт выбранного исходника, а не собирать его по памяти.

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

Статические и динамические значения

Наши основные аргументы фиксированного размера. В ABI даже небольшое число кодируется по установленным правилам, а не произвольной строкой 17. Для динамических массивов и строк появляется дополнительная структура с длиной и смещениями. Поэтому добавление string title существенно меняет способ кодирования, хотя в Solidity выглядит как ещё один параметр.

Если для курса передать UTF-8-строку вместо bytes32, библиотека должна выявить несовместимость или потребуется явное преобразование по договору. Случайная обрезка текста до 32 байтов не вычисляет отпечаток. Правильный алгоритм обработки внешнего файла остаётся самостоятельной операцией.

Для сложных структур ABI использует tuple и описание компонентов. Это ещё одна причина хранить соответствующий артефакт: интерфейс длинной структуры труднее надёжно восстановить вручную. Структура с mapping вообще не превращается в обычный переносимый объект произвольного размера.

Сеть и адрес находятся вне ABI

Пусть у клиента правильный интерфейс CourseCatalog, но выбран адрес другого экземпляра. Он способен получить корректно закодированный ответ с другим издателем и другим отпечатком. Здесь нет ошибки ABI: ошибка относится к источнику состояния.

Поэтому приложение хранит как минимум сеть, адрес и интерфейс. Для проверки выпуска также нужны известный издатель и правила файла. Тройка не является сертификатом безопасности, но устраняет распространённую путаницу между описание программы и её конкретной установкой.

chainId: 31337
address: адрес нашего отдельного локального экземпляра
ABI: артефакт выбранного CourseCatalog
courseId: 17

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

Ошибки и события тоже декодируются

ABI события объясняет расположение индексированных тем и данных. ABI ошибки объясняет селектор отказа и параметры. Если клиент загрузил только описание courses, он может успешно читать запись, но не понимать причину отката публикации. Урезанный набор следует выбирать намеренно под назначение клиента.

При неизвестных байтах не следует изобретать NotActive. Неподходящий интерфейс, внешняя ошибка или транспортный отказ имеют свои причины. Возможность вывести общее сообщение и сохранить диагностические данные важнее желания классифицировать всё по трём нашим названиям.

Неполное условие Возможный эффект
Правильный ABI, другой адрес Правильное декодирование чужого состояния
Правильный адрес, старый ABI Ошибка вызова или понимания ответа
Только getter в ABI Чтение возможно, события и ошибки не описаны
Неверный тип аргумента Запрос не соответствует ожидаемой функции

Мы получили описание языка общения клиента и реестра. В дальнейшем библиотека viem будет выполнять кодирование, но читатель уже сможет объяснить, что она делает и почему одного имени CourseCatalog недостаточно. Спецификация ABI служит точным справочником для этого договора.

Читаем ответ как договор

Предположим, клиент ожидал пару bytes32,bool, а новая программа возвращает bytes32,uint256. Первое поле по-прежнему выглядит знакомым, но второе уже обозначает другую величину. Попытка трактовать его как активность не является совместимостью интерфейсов. Сначала нужно выяснить версию экземпляра и получить правильное описание.

Смена названия выходного поля может быть менее существенной для двоичного кодирования, но важной для кода, который обращается к именованным компонентам библиотеки. Поэтому обновление ABI лучше рассматривать вместе с потребителями, а не оценивать по одному JSON-фрагменту. Формат ответа влияет на способ представления в конкретном клиенте.

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

Для самостоятельного упражнения разделите три ошибки: библиотека не смогла закодировать аргумент; узел выполнил другой метод по другому адресу; клиент не смог декодировать ответ. На каждой стадии нужны разные данные. Универсальное сообщение «неверный ABI» может оказаться слишком широким, если проблема в выборе сети.

В реестре полезно сохранить короткий внешний интерфейс. Чем меньше неоднозначных перегрузок и динамических полей, тем проще читателю сопоставить действия сайта с методами. Это не запрет сложных ABI, а выбор подходящей начальной модели, которую затем можно расширять осознанно.

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

Оглавление серии