[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-1-kak-evm-vypolnyaet-kod-opkody-stek-gaz":3},{"article":4,"author":54},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":7,"meta_description":16,"focus_keyword":17,"og_image":18,"canonical_url":18,"robots_meta":19,"created_at":15,"updated_at":15,"tags":20,"category_name":34,"related_articles":35},"d0000000-0000-0000-0000-000000000201","a0000000-0000-0000-0000-000000000012","Deep EVM #1: Как EVM выполняет ваш код — опкоды, стек и газ","deep-evm-1-kak-evm-vypolnyaet-kod-opkody-stek-gaz","Подробное техническое руководство по работе Ethereum Virtual Machine: как опкоды манипулируют стеком, как работает учёт газа и что на самом деле происходит, когда ваша транзакция выполняется.","## EVM — это стековая машина\n\nEthereum Virtual Machine — это не процессор x86 в вашем ноутбуке. У неё нет регистров. Вместо этого EVM является **стековой машиной** — каждое вычисление помещает данные в стек или извлекает из стека глубиной 1024 элемента, где каждый элемент — 256-битное (32-байтное) слово.\n\nКогда вы вызываете смарт-контракт, EVM получает байткод контракта — плоскую последовательность однобайтных опкодов — и начинает выполнение с байта 0. Здесь нет таблицы функций, нет ELF-заголовка, нет этапа линковки. Байткод и есть программа.\n\n```\n\u002F\u002F Solidity:\n\u002F\u002F uint256 result = 2 + 3;\n\n\u002F\u002F Компилируется в байткод:\n\u002F\u002F PUSH1 0x02  PUSH1 0x03  ADD\n\n\u002F\u002F Трассировка стека:\n\u002F\u002F []           -> PUSH1 0x02 -> [2]\n\u002F\u002F [2]          -> PUSH1 0x03 -> [2, 3]\n\u002F\u002F [2, 3]       -> ADD        -> [5]\n```\n\nКаждый опкод извлекает свои операнды с вершины стека и помещает результат обратно. Опкод ADD извлекает два значения, складывает их и помещает сумму. Это принципиально отличается от регистровых архитектур, где вы указываете регистры-источники и регистр-приёмник.\n\n## Категории опкодов\n\nEVM определяет примерно 140 опкодов, сгруппированных по функциональным категориям:\n\n### Арифметика и сравнение\n- **ADD, SUB, MUL, DIV, MOD** — базовая 256-битная целочисленная арифметика. Все стоят 3 газа (уровень G_verylow).\n- **SDIV, SMOD** — деление и модуль со знаком в дополнительном коде.\n- **ADDMOD, MULMOD** — модулярная арифметика: `(a + b) % N` и `(a * b) % N` одним опкодом. Критически важны для операций на эллиптических кривых, стоят 8 газа.\n- **EXP** — возведение в степень. Стоит 10 газа + 50 за каждый байт экспоненты, что делает его одним из самых дорогих арифметических опкодов.\n- **LT, GT, SLT, SGT, EQ, ISZERO** — опкоды сравнения, помещающие 1 (истина) или 0 (ложь) в стек.\n\n### Побитовые операции\n- **AND, OR, XOR, NOT** — побитовая логика, 3 газа каждая.\n- **SHL, SHR, SAR** — сдвиг влево, логический сдвиг вправо, арифметический сдвиг вправо (добавлены в Constantinople, EIP-145). До них сдвиги требовали MUL\u002FDIV на степени двойки.\n- **BYTE** — извлечение одного байта из 32-байтного слова. `BYTE(0, x)` возвращает старший байт.\n\n### Манипуляции со стеком\n- **POP** — удалить верхний элемент.\n- **PUSH1 — PUSH32** — поместить от 1 до 32 байт непосредственных данных в стек. PUSH1 — самый частый опкод в развёрнутом байткоде.\n- **DUP1 — DUP16** — дублировать N-й элемент стека на вершину.\n- **SWAP1 — SWAP16** — поменять местами верхний элемент с N-м элементом ниже.\n\n### Информация об окружении и блоке\n- **CALLER** (msg.sender), **CALLVALUE** (msg.value), **CALLDATALOAD**, **CALLDATASIZE**, **CALLDATACOPY** — доступ к контексту транзакции.\n- **NUMBER**, **TIMESTAMP**, **BASEFEE**, **CHAINID** — информация уровня блока.\n- **BALANCE**, **EXTCODESIZE**, **EXTCODECOPY** — запрос данных других аккаунтов.\n\n## Расписание газа\n\nКаждый опкод имеет стоимость в газе. Газ выполняет две функции: предотвращает бесконечные циклы (проблема останова) и справедливо оценивает вычислительные ресурсы.\n\nСтоимость газа распределяется по уровням:\n\n| Уровень | Газ | Примеры |\n|---------|-----|---------|\n| Нулевой | 0 | STOP, RETURN, REVERT |\n| Базовый | 2 | ADDRESS, ORIGIN, CALLER |\n| Очень низкий | 3 | ADD, SUB, LT, GT, AND, OR, POP |\n| Низкий | 5 | MUL, DIV, MOD |\n| Средний | 8 | ADDMOD, MULMOD, JUMP |\n| Высокий | 10 | JUMPI |\n| Специальный | разный | SLOAD, SSTORE, CALL, CREATE |\n\nСамые дорогие опкоды — те, что взаимодействуют с состоянием:\n\n```\n\u002F\u002F Стоимость газа для доступа к состоянию (после EIP-2929):\n\u002F\u002F SLOAD (холодный):  2100 газа\n\u002F\u002F SLOAD (тёплый):     100 газа\n\u002F\u002F SSTORE (холодный, 0->ненулевое): 22100 газа\n\u002F\u002F SSTORE (тёплый):    100 газа (+ 20000 если 0->ненулевое)\n\u002F\u002F CALL (холодный):   2600 газа\n\u002F\u002F CALL (тёплый):      100 газа\n\u002F\u002F BALANCE (холодный): 2600 газа\n\u002F\u002F BALANCE (тёплый):   100 газа\n```\n\n## Холодный и тёплый доступ (EIP-2929)\n\nEIP-2929 (обновление Berlin, апрель 2021) ввёл концепцию **списка доступа** — набора адресов и слотов хранилища для каждой транзакции, к которым уже обращались.\n\nПри первом обращении к слоту хранилища или внешнему адресу в транзакции он считается «холодным» и стоит дополнительного газа. Повторные обращения «тёплые» и дешёвые. Вот почему порядок чтения слотов хранилища важен для оптимизации газа.\n\n```solidity\n\u002F\u002F В Solidity этот паттерн дорогой:\nfunction bad() external view returns (uint256) {\n    \u002F\u002F Первое чтение слота: 2100 газа (холодный)\n    uint256 a = myStorage;\n    \u002F\u002F ... логика ...\n    \u002F\u002F Второе чтение: 100 газа (тёплый)\n    uint256 b = myStorage;\n    return a + b;\n}\n\n\u002F\u002F Кэширование в памяти:\nfunction good() external view returns (uint256) {\n    uint256 cached = myStorage; \u002F\u002F 2100 газа (холодный), только один раз\n    return cached + cached;     \u002F\u002F 6 газа (ADD + DUP)\n}\n```\n\n## Поток выполнения: что происходит в транзакции\n\nКогда вы отправляете транзакцию, вызывающую контракт, вот полная последовательность выполнения:\n\n1. **Валидация транзакции** — проверка nonce, баланс >= value + gas * gasPrice, верификация подписи.\n2. **Вычет внутреннего газа** — 21000 газа за саму транзакцию, плюс 16 газа за каждый ненулевой байт calldata и 4 за нулевой.\n3. **Настройка контекста** — EVM создаёт контекст выполнения: код, calldata, вызывающий, значение, оставшийся газ.\n4. **Счётчик программы начинается с 0** — EVM читает опкод на позиции 0 и выполняет его.\n5. **Последовательное выполнение** — каждый опкод выполняется, газ вычитается. JUMP и JUMPI позволяют нелинейный поток управления, но только к позициям, отмеченным JUMPDEST.\n6. **Завершение** — выполнение завершается через STOP (успех, нет данных возврата), RETURN (успех, с данными возврата), REVERT (ошибка, состояние откатывается) или исчерпание газа.\n7. **Фиксация или откат состояния** — при успехе все изменения состояния фиксируются. При откате все изменения в данном контексте вызова отменяются.\n\n## Счётчик программы и JUMP\n\nСчётчик программы (PC) — это неявный регистр, отслеживающий текущую позицию в байткоде. Большинство опкодов продвигают PC на 1 (или на 1 + N для PUSH-опкодов). Два опкода изменяют PC напрямую:\n\n- **JUMP** — извлекает адрес назначения из стека, устанавливает PC на это значение. Адрес назначения должен содержать опкод JUMPDEST, иначе транзакция откатится.\n- **JUMPI** — условный переход. Извлекает адрес назначения и условие. Если условие ненулевое — переход; иначе продолжение последовательного выполнения.\n\nИменно так EVM реализует if\u002Felse, циклы и диспетчеризацию функций. Компилятор Solidity генерирует селектор функций, который загружает первые 4 байта calldata, сравнивает с известными сигнатурами и выполняет JUMPI к соответствующему блоку кода.\n\n```\n\u002F\u002F Диспетчеризация функций (упрощённый байткод):\n\u002F\u002F CALLDATALOAD(0) -> SHR(224) -> function_selector\n\u002F\u002F DUP1 PUSH4 0xa9059cbb EQ PUSH2 0x00a4 JUMPI  \u002F\u002F transfer(address,uint256)\n\u002F\u002F DUP1 PUSH4 0x70a08231 EQ PUSH2 0x00d2 JUMPI  \u002F\u002F balanceOf(address)\n\u002F\u002F PUSH1 0x00 DUP1 REVERT                         \u002F\u002F fallback: revert\n```\n\n## Подвызовы: CALL, STATICCALL, DELEGATECALL\n\nКонтракты могут вызывать другие контракты с помощью трёх опкодов вызова:\n\n- **CALL** — стандартный вызов. Создаёт новый контекст выполнения со своим стеком и памятью. Вызываемый работает независимо; если он откатится, откатываются только его изменения.\n- **STATICCALL** — вызов только для чтения (EIP-214). Любой опкод, изменяющий состояние (SSTORE, CREATE, LOG, SELFDESTRUCT) внутри вызываемого, вызывает немедленный откат.\n- **DELEGATECALL** — выполняет код вызываемого, но в контексте хранилища вызывающего. msg.sender и msg.value сохраняются из исходного вызова. Именно так работают прокси-паттерны и библиотеки.\n\nКаждый опкод вызова принимает 7 аргументов стека: gas, address, value (кроме STATICCALL\u002FDELEGATECALL), argsOffset, argsLength, retOffset, retLength. Стоимость газа: 100 (тёплый) или 2600 (холодный) плюс надбавка за перевод значения.\n\n## Возвраты газа и их ограничения\n\nИсторически очистка слота хранилища с ненулевого на нулевое давала возврат газа — стимулируя очистку состояния. После EIP-3529 (обновление London) максимальный возврат ограничен 20% от общего использованного газа (ранее 50%). Это уничтожило арбитраж газовых токенов (CHI, GST2), которые эксплуатировали возвраты для прибыли.\n\nОставшиеся источники возврата:\n- SSTORE: установка ненулевого слота обратно в ноль возвращает 4800 газа.\n- SELFDESTRUCT: удалён как источник возврата в EIP-3529.\n\n## Практические следствия для MEV\n\nЕсли вы строите MEV-ботов, понимание EVM на уровне опкодов — не опция, а конкурентное требование. Каждая единица газа, сэкономленная при выполнении вашего бота — это маржа прибыли. Ключевые выводы:\n\n- **Симулируйте перед отправкой** — используйте `eth_call` или локальный EVM (revm, EVMONE) для трассировки выполнения и точного расчёта стоимости газа до отправки бандла билдеру.\n- **Минимизируйте холодный доступ** — предварительно прогревайте слоты хранилища через списки доступа (EIP-2930).\n- **Используйте STATICCALL для чтения** — немного дешевле и гарантирует отсутствие мутации состояния.\n- **Знайте стоимость опкодов** — один неудачно расположенный SLOAD может стоить 2100 газа; в конкурентной среде MEV это разница между прибылью и убытком.\n\n## Заключение\n\nEVM элегантна в своей простоте: стековая машина с 256-битными словами, плоский формат байткода и система учёта газа, оценивающая каждую операцию. Понимание этого фундамента — опкоды, стек и газ — является предпосылкой для всего остального в этой серии: разметки памяти, оптимизации хранилища, примитивов безопасности и, наконец, написания чистого Yul.\n\nВ следующей статье мы рассмотрим четыре области данных EVM: стек, память, хранилище и calldata — и почему выбор правильной определяет, стоит ли ваш контракт $0.50 или $50 за выполнение.","\u003Ch2 id=\"evm\">EVM — это стековая машина\u003C\u002Fh2>\n\u003Cp>Ethereum Virtual Machine — это не процессор x86 в вашем ноутбуке. У неё нет регистров. Вместо этого EVM является \u003Cstrong>стековой машиной\u003C\u002Fstrong> — каждое вычисление помещает данные в стек или извлекает из стека глубиной 1024 элемента, где каждый элемент — 256-битное (32-байтное) слово.\u003C\u002Fp>\n\u003Cp>Когда вы вызываете смарт-контракт, EVM получает байткод контракта — плоскую последовательность однобайтных опкодов — и начинает выполнение с байта 0. Здесь нет таблицы функций, нет ELF-заголовка, нет этапа линковки. Байткод и есть программа.\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F Solidity:\n\u002F\u002F uint256 result = 2 + 3;\n\n\u002F\u002F Компилируется в байткод:\n\u002F\u002F PUSH1 0x02  PUSH1 0x03  ADD\n\n\u002F\u002F Трассировка стека:\n\u002F\u002F []           -&gt; PUSH1 0x02 -&gt; [2]\n\u002F\u002F [2]          -&gt; PUSH1 0x03 -&gt; [2, 3]\n\u002F\u002F [2, 3]       -&gt; ADD        -&gt; [5]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Каждый опкод извлекает свои операнды с вершины стека и помещает результат обратно. Опкод ADD извлекает два значения, складывает их и помещает сумму. Это принципиально отличается от регистровых архитектур, где вы указываете регистры-источники и регистр-приёмник.\u003C\u002Fp>\n\u003Ch2 id=\"\">Категории опкодов\u003C\u002Fh2>\n\u003Cp>EVM определяет примерно 140 опкодов, сгруппированных по функциональным категориям:\u003C\u002Fp>\n\u003Ch3>Арифметика и сравнение\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>ADD, SUB, MUL, DIV, MOD\u003C\u002Fstrong> — базовая 256-битная целочисленная арифметика. Все стоят 3 газа (уровень G_verylow).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>SDIV, SMOD\u003C\u002Fstrong> — деление и модуль со знаком в дополнительном коде.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>ADDMOD, MULMOD\u003C\u002Fstrong> — модулярная арифметика: \u003Ccode>(a + b) % N\u003C\u002Fcode> и \u003Ccode>(a * b) % N\u003C\u002Fcode> одним опкодом. Критически важны для операций на эллиптических кривых, стоят 8 газа.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>EXP\u003C\u002Fstrong> — возведение в степень. Стоит 10 газа + 50 за каждый байт экспоненты, что делает его одним из самых дорогих арифметических опкодов.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>LT, GT, SLT, SGT, EQ, ISZERO\u003C\u002Fstrong> — опкоды сравнения, помещающие 1 (истина) или 0 (ложь) в стек.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Побитовые операции\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>AND, OR, XOR, NOT\u003C\u002Fstrong> — побитовая логика, 3 газа каждая.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>SHL, SHR, SAR\u003C\u002Fstrong> — сдвиг влево, логический сдвиг вправо, арифметический сдвиг вправо (добавлены в Constantinople, EIP-145). До них сдвиги требовали MUL\u002FDIV на степени двойки.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>BYTE\u003C\u002Fstrong> — извлечение одного байта из 32-байтного слова. \u003Ccode>BYTE(0, x)\u003C\u002Fcode> возвращает старший байт.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Манипуляции со стеком\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>POP\u003C\u002Fstrong> — удалить верхний элемент.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>PUSH1 — PUSH32\u003C\u002Fstrong> — поместить от 1 до 32 байт непосредственных данных в стек. PUSH1 — самый частый опкод в развёрнутом байткоде.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>DUP1 — DUP16\u003C\u002Fstrong> — дублировать N-й элемент стека на вершину.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>SWAP1 — SWAP16\u003C\u002Fstrong> — поменять местами верхний элемент с N-м элементом ниже.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Информация об окружении и блоке\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>CALLER\u003C\u002Fstrong> (msg.sender), \u003Cstrong>CALLVALUE\u003C\u002Fstrong> (msg.value), \u003Cstrong>CALLDATALOAD\u003C\u002Fstrong>, \u003Cstrong>CALLDATASIZE\u003C\u002Fstrong>, \u003Cstrong>CALLDATACOPY\u003C\u002Fstrong> — доступ к контексту транзакции.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>NUMBER\u003C\u002Fstrong>, \u003Cstrong>TIMESTAMP\u003C\u002Fstrong>, \u003Cstrong>BASEFEE\u003C\u002Fstrong>, \u003Cstrong>CHAINID\u003C\u002Fstrong> — информация уровня блока.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>BALANCE\u003C\u002Fstrong>, \u003Cstrong>EXTCODESIZE\u003C\u002Fstrong>, \u003Cstrong>EXTCODECOPY\u003C\u002Fstrong> — запрос данных других аккаунтов.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"\">Расписание газа\u003C\u002Fh2>\n\u003Cp>Каждый опкод имеет стоимость в газе. Газ выполняет две функции: предотвращает бесконечные циклы (проблема останова) и справедливо оценивает вычислительные ресурсы.\u003C\u002Fp>\n\u003Cp>Стоимость газа распределяется по уровням:\u003C\u002Fp>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Уровень\u003C\u002Fth>\u003Cth>Газ\u003C\u002Fth>\u003Cth>Примеры\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Нулевой\u003C\u002Ftd>\u003Ctd>0\u003C\u002Ftd>\u003Ctd>STOP, RETURN, REVERT\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Базовый\u003C\u002Ftd>\u003Ctd>2\u003C\u002Ftd>\u003Ctd>ADDRESS, ORIGIN, CALLER\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Очень низкий\u003C\u002Ftd>\u003Ctd>3\u003C\u002Ftd>\u003Ctd>ADD, SUB, LT, GT, AND, OR, POP\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Низкий\u003C\u002Ftd>\u003Ctd>5\u003C\u002Ftd>\u003Ctd>MUL, DIV, MOD\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Средний\u003C\u002Ftd>\u003Ctd>8\u003C\u002Ftd>\u003Ctd>ADDMOD, MULMOD, JUMP\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Высокий\u003C\u002Ftd>\u003Ctd>10\u003C\u002Ftd>\u003Ctd>JUMPI\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Специальный\u003C\u002Ftd>\u003Ctd>разный\u003C\u002Ftd>\u003Ctd>SLOAD, SSTORE, CALL, CREATE\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Самые дорогие опкоды — те, что взаимодействуют с состоянием:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F Стоимость газа для доступа к состоянию (после EIP-2929):\n\u002F\u002F SLOAD (холодный):  2100 газа\n\u002F\u002F SLOAD (тёплый):     100 газа\n\u002F\u002F SSTORE (холодный, 0-&gt;ненулевое): 22100 газа\n\u002F\u002F SSTORE (тёплый):    100 газа (+ 20000 если 0-&gt;ненулевое)\n\u002F\u002F CALL (холодный):   2600 газа\n\u002F\u002F CALL (тёплый):      100 газа\n\u002F\u002F BALANCE (холодный): 2600 газа\n\u002F\u002F BALANCE (тёплый):   100 газа\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"eip-2929\">Холодный и тёплый доступ (EIP-2929)\u003C\u002Fh2>\n\u003Cp>EIP-2929 (обновление Berlin, апрель 2021) ввёл концепцию \u003Cstrong>списка доступа\u003C\u002Fstrong> — набора адресов и слотов хранилища для каждой транзакции, к которым уже обращались.\u003C\u002Fp>\n\u003Cp>При первом обращении к слоту хранилища или внешнему адресу в транзакции он считается «холодным» и стоит дополнительного газа. Повторные обращения «тёплые» и дешёвые. Вот почему порядок чтения слотов хранилища важен для оптимизации газа.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F В Solidity этот паттерн дорогой:\nfunction bad() external view returns (uint256) {\n    \u002F\u002F Первое чтение слота: 2100 газа (холодный)\n    uint256 a = myStorage;\n    \u002F\u002F ... логика ...\n    \u002F\u002F Второе чтение: 100 газа (тёплый)\n    uint256 b = myStorage;\n    return a + b;\n}\n\n\u002F\u002F Кэширование в памяти:\nfunction good() external view returns (uint256) {\n    uint256 cached = myStorage; \u002F\u002F 2100 газа (холодный), только один раз\n    return cached + cached;     \u002F\u002F 6 газа (ADD + DUP)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Поток выполнения: что происходит в транзакции\u003C\u002Fh2>\n\u003Cp>Когда вы отправляете транзакцию, вызывающую контракт, вот полная последовательность выполнения:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Валидация транзакции\u003C\u002Fstrong> — проверка nonce, баланс &gt;= value + gas * gasPrice, верификация подписи.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Вычет внутреннего газа\u003C\u002Fstrong> — 21000 газа за саму транзакцию, плюс 16 газа за каждый ненулевой байт calldata и 4 за нулевой.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Настройка контекста\u003C\u002Fstrong> — EVM создаёт контекст выполнения: код, calldata, вызывающий, значение, оставшийся газ.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Счётчик программы начинается с 0\u003C\u002Fstrong> — EVM читает опкод на позиции 0 и выполняет его.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Последовательное выполнение\u003C\u002Fstrong> — каждый опкод выполняется, газ вычитается. JUMP и JUMPI позволяют нелинейный поток управления, но только к позициям, отмеченным JUMPDEST.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Завершение\u003C\u002Fstrong> — выполнение завершается через STOP (успех, нет данных возврата), RETURN (успех, с данными возврата), REVERT (ошибка, состояние откатывается) или исчерпание газа.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Фиксация или откат состояния\u003C\u002Fstrong> — при успехе все изменения состояния фиксируются. При откате все изменения в данном контексте вызова отменяются.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"jump\">Счётчик программы и JUMP\u003C\u002Fh2>\n\u003Cp>Счётчик программы (PC) — это неявный регистр, отслеживающий текущую позицию в байткоде. Большинство опкодов продвигают PC на 1 (или на 1 + N для PUSH-опкодов). Два опкода изменяют PC напрямую:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>JUMP\u003C\u002Fstrong> — извлекает адрес назначения из стека, устанавливает PC на это значение. Адрес назначения должен содержать опкод JUMPDEST, иначе транзакция откатится.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>JUMPI\u003C\u002Fstrong> — условный переход. Извлекает адрес назначения и условие. Если условие ненулевое — переход; иначе продолжение последовательного выполнения.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Именно так EVM реализует if\u002Felse, циклы и диспетчеризацию функций. Компилятор Solidity генерирует селектор функций, который загружает первые 4 байта calldata, сравнивает с известными сигнатурами и выполняет JUMPI к соответствующему блоку кода.\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F Диспетчеризация функций (упрощённый байткод):\n\u002F\u002F CALLDATALOAD(0) -&gt; SHR(224) -&gt; function_selector\n\u002F\u002F DUP1 PUSH4 0xa9059cbb EQ PUSH2 0x00a4 JUMPI  \u002F\u002F transfer(address,uint256)\n\u002F\u002F DUP1 PUSH4 0x70a08231 EQ PUSH2 0x00d2 JUMPI  \u002F\u002F balanceOf(address)\n\u002F\u002F PUSH1 0x00 DUP1 REVERT                         \u002F\u002F fallback: revert\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"call-staticcall-delegatecall\">Подвызовы: CALL, STATICCALL, DELEGATECALL\u003C\u002Fh2>\n\u003Cp>Контракты могут вызывать другие контракты с помощью трёх опкодов вызова:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>CALL\u003C\u002Fstrong> — стандартный вызов. Создаёт новый контекст выполнения со своим стеком и памятью. Вызываемый работает независимо; если он откатится, откатываются только его изменения.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>STATICCALL\u003C\u002Fstrong> — вызов только для чтения (EIP-214). Любой опкод, изменяющий состояние (SSTORE, CREATE, LOG, SELFDESTRUCT) внутри вызываемого, вызывает немедленный откат.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>DELEGATECALL\u003C\u002Fstrong> — выполняет код вызываемого, но в контексте хранилища вызывающего. msg.sender и msg.value сохраняются из исходного вызова. Именно так работают прокси-паттерны и библиотеки.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Каждый опкод вызова принимает 7 аргументов стека: gas, address, value (кроме STATICCALL\u002FDELEGATECALL), argsOffset, argsLength, retOffset, retLength. Стоимость газа: 100 (тёплый) или 2600 (холодный) плюс надбавка за перевод значения.\u003C\u002Fp>\n\u003Ch2 id=\"\">Возвраты газа и их ограничения\u003C\u002Fh2>\n\u003Cp>Исторически очистка слота хранилища с ненулевого на нулевое давала возврат газа — стимулируя очистку состояния. После EIP-3529 (обновление London) максимальный возврат ограничен 20% от общего использованного газа (ранее 50%). Это уничтожило арбитраж газовых токенов (CHI, GST2), которые эксплуатировали возвраты для прибыли.\u003C\u002Fp>\n\u003Cp>Оставшиеся источники возврата:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>SSTORE: установка ненулевого слота обратно в ноль возвращает 4800 газа.\u003C\u002Fli>\n\u003Cli>SELFDESTRUCT: удалён как источник возврата в EIP-3529.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"mev\">Практические следствия для MEV\u003C\u002Fh2>\n\u003Cp>Если вы строите MEV-ботов, понимание EVM на уровне опкодов — не опция, а конкурентное требование. Каждая единица газа, сэкономленная при выполнении вашего бота — это маржа прибыли. Ключевые выводы:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Симулируйте перед отправкой\u003C\u002Fstrong> — используйте \u003Ccode>eth_call\u003C\u002Fcode> или локальный EVM (revm, EVMONE) для трассировки выполнения и точного расчёта стоимости газа до отправки бандла билдеру.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Минимизируйте холодный доступ\u003C\u002Fstrong> — предварительно прогревайте слоты хранилища через списки доступа (EIP-2930).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Используйте STATICCALL для чтения\u003C\u002Fstrong> — немного дешевле и гарантирует отсутствие мутации состояния.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Знайте стоимость опкодов\u003C\u002Fstrong> — один неудачно расположенный SLOAD может стоить 2100 газа; в конкурентной среде MEV это разница между прибылью и убытком.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"\">Заключение\u003C\u002Fh2>\n\u003Cp>EVM элегантна в своей простоте: стековая машина с 256-битными словами, плоский формат байткода и система учёта газа, оценивающая каждую операцию. Понимание этого фундамента — опкоды, стек и газ — является предпосылкой для всего остального в этой серии: разметки памяти, оптимизации хранилища, примитивов безопасности и, наконец, написания чистого Yul.\u003C\u002Fp>\n\u003Cp>В следующей статье мы рассмотрим четыре области данных EVM: стек, память, хранилище и calldata — и почему выбор правильной определяет, стоит ли ваш контракт $0.50 или $50 за выполнение.\u003C\u002Fp>\n","ru","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:23.362332Z","Подробное руководство по работе Ethereum Virtual Machine: опкоды, операции со стеком, учёт газа, холодный и тёплый доступ.","EVM опкоды",null,"index, follow",[21,26,30],{"id":22,"name":23,"slug":24,"created_at":25},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":27,"name":28,"slug":29,"created_at":25},"c0000000-0000-0000-0000-000000000020","Gas Optimization","gas-optimization",{"id":31,"name":32,"slug":33,"created_at":25},"c0000000-0000-0000-0000-000000000014","Solidity","solidity","Блокчейн",[36,42,48],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":34,"published_at":41},"de000000-0000-0000-0000-000000000013","Уровень интероперабельности Ethereum: как 55+ L2 становятся одной сетью","uroven-interoperabelnosti-ethereum-kak-55-l2-stanovyatsya-odnoj-setyu","У Ethereum 55+ роллапов Layer 2, фрагментирующих ликвидность и пользовательский опыт. Уровень интероперабельности Ethereum — объединяющий кросс-роллап-мессаджинг, общие секвенсоры и based-роллапы — призван объединить их в единую компонуемую сеть.","2026-03-28T10:44:36.068675Z",{"id":43,"title":44,"slug":45,"excerpt":46,"locale":12,"category_name":34,"published_at":47},"de000000-0000-0000-0000-000000000012","ZK-доказательства за пределами роллапов: верифицируемый AI-инференс на Ethereum","zk-dokazatelstva-za-predelami-rollapov-verificiruemyj-ai-inferens-ethereum","Доказательства с нулевым разглашением — это уже не только инструмент масштабирования. В 2026 году zkML обеспечивает верифицируемый AI-инференс в блокчейне, ZK-копроцессоры переносят тяжёлые вычисления оффчейн с ончейн-верификацией, а новые системы доказательств SP1 и Jolt делают это практичным.","2026-03-28T10:44:36.026310Z",{"id":49,"title":50,"slug":51,"excerpt":52,"locale":12,"category_name":34,"published_at":53},"dd000000-0000-0000-0000-000000000013","EIP-7702 на практике: создание потоков смарт-аккаунтов после Pectra","eip-7702-na-praktike-sozdanie-potokov-smart-akkauntov-posle-pectra","EIP-7702 позволяет любому EOA Ethereum временно действовать как смарт-контракт в рамках одной транзакции. Вот как реализовать пакетные транзакции, спонсирование газа и социальное восстановление с помощью нового примитива абстракции аккаунтов.","2026-03-28T10:44:35.357227Z",{"id":13,"name":55,"slug":56,"bio":57,"photo_url":18,"linkedin":18,"role":58,"created_at":59,"updated_at":59},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]