ABI (Application Binary Interface) в контексте Ethereum — это спецификация того, как кодируются и декодируются данные при вызове функций смарт-контрактов и при обработке событий. Любой вызов контракта на уровне EVM — это просто байты, упакованные по правилам ABI.
Через ABI договариваются:
- как из человеческого описания transfer(address,uint256) получить бинарный формат calldata;
- как читать возвращаемые значения функций;
- как кодируются события (events), чтобы их можно было декодировать на фронтенде и в аналитике.
Зачем нужен ABI
Смарт-контракт работает с байтами, а разработчик — с типами и структурами данных. ABI решает задачу «перевода» между этими мирами:
- описывает сигнатуры функций и событий: имена, типы аргументов, возвращаемые значения;
- задаёт строгое правило кодирования аргументов в calldata и результатов — в return data;
- определяет формат логов событий, чтобы их могли парсить кошельки, блок-эксплореры и dApp’ы.
Без ABI:
- нельзя было бы надёжно вызвать функцию по имени и набору типов;
- инструменты вроде Web3.js / Ethers.js не смогли бы автоматически формировать транзакции и декодировать ответы.
Как ABI связан с function selector
Для внешних вызовов функций контрактов используется схема:
- Строка сигнатуры вида "transfer(address,uint256)".
- Вычисляется keccak256 от этой строки — см. Keccak-256.
- Первые 4 байта результата — это function selector.
Поле calldata внешнего вызова выглядит так:
- 4 байта — селектор функции;
- далее — аргументы, закодированные по правилам ABI (ABI encoding).
То есть ABI описывает:
- какие типы стоят после селектора;
- как именно каждый тип превращается в байты.
Статические и динамические типы в ABI
ABI делит типы на:
- Статические — фиксированный размер:
- uint256, int256, address, bool, bytes32, фиксированные массивы с фиксированным числом элементов;
- в кодировке каждый такой элемент занимает ровно 32 байта.
- Динамические — размер зависит от значения:
- bytes, string, динамические массивы (uint256[], address[]), структуры с динамическими полями;
- в «голове» (head) хранятся только смещения (offset), а сами данные — в «хвосте» (tail) кодировки.
Общая идея ABI-кодирования:
- все аргументы сначала превращаются в последовательность 32-байтовых «слов»;
- для динамических типов в основном блоке записывается offset, который указывает, где в хвосте лежит длина и сами данные;
- длина динамического массива или строки тоже хранится как 32-байтовое слово перед самими элементами/байтами.
abi.encode и abi.encodePacked
В Solidity поверх базовых правил ABI есть удобные помощники:
- abi.encode(...)
- кодирует аргументы в полноценном ABI-формате (как в calldata/return data);
- каждый аргумент выровнен до 32 байт, динамические типы используют схему head/tail.
- abi.encodePacked(...)
- делает плотное, «спакованное» кодирование без выравнивания;
- может быть удобен для хеширования (keccak256(abi.encodePacked(...))), но при этом возможны коллизии разных наборов аргументов.
Типичные ошибки:
- использовать abi.encodePacked там, где ожидается обычный ABI-формат (например, внутри низкоуровневого вызова);
- не учитывать возможные коллизии при хешировании составных параметров.
ABI и вызовы/ответы функций
Внешний вызов функции контракта «снаружи» выглядит так:
- фронтенд/библиотека формирует JSON-подобное описание: имя функции, аргументы, типы;
- по ABI считается селектор, кодируются аргументы → получается calldata;
- транзакция отправляется в сеть, EVM выполняет байткод;
- возвращаемое значение кодируется обратно по ABI и попадает в return data.
На уровне EVM:
- ни имён функций, ни типов уже нет — только байтовый поток;
- их нужно восстановить с помощью того же ABI-описания, которым пользовались при кодировании.
ABI и события (events)
События в Ethereum также используют ABI:
- строка сигнатуры события ("Transfer(address,address,uint256)") хешируется Keccak-256;
- получившийся хеш используется как topic0 — идентификатор события;
- индексация аргументов (indexed/non-indexed) определяет, какие поля попадут в topics, а какие — в data.
ABI:
- задаёт порядок и типы аргументов события;
- описывает, какие значения искать в топиках, а какие — декодировать из блока данных.
Где используется ABI на практике
- Фронтенд dApp’ов
JSON-описание ABI контракта подключается к Web3/Ethers.js, и разработчик вызывает contract.transfer(...), не думая о байтах.
- Кошельки и блок-эксплореры
Интерфейсы читают ABI популярных контрактов и по нему красиво отображают вызовы и события, а не «сырые» hex-значения.
- Инструменты разработки
Hardhat, Foundry, Truffle, Remix — все используют ABI для генерации интерфейсов и типобезопасных обёрток вокруг контрактов.
