Часть III · Глава 28 из 43 · Shanghai · Capella

EIP-3855: PUSH0 — опкод для числа, которое нужно всем

Положить константу ноль на стек — одна из самых частых операций в коде EVM, и до 2023 года для неё не было отдельной инструкции. Компиляторы либо тратили лишний байт на PUSH1 0, либо прибегали к хаку. PUSH0 — однобайтовый опкод, который делает ровно одно дело, а поскольку ноль встречается повсюду, эта крошечная экономия накапливается в каждом контракте.

Обновлено 24 июн. 2026 г. · 7 мин
Предполагается
  • стек и опкоды EVM
  • газ и размер кода

Shanghai, апрель 2023 года. Не каждый значимый апгрейд грандиозен; некоторые просто устраняют мелкий порез, который годами чувствовал каждый контракт. Вот этот порез: положить число ноль на стек — одна из самых частых операций во всём коде EVM: инициализация переменных, сравнения, обнуление памяти, шаблонный код вокруг каждой функции, — а у EVM не было для этого опкода. Есть опкоды, чтобы протолкнуть любую константу от 1 до 32 байт, но не аккуратный опкод для самой используемой константы из всех. EIP-3855 добавляет PUSH0. Это едва ли не самый маленький EIP из возможных, и хороший урок того, почему «маленькое × повсюду» стоит хардфорка.

1 шаг Нет опкода для нуля

Проблема: два плохих способа протолкнуть ноль

Чтобы положить ноль на стек, у кода EVM было два варианта, и ни один не был хорош. Очевидный — PUSH1 0x00, но это два байта (опкод PUSH1 плюс его непосредственный операнд 0x00) и стоит 3 газа — тратится байт драгоценного кода контракта лишь на то, чтобы закодировать константу, которую машина могла бы произвести бесплатно. Поэтому компиляторы схитрили и злоупотребляли трюком с RETURNDATASIZE (RETURNDATASIZE trick) Использование опкода RETURNDATASIZE (1 байт, 2 газа), чтобы протолкнуть ноль, эксплуатируя тот факт, что returndata пусто (размер 0) в тех местах, где компиляторы это знали заранее. Меньше, чем PUSH1 0, но это хак, зависящий от контекста, а не от намерения. : однобайтовым опкодом, который в нужном контексте возвращает 0. Он экономит байт, но это хак — вы читаете значение, которое означает «размер данных возврата последнего вызова», полагаясь на то, что оно равно нулю, вместо того чтобы сказать, что вы имеете в виду.

push 0 — везде, но опкода не было PUSH1 0x00 2 bytes · 3 gas лишний байт кода RETURNDATASIZE 1 byte · 2 gas хак: он просто оказывается 0 оба оставляют 0 в стеке — но ни один не годится как дешёвый 'push zero' а 0 кладут на стек постоянно, так что потери — повсюду
Два способа положить ноль на стек, и оба плохи: PUSH1 0x00 тратит два байта и 3 газа, а хак с RETURNDATASIZE — один байт, но полагается на то, что returndata случайно пусто. Оба оставляют 0, но ни один не является честным «проталкиванием нуля» — а ноль проталкивается постоянно.

→ Шаг 2: дать нулю собственную инструкцию.

2 шаг Добавляем PUSH0

PUSH0: один байт, одна задача

PUSH0 Опкод 0x5F, добавленный в Shanghai. Он проталкивает константу 0 на стек. Один байт кода, 2 газа (базовый тариф), и он говорит ровно то, что делает. (опкод 0x5F) делает ровно одно: проталкивает значение 0. Это один байт кода стоимостью 2 газа — меньше и дешевле, чем PUSH1 0, и, в отличие от трюка с RETURNDATASIZE, он честен насчёт своего намерения, так что его нельзя сломать изменением контекста. Он аккуратно занимает своё место в пространстве опкодов прямо перед PUSH1 (0x60), завершая семейство «протолкнуть константу» случаем нулевой длины.

добавить отдельный опкод: PUSH0 (0x5F) PUSH0 0x5F 0 стек 1 байт кода · 2 газа · кладёт константу 0 меньше и дешевле, чем PUSH1 0, — и не нужен трюк
PUSH0 (0x5F) проталкивает константу 0 одним байтом за 2 газа. Он меньше и дешевле, чем PUSH1 0x00, и означает то, что говорит, — никакой зависимости от того, что returndata пусто.

→ Шаг 3: умножаем на «повсюду».

3 шаг Маленькое × повсюду

Итог: байт, сэкономленный миллионы раз

Один байт кода — это ничто; байт, сэкономленный на каждом проталкивании нуля в каждом контракте, — это много. Поскольку ноль проталкивается постоянно, замена PUSH1 0 на PUSH0 заметно уменьшает байткод контрактов — а размер кода является главной статьёй стоимости развёртывания контракта, к тому же ограниченной 24 КБ, так что меньший код означает более дешёвое развёртывание и больше запаса для сложных контрактов. Исполнение тоже становится чуть дешевле, а компиляторы могут отказаться от хака с RETURNDATASIZE в пользу того, что действительно говорит «протолкни ноль».

нулей много — экономия накапливается до PUSH1 0 · PUSH1 0 · … после PUSH0 · PUSH0 · … код меньше → дешевле деплой · больше места под лимитом 24KB и хак с RETURNDATASIZE наконец можно списать
Замена PUSH1 0 на PUSH0 по всему контракту уменьшает его байткод — дешевле развернуть и больше места под лимитом размера кода в 24 КБ. Поскольку ноль проталкивается повсюду, однобайтовая экономия накапливается по каждому контракту в сети.
04 Глубже Куда двигаться дальше