---
title: "Понимание спецификаций EVM в желтой книге"
description: "Понимание части желтой книги, формальных спецификаций Эфириума, которая объясняет виртуальную машину Эфириума (EVM)."
author: "qbzzt"
tags: ["evm"]
skill: intermediate
breadcrumb: "EVM в желтой книге"
lang: ru
published: 2022-05-15
---

[Желтая книга](https://ethereum.github.io/yellowpaper/paper.pdf) — это формальная спецификация Эфириума. За исключением поправок, внесенных в ходе [процесса EIP](/eips/), она содержит точное описание того, как все работает. Она написана как математическая статья, включающая терминологию, которая может быть незнакома программистам. В этой статье вы узнаете, как ее читать, а следовательно, и другие связанные с ней математические статьи.

## Какая желтая книга? {#which-yellow-paper}

Как и почти все остальное в Эфириуме, желтая книга со временем развивается. Чтобы иметь возможность ссылаться на конкретную версию, я загрузил [текущую версию на момент написания](https://ethereum.github.io/yellowpaper/paper.pdf). Номера разделов, страниц и уравнений, которые я использую, будут относиться к этой версии. Рекомендуется держать ее открытой в другом окне во время чтения этого документа.

### Почему EVM? {#why-the-evm}

Оригинальная желтая книга была написана в самом начале разработки Эфириума. Она описывает оригинальный механизм консенсуса на основе доказательства выполнения работы (PoW), который изначально использовался для защиты сети. Однако в сентябре 2022 года Эфириум отказался от доказательства выполнения работы (PoW) и начал использовать консенсус на основе доказательства доли владения. В этом руководстве основное внимание будет уделено частям желтой книги, определяющим виртуальную машину Эфириума (EVM). EVM осталась неизменной после перехода на доказательство доли владения (за исключением возвращаемого значения кода операции DIFFICULTY).

## 9 Модель исполнения

Этот раздел (стр. 14-16) включает большую часть определения EVM.

Термин _состояние системы_ (system state) включает в себя все, что нужно знать о системе для ее запуска. В типичном компьютере это означает память, содержимое регистров и т. д.

[Машина Тьюринга](https://en.wikipedia.org/wiki/Turing_machine) — это вычислительная модель. По сути, это упрощенная версия компьютера, которая, как доказано, обладает той же способностью выполнять вычисления, что и обычный компьютер (все, что может вычислить компьютер, может вычислить и машина Тьюринга, и наоборот). Эта модель упрощает доказательство различных теорем о том, что является вычислимым, а что нет.

Термин [полный по Тьюрингу](https://en.wikipedia.org/wiki/Turing_completeness) (Turing-complete) означает компьютер, который может выполнять те же вычисления, что и машина Тьюринга. Машины Тьюринга могут попадать в бесконечные циклы, а EVM — нет, потому что у нее закончится газ, поэтому она является лишь квазиполной по Тьюрингу.
## 9.1 Основы {#91-basics}

В этом разделе изложены основы EVM и ее сравнение с другими вычислительными моделями.

[Стековая машина](https://en.wikipedia.org/wiki/Stack_machine) — это компьютер, который хранит промежуточные данные не в регистрах, а в [**стеке**](<https://en.wikipedia.org/wiki/Stack_(abstract_data_type)>). Это предпочтительная архитектура для виртуальных машин, поскольку ее легко реализовать, а значит, ошибки и уязвимости в системе безопасности возникают гораздо реже. Память в стеке разделена на 256-битные слова. Это было выбрано потому, что это удобно для основных криптографических операций Эфириума, таких как хеширование Keccak-256 и вычисления на эллиптической кривой. Максимальный размер стека составляет 1024 элемента (1024 x 256 бит). Когда выполняются коды операций, они обычно получают свои параметры из стека. Существуют коды операций специально для реорганизации элементов в стеке, такие как `POP` (удаляет элемент с вершины стека), `DUP_N` (дублирует N-й элемент в стеке) и т. д.

EVM также имеет энергозависимое пространство, называемое **памятью** (memory), которое используется для хранения данных во время выполнения. Эта память организована в виде 32-байтовых слов. Все ячейки памяти инициализируются нулем. Если вы выполните этот код на [Yul](https://docs.soliditylang.org/en/latest/yul.html) для добавления слова в память, он заполнит 32 байта памяти, дополнив пустое пространство в слове нулями, т. е. он создаст одно слово — с нулями в позициях 0-29, 0x60 в 30 и 0xA7 в 31.

```yul
mstore(0, 0x60A7)
```

`mstore` — это один из трех кодов операций, которые EVM предоставляет для взаимодействия с памятью: он загружает слово в память. Два других — это `mstore8`, который загружает один байт в память, и `mload`, который перемещает слово из памяти в стек.

EVM также имеет отдельную энергонезависимую модель **хранилища** (storage), которая поддерживается как часть состояния системы — эта память организована в виде массивов слов (в отличие от массивов байтов с адресацией по словам в стеке). В этом хранилище контракты хранят постоянные данные — контракт может взаимодействовать только со своим собственным хранилищем. Хранилище организовано в виде сопоставлений «ключ-значение».

Хотя это не упоминается в данном разделе желтой книги, также полезно знать, что существует четвертый тип памяти. **Данные вызова** (calldata) — это память только для чтения с байтовой адресацией, используемая для хранения значения, переданного с параметром `data` транзакции. EVM имеет специальные коды операций для управления `calldata`. `calldatasize` возвращает размер данных. `calldataload` загружает данные в стек. `calldatacopy` копирует данные в память.

Стандартная [архитектура фон Неймана](https://en.wikipedia.org/wiki/Von_Neumann_architecture) хранит код и данные в одной и той же памяти. EVM не следует этому стандарту из соображений безопасности — совместное использование энергозависимой памяти делает возможным изменение программного кода. Вместо этого код сохраняется в хранилище.

Существует только два случая, когда код выполняется из памяти:

- Когда контракт создает другой контракт (используя [`CREATE`](https://www.evm.codes/#f0) или [`CREATE2`](https://www.evm.codes/#f5)), код для конструктора контракта берется из памяти.
- Во время создания _любого_ контракта выполняется код конструктора, а затем возвращается код самого контракта, также из памяти.

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

## 9.2 Обзор комиссий {#92-fees-overview}

В этом разделе объясняется, как рассчитываются комиссии за газ. Существует три вида затрат:

### Стоимость кода операции

Базовая стоимость конкретного кода операции. Чтобы получить это значение, найдите группу стоимости кода операции в Приложении H (стр. 29, под уравнением (329)) и найдите группу стоимости в уравнении (326). Это даст вам функцию стоимости, которая в большинстве случаев использует параметры из Приложения G (стр. 28).

Например, код операции [`CALLDATACOPY`](https://www.evm.codes/#37) является членом группы _W<sub>copy</sub>_. Стоимость кода операции для этой группы составляет _G<sub>verylow</sub>+G<sub>copy</sub>×⌈μ<sub>s</sub>[2]÷32⌉_. Посмотрев на Приложение G, мы видим, что обе константы равны 3, что дает нам _3+3×⌈μ<sub>s</sub>[2]÷32⌉_.

Нам все еще нужно расшифровать выражение _⌈μ<sub>s</sub>[2]÷32⌉_. Внешняя часть, _⌈ \<value\> ⌉_ — это функция округления в большую сторону (ceiling), функция, которая для заданного значения возвращает наименьшее целое число, которое не меньше этого значения. Например, _⌈2.5⌉ = ⌈3⌉ = 3_. Внутренняя часть — это _μ<sub>s</sub>[2]÷32_. Посмотрев на раздел 3 (Условные обозначения) на стр. 3, _μ_ — это состояние машины. Состояние машины определено в разделе 9.4.1 на стр. 15. Согласно этому разделу, одним из параметров состояния машины является _s_ для стека. Собирая все вместе, кажется, что _μ<sub>s</sub>[2]_ — это позиция №2 в стеке. Посмотрев на [код операции](https://www.evm.codes/#37), позиция №2 в стеке — это размер данных в байтах. Посмотрев на другие коды операций в группе W<sub>copy</sub>, [`CODECOPY`](https://www.evm.codes/#39) и [`RETURNDATACOPY`](https://www.evm.codes/#3e), они также имеют размер данных в той же позиции. Таким образом, _⌈μ<sub>s</sub>[2]÷32⌉_ — это количество 32-байтовых слов, необходимых для хранения копируемых данных. Собирая все вместе, базовая стоимость [`CALLDATACOPY`](https://www.evm.codes/#37) составляет 3 газа плюс 3 за каждое слово копируемых данных.
### Стоимость выполнения {#running-cost}

Стоимость выполнения вызываемого нами кода.

- В случае [`CREATE`](https://www.evm.codes/#f0) и [`CREATE2`](https://www.evm.codes/#f5) — конструктор для нового контракта.
- В случае [`CALL`](https://www.evm.codes/#f1), [`CALLCODE`](https://www.evm.codes/#f2), [`STATICCALL`](https://www.evm.codes/#fa) или [`DELEGATECALL`](https://www.evm.codes/#f4) — вызываемый нами контракт.

### Стоимость расширения памяти

Стоимость расширения памяти (при необходимости).

В уравнении 326 это значение записывается как _C<sub>mem</sub>(μ<sub>i</sub>')-C<sub>mem</sub>(μ<sub>i</sub>)_. Снова посмотрев на раздел 9.4.1, мы видим, что _μ<sub>i</sub>_ — это количество слов в памяти. Таким образом, _μ<sub>i</sub>_ — это количество слов в памяти до выполнения кода операции, а _μ<sub>i</sub>'_ — количество слов в памяти после выполнения кода операции.

Функция _C<sub>mem</sub>_ определена в уравнении 328: _C<sub>mem</sub>(a) = G<sub>memory</sub> × a + ⌊a<sup>2</sup> ÷ 512⌋_. _⌊x⌋_ — это функция округления в меньшую сторону (floor), функция, которая для заданного значения возвращает наибольшее целое число, которое не больше этого значения. Например, _⌊2.5⌋ = ⌊2⌋ = 2._ Когда _a < √512_, _a<sup>2</sup> < 512_, и результат функции округления в меньшую сторону равен нулю. Таким образом, для первых 22 слов (704 байта) стоимость растет линейно с количеством требуемых слов памяти. После этой точки _⌊a<sup>2</sup> ÷ 512⌋_ становится положительным. Когда требуемая память достаточно велика, стоимость газа пропорциональна квадрату объема памяти.

**Примечание**: эти факторы влияют только на _базовую_ стоимость газа — они не учитывают рынок комиссий или чаевые валидаторам, которые определяют, сколько должен заплатить конечный пользователь — это просто чистая стоимость выполнения определенной операции в EVM.

[Подробнее о газе](/developers/docs/gas/).
## 9.3 Среда исполнения

Среда исполнения — это кортеж _I_, который включает информацию, не являющуюся частью состояния блокчейна или EVM.

| Параметр | Код операции для доступа к данным | Код Solidity для доступа к данным |
| --------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| _I<sub>a</sub>_ | [`ADDRESS`](https://www.evm.codes/#30) | `address(this)` |
| _I<sub>o</sub>_ | [`ORIGIN`](https://www.evm.codes/#32) | `tx.origin` |
| _I<sub>p</sub>_ | [`GASPRICE`](https://www.evm.codes/#3a) | `tx.gasprice` |
| _I<sub>d</sub>_ | [`CALLDATALOAD`](https://www.evm.codes/#35) и т. д. | `msg.data` |
| _I<sub>s</sub>_ | [`CALLER`](https://www.evm.codes/#33) | `msg.sender` |
| _I<sub>v</sub>_ | [`CALLVALUE`](https://www.evm.codes/#34) | `msg.value` |
| _I<sub>b</sub>_ | [`CODECOPY`](https://www.evm.codes/#39) | `address(this).code` |
| _I<sub>H</sub>_ | Поля заголовка блока, такие как [`NUMBER`](https://www.evm.codes/#43) и [`DIFFICULTY`](https://www.evm.codes/#44) | `block.number`, `block.difficulty` и т. д. |
| _I<sub>e</sub>_ | Глубина стека вызовов для вызовов между контрактами (включая создание контракта) |
| _I<sub>w</sub>_ | Разрешено ли EVM изменять состояние, или она работает статически |

Для понимания остальной части раздела 9 необходимы еще несколько параметров:

| Параметр | Определен в разделе | Значение |
| --------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _σ_ | 2 (стр. 2, уравнение 1) | Состояние блокчейна |
| _g_ | 9.3 (стр. 14) | Оставшийся газ |
| _A_ | 6.1 (стр. 9) | Накопленное подсостояние (изменения, запланированные на момент завершения транзакции) |
| _o_ | 9.3 (стр. 14) | Вывод — возвращаемый результат в случае внутренней транзакции (когда один контракт вызывает другой) и вызовов функций просмотра (когда вы просто запрашиваете информацию, поэтому нет необходимости ждать транзакции) |
## 9.4 Обзор исполнения

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

Уравнения 146-151 дают нам начальные условия для запуска EVM:

| Символ | Начальное значение | Значение |
| ---------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| _μ<sub>g</sub>_ | _g_ | Оставшийся газ |
| _μ<sub>pc</sub>_ | _0_ | Счетчик команд, адрес следующей инструкции для выполнения |
| _μ<sub>m</sub>_ | _(0, 0, ...)_ | Память, инициализированная нулями |
| _μ<sub>i</sub>_ | _0_ | Самая высокая используемая ячейка памяти |
| _μ<sub>s</sub>_ | _()_ | Стек, изначально пустой |
| _μ<sub>o</sub>_ | _∅_ | Вывод, пустое множество до тех пор, пока мы не остановимся либо с возвращаемыми данными ([`RETURN`](https://www.evm.codes/#f3) или [`REVERT`](https://www.evm.codes/#fd)), либо без них ([`STOP`](https://www.evm.codes/#00) или [`SELFDESTRUCT`](https://www.evm.codes/#ff)). |

Уравнение 152 говорит нам, что в каждый момент времени во время выполнения возможны четыре условия, и что с ними делать:

1.  `Z(σ,μ,A,I)`. Z представляет собой функцию, которая проверяет, создает ли операция недопустимый переход состояния (см. [исключительную остановку](#942-exceptional-halt)). Если она возвращает значение True (истина), новое состояние идентично старому (за исключением того, что сжигается газ), поскольку изменения не были реализованы.
2.  Если выполняемый код операции — [`REVERT`](https://www.evm.codes/#fd), новое состояние совпадает со старым состоянием, часть газа теряется.
3.  Если последовательность операций завершена, о чем свидетельствует [`RETURN`](https://www.evm.codes/#f3), состояние обновляется до нового состояния.
4.  Если мы не находимся ни в одном из конечных условий 1-3, продолжаем выполнение.
## 9.4.1 Состояние машины {#941-machine-state}

В этом разделе более подробно объясняется состояние машины. В нем указывается, что _w_ — это текущий код операции. Если _μ<sub>pc</sub>_ меньше _||I<sub>b</sub>||_, длины кода, то этот байт (_I<sub>b</sub>[μ<sub>pc</sub>]_) является кодом операции. В противном случае код операции определяется как [`STOP`](https://www.evm.codes/#00).

Поскольку это [стековая машина](https://en.wikipedia.org/wiki/Stack_machine), нам нужно отслеживать количество элементов, извлеченных (_δ_) и помещенных (_α_) каждым кодом операции.

## 9.4.2 Исключительная остановка

В этом разделе определяется функция _Z_, которая указывает, когда происходит аварийное завершение. Это [логическая](https://en.wikipedia.org/wiki/Boolean_data_type) функция, поэтому она использует [_∨_ для логического ИЛИ](https://en.wikipedia.org/wiki/Logical_disjunction) и [_∧_ для логического И](https://en.wikipedia.org/wiki/Logical_conjunction).

У нас происходит исключительная остановка, если выполняется любое из этих условий:

- **_μ<sub>g</sub> < C(σ,μ,A,I)_**
  Как мы видели в разделе 9.2, _C_ — это функция, определяющая стоимость газа. Оставшегося газа недостаточно для покрытия следующего кода операции.

- **_δ<sub>w</sub>=∅_**
  Если количество элементов, извлекаемых для кода операции, не определено, то и сам код операции не определен.

- **_|| μ<sub>s</sub> || < δ<sub>w</sub>_**
  Опустошение стека (stack underflow), недостаточно элементов в стеке для текущего кода операции.

- **_w = JUMP ∧ μ<sub>s</sub>[0]∉D(I<sub>b</sub>)_**
  Код операции — [`JUMP`](https://www.evm.codes/#56), а адрес не является [`JUMPDEST`](https://www.evm.codes/#5b). Переходы действительны _только_ тогда, когда местом назначения является [`JUMPDEST`](https://www.evm.codes/#5b).

- **_w = JUMPI ∧ μ<sub>s</sub>[1]≠0 ∧ μ<sub>s</sub>[0] ∉ D(I<sub>b</sub>)_**
  Код операции — [`JUMPI`](https://www.evm.codes/#57), условие истинно (не равно нулю), поэтому переход должен произойти, а адрес не является [`JUMPDEST`](https://www.evm.codes/#5b). Переходы действительны _только_ тогда, когда местом назначения является [`JUMPDEST`](https://www.evm.codes/#5b).

- **_w = RETURNDATACOPY ∧ μ<sub>s</sub>[1]+μ<sub>s</sub>[2]>|| μ<sub>o</sub> ||_**
  Код операции — [`RETURNDATACOPY`](https://www.evm.codes/#3e). В этом коде операции элемент стека _μ<sub>s</sub>[1]_ — это смещение для чтения в буфере возвращаемых данных, а элемент стека _μ<sub>s</sub>[2]_ — это длина данных. Это условие возникает, когда вы пытаетесь прочитать данные за пределами буфера возвращаемых данных. Обратите внимание, что аналогичного условия для данных вызова (calldata) или для самого кода нет. Когда вы пытаетесь прочитать данные за пределами этих буферов, вы просто получаете нули.

- **_|| μ<sub>s</sub> || - δ<sub>w</sub> + α<sub>w</sub> > 1024_**

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

- **_¬I<sub>w</sub> ∧ W(w,μ)_**
  Работаем ли мы статически ([¬ — это отрицание](https://en.wikipedia.org/wiki/Negation), а _I<sub>w</sub>_ истинно, когда нам разрешено изменять состояние блокчейна)? Если да, и мы пытаемся выполнить операцию, изменяющую состояние, она не может быть выполнена.

  Функция _W(w,μ)_ определяется позже в уравнении 159. _W(w,μ)_ истинно, если выполняется одно из этих условий:

  - **_w ∈ \{CREATE, CREATE2, SSTORE, SELFDESTRUCT}_**
    Эти коды операций изменяют состояние путем создания нового контракта, сохранения значения или уничтожения текущего контракта.

  - **_LOG0≤w ∧ w≤LOG4_**
    Если мы вызваны статически, мы не можем создавать записи логов.
    Все коды операций логов находятся в диапазоне от [`LOG0` (A0)](https://www.evm.codes/#a0) до [`LOG4` (A4)](https://www.evm.codes/#a4).
    Число после кода операции лога указывает, сколько тем содержит запись лога.
  - **_w=CALL ∧ μ<sub>s</sub>[2]≠0_**
    Вы можете вызвать другой контракт, когда находитесь в статическом режиме, но если вы это сделаете, вы не сможете перевести ему ETH.

- **_w = SSTORE ∧ μ<sub>g</sub> ≤ G<sub>callstipend</sub>_**
  Вы не можете выполнить [`SSTORE`](https://www.evm.codes/#55), если у вас нет газа больше, чем G<sub>callstipend</sub> (определено как 2300 в Приложении G).
## 9.4.3 Действительность места назначения перехода

Здесь мы формально определяем, что такое коды операций [`JUMPDEST`](https://www.evm.codes/#5b). Мы не можем просто искать значение байта 0x5B, потому что оно может находиться внутри PUSH (и, следовательно, быть данными, а не кодом операции).

В уравнении (162) мы определяем функцию _N(i,w)_. Первый параметр, _i_, — это позиция кода операции. Второй, _w_, — это сам код операции. Если _w∈[PUSH1, PUSH32]_, это означает, что код операции — это PUSH (квадратные скобки определяют диапазон, включающий конечные точки). В этом случае следующий код операции находится по адресу _i+2+(w−PUSH1)_. Для [`PUSH1`](https://www.evm.codes/#60) нам нужно продвинуться на два байта (сам PUSH и однобайтовое значение), для [`PUSH2`](https://www.evm.codes/#61) нам нужно продвинуться на три байта, потому что это двухбайтовое значение, и т. д. Все остальные коды операций EVM имеют длину всего один байт, поэтому во всех остальных случаях _N(i,w)=i+1_.

Эта функция используется в уравнении (161) для определения _D<sub>J</sub>(c,i)_, которое является [множеством](<https://en.wikipedia.org/wiki/Set_(mathematics)>) всех действительных мест назначения перехода в коде _c_, начиная с позиции кода операции _i_. Эта функция определяется рекурсивно. Если _i≥||c||_, это означает, что мы находимся в конце кода или после него. Мы больше не найдем никаких мест назначения перехода, поэтому просто возвращаем пустое множество.

Во всех остальных случаях мы смотрим на остальную часть кода, переходя к следующему коду операции и получая множество, начиная с него. _c[i]_ — это текущий код операции, поэтому _N(i,c[i])_ — это позиция следующего кода операции. Следовательно, _D<sub>J</sub>(c,N(i,c[i]))_ — это множество действительных мест назначения перехода, которое начинается со следующего кода операции. Если текущий код операции не является `JUMPDEST`, просто верните это множество. Если это `JUMPDEST`, включите его в результирующее множество и верните его.
## 9.4.4 Нормальная остановка {#944-normal-halt}

Функция остановки _H_ может возвращать три типа значений.

- Если мы не находимся в коде операции остановки, возвращается _∅_, пустое множество. По соглашению это значение интерпретируется как логическое ложь (false).
- Если у нас есть код операции остановки, который не производит вывод (либо [`STOP`](https://www.evm.codes/#00), либо [`SELFDESTRUCT`](https://www.evm.codes/#ff)), в качестве возвращаемого значения возвращается последовательность байтов нулевого размера. Обратите внимание, что это сильно отличается от пустого множества. Это значение означает, что EVM действительно остановилась, просто нет возвращаемых данных для чтения.
- Если у нас есть код операции остановки, который производит вывод (либо [`RETURN`](https://www.evm.codes/#f3), либо [`REVERT`](https://www.evm.codes/#fd)), возвращается последовательность байтов, указанная этим кодом операции. Эта последовательность берется из памяти, значение на вершине стека (_μ<sub>s</sub>[0]_) — это первый байт, а значение после него (_μ<sub>s</sub>[1]_) — это длина.

## H.2 Набор инструкций

Перед тем как перейти к последнему подразделу EVM, 9.5, давайте рассмотрим сами инструкции. Они определены в Приложении H.2, которое начинается на стр. 30. Ожидается, что все, что не указано как изменяющееся при использовании этого конкретного кода операции, останется неизменным. Переменные, которые изменяются, обозначаются как \<что-то\>′.

Например, давайте рассмотрим код операции [`ADD`](https://www.evm.codes/#01).

| Значение | Мнемоника | δ   | α   | Описание                                                  |
| -------: | --------- | --- | --- | --------------------------------------------------------- |
|     0x01 | ADD       | 2   | 1   | Операция сложения.                                        |
|          |           |     |     | _μ′<sub>s</sub>[0] ≡ μ<sub>s</sub>[0] + μ<sub>s</sub>[1]_ |

_δ_ — это количество значений, которые мы извлекаем из стека. В данном случае два, потому что мы складываем два верхних значения.

_α_ — это количество значений, которые мы помещаем обратно. В данном случае одно — сумма.

Таким образом, новая вершина стека (_μ′<sub>s</sub>[0]_) — это сумма старой вершины стека (_μ<sub>s</sub>[0]_) и старого значения под ней (_μ<sub>s</sub>[1]_).

Вместо того чтобы перечислять все коды операций в виде утомительного списка, в этой статье рассматриваются только те коды операций, которые привносят что-то новое.

| Значение | Мнемоника | δ   | α   | Описание                                                                                                   |
| -------: | --------- | --- | --- | ---------------------------------------------------------------------------------------------------------- |
|     0x20 | KECCAK256 | 2   | 1   | Вычисление хеша Keccak-256.                                                                                |
|          |           |     |     | _μ′<sub>s</sub>[0] ≡ KEC(μ<sub>m</sub>[μ<sub>s</sub>[0] . . . (μ<sub>s</sub>[0] + μ<sub>s</sub>[1] − 1)])_ |
|          |           |     |     | _μ′<sub>i</sub> ≡ M(μ<sub>i</sub>,μ<sub>s</sub>[0],μ<sub>s</sub>[1])_                                      |

Это первый код операции, который обращается к памяти (в данном случае только для чтения). Однако он может выйти за текущие пределы памяти, поэтому нам нужно обновить _μ<sub>i</sub>_. Мы делаем это с помощью функции _M_, определенной в уравнении 330 на стр. 30.

| Значение | Мнемоника | δ   | α   | Описание                                  |
| -------: | --------- | --- | --- | ----------------------------------------- |
|     0x31 | BALANCE   | 1   | 1   | Получение баланса заданного аккаунта.     |
|          |           |     |     | ...                                       |

Адрес, баланс которого нам нужно узнать, — это _μ<sub>s</sub>[0] mod 2<sup>160</sup>_. На вершине стека находится адрес, но поскольку адреса состоят только из 160 бит, мы вычисляем значение [по модулю](https://en.wikipedia.org/wiki/Modulo_operation) 2<sup>160</sup>.

Если _σ[μ<sub>s</sub>[0] mod 2<sup>160</sup>] ≠ ∅_, это означает, что об этом адресе есть информация. В этом случае _σ[μ<sub>s</sub>[0] mod 2<sup>160</sup>]<sub>b</sub>_ — это баланс для этого адреса. Если _σ[μ<sub>s</sub>[0] mod 2<sup>160</sup>] = ∅_, это означает, что этот адрес не инициализирован и баланс равен нулю. Вы можете увидеть список полей информации об аккаунте в разделе 4.1 на стр. 4.

Второе уравнение, _A'<sub>a</sub> ≡ A<sub>a</sub> ∪ \{μ<sub>s</sub>[0] mod 2<sup>160</sup>\}_, связано с разницей в стоимости между доступом к «теплому» хранилищу (хранилищу, к которому недавно обращались и которое, вероятно, кэшировано) и «холодному» хранилищу (хранилищу, к которому не обращались и которое, вероятно, находится в более медленной памяти, извлечение из которой обходится дороже). _A<sub>a</sub>_ — это список адресов, к которым транзакция обращалась ранее, и доступ к которым, следовательно, должен быть дешевле, как определено в разделе 6.1 на стр. 9. Вы можете прочитать больше на эту тему в [EIP-2929](https://eips.ethereum.org/EIPS/eip-2929).

| Значение | Мнемоника | δ   | α   | Описание                                |
| -------: | --------- | --- | --- | --------------------------------------- |
|     0x8F | DUP16     | 16  | 17  | Дублирование 16-го элемента стека.      |
|          |           |     |     | _μ′<sub>s</sub>[0] ≡ μ<sub>s</sub>[15]_ |

Обратите внимание, что для использования любого элемента стека нам нужно извлечь его, что означает, что нам также нужно извлечь все элементы стека, находящиеся над ним. В случае с [`DUP<n>`](https://www.evm.codes/#8f) и [`SWAP<n>`](https://www.evm.codes/#9f) это означает необходимость извлечь, а затем поместить обратно до шестнадцати значений.
## 9.5 Цикл выполнения

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

Уравнение (164) гласит, что при заданном состоянии:

- _σ_ (глобальное состояние блокчейна)
- _μ_ (состояние EVM)
- _A_ (подсостояние, изменения, которые должны произойти по завершении транзакции)
- _I_ (среда выполнения)

Новое состояние — _(σ', μ', A', I')_.

Уравнения (165)-(167) определяют стек и его изменение в результате выполнения кода операции (_μ<sub>s</sub>_). Уравнение (168) — это изменение количества газа (_μ<sub>g</sub>_). Уравнение (169) — это изменение счетчика команд (_μ<sub>pc</sub>_). Наконец, уравнения (170)-(173) указывают, что остальные параметры остаются неизменными, если они явно не изменены кодом операции.

На этом EVM полностью определена.
## Заключение {#conclusion}

Математическая нотация точна и позволила желтой книге специфицировать каждую деталь Эфириума. Однако у нее есть некоторые недостатки:

- Ее могут понять только люди, а это означает, что [тесты на соответствие](https://github.com/ethereum/tests) должны быть написаны вручную.
- Программисты понимают компьютерный код.
  Они могут понимать или не понимать математическую нотацию.

Возможно, по этим причинам более новые [спецификации уровня консенсуса](https://github.com/ethereum/consensus-specs/blob/master/tests/core/pyspec/README.md) написаны на Python. Существуют [спецификации уровня исполнения на Python](https://ethereum.github.io/execution-specs), но они не полные. До тех пор, пока вся желтая книга также не будет переведена на Python или аналогичный язык, желтая книга будет продолжать использоваться, и умение ее читать будет полезным.
