---
title: "Розуміння специфікацій EVM у Жовтій книзі"
description: "Розуміння частини Жовтої книги, формальних специфікацій Етеріуму, яка пояснює віртуальну машину Етеріуму (EVM)."
author: "qbzzt"
tags: ["evm"]
skill: intermediate
breadcrumb: "EVM у Жовтій книзі"
lang: uk
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 року Етеріум відмовився від доказу виконання роботи та почав використовувати консенсус на основі доказу частки (PoS). Цей посібник буде зосереджений на частинах Жовтої книги, що визначають віртуальну машину Етеріуму (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)), код для конструктора контракту береться з пам'яті.
- Під час створення _будь-якого_ контракту виконується код конструктора, а потім повертається код самого контракту, також із пам'яті.

Термін виняткове виконання (exceptional execution) означає виняток, який спричиняє зупинку виконання поточного контракту.

## 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 function), функція, яка для заданого значення повертає найменше ціле число, що не є меншим за це значення. Наприклад, _⌈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 function), функція, яка для заданого значення повертає найбільше ціле число, що не є більшим за це значення. Наприклад, _⌊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]_ — довжина даних. Ця умова виникає, коли ви намагаєтеся прочитати за межами буфера даних повернення. Зверніть увагу, що подібної умови немає для даних виклику або для самого коду. Коли ви намагаєтеся прочитати за межами цих буферів, ви просто отримуєте нулі.

- **_|| μ<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 або подібну мову, Жовта книга продовжуватиме використовуватися, і вміння її читати є корисним.
