Опкоды EVM: как устроен байткод Ethereum и сколько газа стоят операции

Опкод EVM (opcode) — это одна байтовая команда виртуальной машины Ethereum, которая задаёт конкретное действие: сложить числа, прочитать переменную из хранилища, вызвать другой контракт, выкинуть лог и т.д. Смарт-контракт на Solidity или другом языке в итоге компилируется в последовательность опкодов — байткод, который выполняет EVM.

У каждого опкода есть:

  • однобайтовый код (0x00–0xFF);
  • имя (например, ADD, SLOAD, CALL);
  • набор параметров (берутся из стека или из байткода);
  • стоимость по газу, которая влияет на цену исполнения транзакции.

Опкоды EVM: как устроен байткод Ethereum и сколько газа стоят операции

EVM как стековая машина

EVM — это стековая виртуальная машина. Это означает, что опкоды читают и записывают данные в стек:

  • стек — LIFO-структура (последний пришёл — первый вышел), каждый элемент — 256-битное слово;
  • большинство опкодов берут аргументы из стека и кладут результат обратно в стек;
  • кроме стека есть память (временное хранилище в рамках вызова) и storage (постоянное хранилище контракта).

Пример простейшей последовательности опкодов:

  • PUSH1 0x02 — кладёт число 2 в стек;
  • PUSH1 0x03 — кладёт 3 в стек;
  • ADD — снимает два верхних элемента (3 и 2), складывает и кладёт 5 обратно в стек.

Байт-код контракта — это просто цепочка таких команд с параметрами. Высокоуровневый код (Solidity, Vyper) компилируется в такую последовательность опкодов.

Основные группы опкодов

Опкоды EVM логически делятся на несколько групп:

  • Арифметика и логика

Операции над числами и битами: ADD, SUB, MUL, DIV, MOD, AND, OR, XOR, NOT, SHL, SHR.

Эти опкоды работают только со стеком и не трогают память/хранилище.

  • Сравнение и условные переходы

LT, GT, EQ, ISZERO и условный переход JUMPI.

На их основе реализуются if, while и другие конструкции в коде смарт-контракта.

  • Работа со стеком

POP, DUP1DUP16, SWAP1SWAP16.

Нужны для перестановки элементов в стеке, чтобы подать их на нужные опкоды.

  • Память и хранилище
    • MLOAD, MSTORE, MSTORE8 — операции с временной памятью (memory);
    • SLOAD, SSTORE — чтение и запись в постоянное хранилище (storage).

Именно SSTORE и SLOAD дают основной вклад в стоимость газа — см. Газ в Ethereum.

  • Окружение и блокчейн

Опкоды, которые дают доступ к данным транзакции и блока:

  • CALLER, CALLVALUE, CALLDATALOAD — информация о вызове;
  • ADDRESS, BALANCE — данные текущего контракта и баланса;
  • TIMESTAMP, NUMBER, GASPRICE — параметры блока и транзакции.

Через них контракт «видит» внешнее окружение сети.

  • Вызовы и создание контрактов

CALL, STATICCALL, DELEGATECALL, CALLCODE, CREATE, CREATE2.

С их помощью контракт взаимодействует с другими контрактами и создаёт новые. Расширенные риски таких операций разбираются на страницах DELEGATECALL и риски прокси и CREATE2.

  • Логи (events)

LOG0LOG4 записывают события в лог-журнал, который индексируется по топикам (в том числе с использованием Keccak-256).

  • Системные опкоды

RETURN, REVERT, INVALID, STOP, SELFDESTRUCT — управление завершением исполнения и ошибками.

Опкоды и стоимость газа

Каждый опкод имеет свою базовую стоимость по газу, которая определяет, сколько «вычислительных ресурсов» сети он потребляет.

Основные идеи:

  • дешёвые операции (арифметика, стек, простая память) стоят мало газа;
  • операции с постоянным хранилищем (SSTORE, SLOAD) стоят значительно дороже, так как меняют состояние сети;
  • некоторые опкоды могут иметь динамическую стоимость, зависящую от контекста (например, первый доступ к слоту storage в рамках транзакции может стоить дороже, чем последующие «тёплые» обращения).

Общее потребление газа транзакцией — это сумма затрат всех опкодов плюс расходы на передачу данных в calldata и изменения состояния. Подробнее тема раскрывается в комиссиях за газ в Ethereum.

Зачем разработчику понимать опкоды

Хотя в повседневной работе разработчики чаще пишут на Solidity, понимание опкодов EVM полезно:

  • Оптимизация газа

Зная, какие опкоды «дорогие», легче переписать участок кода так, чтобы использовать меньше SSTORE или сократить обращения к памяти.

  • Отладка и аудит

При аудите сложных контрактов важно уметь смотреть на байткод: проверять, как реально реализованы проверки, нет ли неожиданных DELEGATECALL или опасных шаблонов.

  • Работа с низкоуровневым кодом

Языки вроде Yul дают более прямой доступ к опкодам и позволяют писать вручную оптимизированные участки логики.

  • Реверс-инжиниринг

При анализе чужих контрактов без исходников приходится смотреть именно на последовательность опкодов, чтобы восстановить логику.

См. также

Task Runner