Zrozumienie specyfikacji EVM z Yellow Paper
Yellow Paper (opens in a new tab) to formalna specyfikacja Ethereum. Z wyjątkiem zmian wprowadzonych w ramach procesu EIP, zawiera ona dokładny opis działania wszystkiego. Jest napisana jako praca matematyczna, która zawiera terminologię, która może nie być znana programistom. W tym artykule dowiesz się, jak ją czytać, a co za tym idzie, inne powiązane prace matematyczne.
Który Yellow Paper?
Podobnie jak prawie wszystko inne w Ethereum, Yellow Paper ewoluuje z czasem. Aby móc odnieść się do konkretnej wersji, załadowałem aktualną wersję w momencie pisania tego tekstu. Numery sekcji, stron i równań, których używam, będą odnosić się do tej wersji. Dobrym pomysłem jest otwarcie jej w innym oknie podczas czytania tego dokumentu.
Dlaczego EVM?
Oryginalny Yellow Paper został napisany na samym początku rozwoju Ethereum. Opisuje oryginalny mechanizm konsensusu oparty na proof-of-work, który był pierwotnie używany do zabezpieczania sieci. Jednakże we wrześniu 2022 roku Ethereum wyłączyło proof-of-work i zaczęło używać konsensusu opartego na proof-of-stake. Ten samouczek skupi się na częściach Yellow Paper definiujących Wirtualną Maszynę Ethereum. EVM pozostała niezmieniona po przejściu na proof-of-stake (z wyjątkiem zwracanej wartości kodu operacyjnego DIFFICULTY).
9 Model wykonania
Ta sekcja (s. 12-14) zawiera większość definicji EVM.
Termin stan systemu obejmuje wszystko, co trzeba wiedzieć o systemie, aby go uruchomić. W typowym komputerze oznacza to pamięć, zawartość rejestrów itp.
Maszyna Turinga (opens in a new tab) jest modelem obliczeniowym. Zasadniczo jest to uproszczona wersja komputera, o której udowodniono, że ma taką samą zdolność do wykonywania obliczeń jak normalny komputer (wszystko, co może obliczyć komputer, może obliczyć maszyna Turinga i na odwrót). Ten model ułatwia udowadnianie różnych twierdzeń o tym, co jest, a co nie jest obliczalne.
Termin kompletność Turinga (opens in a new tab) oznacza komputer, który może wykonywać te same obliczenia co maszyna Turinga. Maszyny Turinga mogą wpaść w nieskończone pętle, a EVM nie, ponieważ zabrakłoby jej gazu, więc jest tylko quasi-kompletna w sensie Turinga.
9.1 Podstawy
Ta sekcja przedstawia podstawy EVM i porównuje ją z innymi modelami obliczeniowymi.
Maszyna stosowa (opens in a new tab) to komputer, który przechowuje dane pośrednie nie w rejestrach, ale na stosie (opens in a new tab). Jest to preferowana architektura dla maszyn wirtualnych, ponieważ jest łatwa w implementacji, co oznacza, że błędy i luki w zabezpieczeniach są znacznie mniej prawdopodobne. Pamięć na stosie jest podzielona na 256-bitowe słowa. Zostało to wybrane, ponieważ jest to wygodne dla podstawowych operacji kryptograficznych Ethereum, takich jak haszowanie Keccak-256 i obliczenia na krzywych eliptycznych. Maksymalny rozmiar stosu to 1024 elementy (1024 x 256 bitów). Kiedy kody operacyjne są wykonywane, zazwyczaj pobierają swoje parametry ze stosu. Istnieją kody operacyjne specjalnie do reorganizacji elementów na stosie, takie jak POP (usuwa element z wierzchołka stosu), DUP_N (duplikuje N-ty element na stosie) itp.
EVM posiada również ulotną przestrzeń zwaną pamięcią, która jest używana do przechowywania danych podczas wykonywania. Pamięć ta jest zorganizowana w 32-bajtowe słowa. Wszystkie lokalizacje w pamięci są inicjalizowane do zera. Jeśli wykonasz ten kod w Yul (opens in a new tab), aby dodać słowo do pamięci, wypełni on 32 bajty pamięci, uzupełniając pustą przestrzeń w słowie zerami, tzn. tworzy jedno słowo – z zerami w lokalizacjach 0–29, 0x60 w lokalizacji 30 i 0xA7 w lokalizacji 31.
1mstore(0, 0x60A7)mstore to jeden z trzech kodów operacyjnych, które EVM zapewnia do interakcji z pamięcią – ładuje słowo do pamięci. Pozostałe dwa to mstore8, który ładuje pojedynczy bajt do pamięci, oraz mload, który przenosi słowo z pamięci na stos.
EVM posiada również oddzielną, nietrwałą pamięć trwałą (storage), która jest utrzymywana jako część stanu systemu – jest ona zorganizowana w tablice słów (w przeciwieństwie do adresowalnych słowami tablic bajtów na stosie). W tej pamięci trwałej kontrakty przechowują trwałe dane — kontrakt może wchodzić w interakcje tylko z własną pamięcią trwałą. Pamięć trwała jest zorganizowana w mapowania klucz-wartość.
Chociaż nie jest to wspomniane w tej sekcji Yellow Paper, warto również wiedzieć, że istnieje czwarty rodzaj pamięci. Calldata to adresowalna bajtowo pamięć tylko do odczytu, używana do przechowywania wartości przekazanej wraz z parametrem data transakcji. EVM ma określone kody operacyjne do zarządzania calldata. calldatasize zwraca rozmiar danych. calldataload ładuje dane na stos. calldatacopy kopiuje dane do pamięci.
Standardowa architektura von Neumanna (opens in a new tab) przechowuje kod i dane w tej samej pamięci. EVM nie przestrzega tego standardu ze względów bezpieczeństwa — współdzielenie pamięci ulotnej umożliwia zmianę kodu programu. Zamiast tego kod jest zapisywany w pamięci trwałej.
Istnieją tylko dwa przypadki, w których kod jest wykonywany z pamięci:
- Gdy kontrakt tworzy inny kontrakt (przy użyciu
CREATE(opens in a new tab) lubCREATE2(opens in a new tab)), kod konstruktora kontraktu pochodzi z pamięci. - Podczas tworzenia dowolnego kontraktu kod konstruktora jest uruchamiany, a następnie zwraca kod właściwego kontraktu, również z pamięci.
Termin wykonanie wyjątkowe oznacza wyjątek, który powoduje zatrzymanie wykonywania bieżącego kontraktu.
9.2 Przegląd opłat
W tej sekcji wyjaśniono, w jaki sposób obliczane są opłaty za gaz. Istnieją trzy koszty:
Koszt kodu operacyjnego
Nieodłączny koszt danego kodu operacyjnego. Aby uzyskać tę wartość, znajdź grupę kosztów kodu operacyjnego w Dodatku H (s. 28, pod równaniem (327)) i znajdź grupę kosztów w równaniu (324). Daje to funkcję kosztu, która w większości przypadków wykorzystuje parametry z Dodatku G (s. 27).
Na przykład kod operacyjny CALLDATACOPY (opens in a new tab) należy do grupy Wcopy. Koszt kodu operacyjnego dla tej grupy to Gverylow+Gcopy×⌈μs[2]÷32⌉. Patrząc na Dodatek G, widzimy, że obie stałe wynoszą 3, co daje nam 3+3×⌈μs[2]÷32⌉.
Nadal musimy rozszyfrować wyrażenie ⌈μs[2]÷32⌉. Zewnętrzna część, ⌈ <value> ⌉, to funkcja sufitu, czyli funkcja, która dla danej wartości zwraca najmniejszą liczbę całkowitą, która nie jest mniejsza od tej wartości. Na przykład ⌈2,5⌉ = ⌈3⌉ = 3. Wewnętrzna część to μs[2]÷32. Patrząc na sekcję 3 (Konwencje) na s. 3, μ to stan maszyny. Stan maszyny jest zdefiniowany w sekcji 9.4.1 na s. 13. Zgodnie z tą sekcją jednym z parametrów stanu maszyny jest s dla stosu. Podsumowując, wydaje się, że μs[2] to lokalizacja #2 na stosie. Patrząc na kod operacyjny (opens in a new tab), lokalizacja #2 na stosie to rozmiar danych w bajtach. Patrząc na inne kody operacyjne w grupie Wcopy, CODECOPY (opens in a new tab) i RETURNDATACOPY (opens in a new tab), mają one również rozmiar danych w tej samej lokalizacji. Zatem ⌈μs[2]÷32⌉ to liczba 32-bajtowych słów wymaganych do przechowywania kopiowanych danych. Podsumowując wszystko, nieodłączny koszt CALLDATACOPY (opens in a new tab) to 3 jednostki gazu plus 3 za każde kopiowane słowo danych.
Koszt bieżący
Koszt uruchomienia kodu, który wywołujemy.
- W przypadku
CREATE(opens in a new tab) iCREATE2(opens in a new tab) jest to konstruktor nowego kontraktu. - W przypadku
CALL(opens in a new tab),CALLCODE(opens in a new tab),STATICCALL(opens in a new tab) lubDELEGATECALL(opens in a new tab) jest to kontrakt, który wywołujemy.
Koszt rozszerzenia pamięci
Koszt rozszerzenia pamięci (w razie potrzeby).
W równaniu 324 wartość ta jest zapisana jako Cmem(μi')-Cmem(μi). Patrząc ponownie na sekcję 9.4.1, widzimy, że μi to liczba słów w pamięci. Zatem μi to liczba słów w pamięci przed kodem operacyjnym, a μi' to liczba słów w pamięci po kodzie operacyjnym.
Funkcja Cmem jest zdefiniowana w równaniu 326: Cmem(a) = Gmemory × a + ⌊a2 ÷ 512⌋. ⌊x⌋ to funkcja podłogi, czyli funkcja, która dla danej wartości zwraca największą liczbę całkowitą, która nie jest większa od tej wartości. Na przykład ⌊2,5⌋ = ⌊2⌋ = 2. Gdy a < √512, a2 < 512, a wynik funkcji podłogi wynosi zero. Zatem dla pierwszych 22 słów (704 bajtów) koszt rośnie liniowo wraz z liczbą wymaganych słów pamięci. Powyżej tego punktu ⌊a2 ÷ 512⌋ jest dodatnie. Gdy wymagana pamięć jest wystarczająco duża, koszt gazu jest proporcjonalny do kwadratu ilości pamięci.
Uwaga: te czynniki wpływają tylko na nieodłączny koszt gazu – nie uwzględniają rynku opłat ani napiwków dla walidatorów, które określają, ile użytkownik końcowy musi zapłacić – to tylko surowy koszt uruchomienia określonej operacji na EVM.
9.3 Środowisko wykonawcze
Środowisko wykonawcze to krotka, I, która zawiera informacje, które nie są częścią stanu blockchaina ani EVM.
| Parametr | Kod operacyjny dostępu do danych | Kod Solidity do dostępu do danych |
|---|---|---|
| Ia | ADRES (opens in a new tab) | address(this) |
| Io | ORIGIN (opens in a new tab) | tx.origin |
| Ip | GASPRICE (opens in a new tab) | tx.gasprice |
| Id | CALLDATALOAD (opens in a new tab) itp. | msg.data |
| Is | CALLER (opens in a new tab) | msg.sender |
| Iv | CALLVALUE (opens in a new tab) | msg.value |
| Ib | CODECOPY (opens in a new tab) | address(this).code |
| IH | Pola nagłówka bloku, takie jak NUMBER (opens in a new tab) i DIFFICULTY (opens in a new tab) | block.number, block.difficulty, itp. |
| Ie | Głębokość stosu wywołań dla wywołań między kontraktami (w tym tworzenie kontraktów) | |
| Iw | Czy EVM może zmieniać stan, czy działa statycznie |
Kilka innych parametrów jest niezbędnych do zrozumienia reszty sekcji 9:
| Parametr | Zdefiniowano w sekcji | Znaczenie |
|---|---|---|
| σ | 2 (s. 2, równanie 1) | Stan blockchaina |
| g | 9.3 (s. 13) | Pozostały gaz |
| A | 6.1 (s. 8) | Naliczony podstan (zmiany zaplanowane na zakończenie transakcji) |
| o | 9.3 (s. 13) | Dane wyjściowe — zwracany wynik w przypadku transakcji wewnętrznej (gdy jeden kontrakt wywołuje drugi) i wywołania funkcji widoku (gdy pytasz tylko o informacje, więc nie ma potrzeby czekać na transakcję) |
9.4 Przegląd wykonania
Teraz, gdy mamy już wszystkie wstępne informacje, możemy w końcu zacząć pracować nad tym, jak działa EVM.
Równania 137–142 podają nam warunki początkowe do uruchomienia EVM:
| Symbol | Wartość początkowa | Znaczenie |
|---|---|---|
| μg | g | Pozostały gaz |
| μpc | 0 | Licznik programu, adres następnej instrukcji do wykonania |
| μm | (0, 0, ...) | Pamięć, zainicjalizowana samymi zerami |
| μi | 0 | Najwyższa używana lokalizacja pamięci |
| μs | () | Stos, początkowo pusty |
| μo | ∅ | Dane wyjściowe, pusty zbiór do momentu, gdy zatrzymamy się z danymi zwrotnymi (RETURN (opens in a new tab) lub REVERT (opens in a new tab)) lub bez nich (STOP (opens in a new tab) lub SELFDESTRUCT (opens in a new tab)). |
Równanie 143 mówi nam, że w każdym momencie podczas wykonywania istnieją cztery możliwe warunki i co z nimi zrobić:
Z(σ,μ,A,I). Z reprezentuje funkcję, która sprawdza, czy operacja tworzy nieprawidłowe przejście stanu (zobacz zatrzymanie wyjątkowe). Jeśli wynikiem jest Prawda, nowy stan jest identyczny ze starym (z wyjątkiem tego, że gaz jest spalany), ponieważ zmiany nie zostały zaimplementowane.- Jeśli wykonywany jest kod operacyjny
REVERT(opens in a new tab), nowy stan jest taki sam jak stary stan, a część gazu przepada. - Jeśli sekwencja operacji jest zakończona, co jest sygnalizowane przez
RETURN(opens in a new tab)), stan jest aktualizowany do nowego stanu. - Jeśli nie jesteśmy w jednym z warunków końcowych 1-3, kontynuuj działanie.
9.4.1 Stan maszyny
Ta sekcja wyjaśnia bardziej szczegółowo stan maszyny. Określa, że w to bieżący kod operacyjny. Jeśli μpc jest mniejsze niż ||Ib||, długość kodu, to ten bajt (Ib[μpc]) jest kodem operacyjnym. W przeciwnym razie kod operacyjny jest zdefiniowany jako STOP (opens in a new tab).
Ponieważ jest to maszyna stosowa (opens in a new tab), musimy śledzić liczbę elementów zdjętych ze stosu (δ) i umieszczonych na nim (α) przez każdy kod operacyjny.
9.4.2 Wyjątkowe zatrzymanie
W tej sekcji zdefiniowano funkcję Z, która określa, kiedy mamy do czynienia z nienormalnym zakończeniem. Jest to funkcja logiczna (opens in a new tab), więc używa ∨ dla lub logicznego (opens in a new tab) i ∧ dla i logicznego (opens in a new tab).
Mamy wyjątkowe zatrzymanie, jeśli którykolwiek z tych warunków jest prawdziwy:
-
μg < C(σ,μ,A,I) Jak widzieliśmy w sekcji 9.2, C to funkcja, która określa koszt gazu. Nie ma wystarczającej ilości gazu na pokrycie następnego kodu operacyjnego.
-
δw=∅ Jeśli liczba elementów zdejmowanych dla kodu operacyjnego jest niezdefiniowana, to sam kod operacyjny jest niezdefiniowany.
-
|| μs || < δw Niedomiar stosu, niewystarczająca liczba elementów na stosie dla bieżącego kodu operacyjnego.
-
w = JUMP ∧ μs[0]∉D(Ib) Kod operacyjny to
JUMP(opens in a new tab), a adres to nieJUMPDEST(opens in a new tab). Skoki są ważne tylko wtedy, gdy miejscem docelowym jestJUMPDEST(opens in a new tab). -
w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) Kod operacyjny to
JUMPI(opens in a new tab), warunek jest prawdziwy (niezerowy), więc skok powinien nastąpić, a adres nie jestJUMPDEST(opens in a new tab). Skoki są ważne tylko wtedy, gdy miejscem docelowym jestJUMPDEST(opens in a new tab). -
w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || Kod operacyjny to
RETURNDATACOPY(opens in a new tab). W tym kodzie operacyjnym element stosu μs[1] to przesunięcie, od którego należy odczytywać dane w buforze danych zwrotnych, a element stosu μs[2] to długość danych. Ten warunek występuje, gdy próbujesz odczytać dane poza końcem bufora danych zwrotnych. Należy zauważyć, że nie ma podobnego warunku dla calldata ani dla samego kodu. Gdy próbujesz odczytać dane poza końcem tych buforów, otrzymujesz po prostu zera. -
|| μs || - δw + αw > 1024
Przepełnienie stosu. Jeśli uruchomienie kodu operacyjnego spowoduje, że stos będzie zawierał ponad 1024 elementy, przerwij.
-
¬Iw ∧ W(w,μ) Czy działamy statycznie (¬ to negacja (opens in a new tab), a Iw jest prawdziwe, gdy możemy zmienić stan blockchaina)? Jeśli tak i próbujemy wykonać operację zmiany stanu, nie może ona się powieść.
Funkcja W(w,μ) jest zdefiniowana później w równaniu 150. W(w,μ) jest prawdą, jeśli jeden z tych warunków jest prawdziwy:
-
w ∈ {CREATE, CREATE2, SSTORE, SELFDESTRUCT} Te kody operacyjne zmieniają stan, tworząc nowy kontrakt, przechowując wartość lub niszcząc bieżący kontrakt.
-
LOG0≤w ∧ w≤LOG4 Jeśli jesteśmy wywoływani statycznie, nie możemy emitować wpisów dziennika. Kody operacyjne dziennika znajdują się w zakresie od
LOG0(A0) (opens in a new tab) doLOG4(A4) (opens in a new tab). Liczba po kodzie operacyjnym dziennika określa, ile tematów zawiera wpis dziennika. -
w=CALL ∧ μs[2]≠0 Możesz wywołać inny kontrakt, gdy jesteś statyczny, ale jeśli to zrobisz, nie możesz przelać do niego ETH.
-
-
w = SSTORE ∧ μg ≤ Gcallstipend Nie można uruchomić
SSTORE(opens in a new tab), chyba że masz więcej gazu niż Gcallstipend (zdefiniowane jako 2300 w Dodatku G).
9.4.3 Prawidłowość miejsca docelowego skoku
Tutaj formalnie definiujemy, czym są kody operacyjne JUMPDEST (opens in a new tab). Nie możemy po prostu szukać wartości bajtowej 0x5B, ponieważ może ona znajdować się wewnątrz PUSH (a zatem być danymi, a nie kodem operacyjnym).
W równaniu (153) definiujemy funkcję N(i,w). Pierwszy parametr, i, to lokalizacja kodu operacyjnego. Drugi, w, to sam kod operacyjny. Jeśli w∈[PUSH1, PUSH32], oznacza to, że kod operacyjny to PUSH (nawiasy kwadratowe definiują zakres, który obejmuje punkty końcowe). W takim przypadku następny kod operacyjny znajduje się w i+2+(w−PUSH1). W przypadku PUSH1 (opens in a new tab) musimy przesunąć się o dwa bajty (sam PUSH i jednobajtowa wartość), w przypadku PUSH2 (opens in a new tab) musimy przesunąć się o trzy bajty, ponieważ jest to dwubajtowa wartość itd. Wszystkie inne kody operacyjne EVM mają tylko jeden bajt długości, więc we wszystkich innych przypadkach N(i,w)=i+1.
Ta funkcja jest używana w równaniu (152) do zdefiniowania DJ(c,i), który jest zbiorem (opens in a new tab) wszystkich prawidłowych miejsc docelowych skoku w kodzie c, zaczynając od lokalizacji kodu operacyjnego i. Ta funkcja jest zdefiniowana rekurencyjnie. Jeśli i≥||c||, oznacza to, że jesteśmy na końcu kodu lub za nim. Nie znajdziemy już żadnych miejsc docelowych skoku, więc po prostu zwróć pusty zbiór.
We wszystkich innych przypadkach patrzymy na resztę kodu, przechodząc do następnego kodu operacyjnego i pobierając zbiór, zaczynając od niego. c[i] to bieżący kod operacyjny, więc N(i,c[i]) to lokalizacja następnego kodu operacyjnego. DJ(c,N(i,c[i])) jest zatem zbiorem prawidłowych miejsc docelowych skoku, który zaczyna się od następnego kodu operacyjnego. Jeśli bieżący kod operacyjny nie jest JUMPDEST, po prostu zwróć ten zbiór. Jeśli jest to JUMPDEST, umieść go w zbiorze wyników i zwróć go.
9.4.4 Normalne zatrzymanie
Funkcja zatrzymania H może zwracać trzy typy wartości.
- Jeśli nie jesteśmy w kodzie operacyjnym zatrzymania, zwróć ∅, pusty zbiór. Zgodnie z konwencją wartość ta jest interpretowana jako logiczny fałsz.
- Jeśli mamy kod operacyjny zatrzymania, który nie generuje danych wyjściowych (albo
STOP(opens in a new tab), alboSELFDESTRUCT(opens in a new tab)), zwróć sekwencję zerowej liczby bajtów jako wartość zwracaną. Należy zauważyć, że jest to zupełnie co innego niż pusty zbiór. Ta wartość oznacza, że EVM naprawdę się zatrzymała, tylko nie ma danych zwrotnych do odczytania. - Jeśli mamy kod operacyjny zatrzymania, który generuje dane wyjściowe (albo
RETURN(opens in a new tab), alboREVERT(opens in a new tab)), zwróć sekwencję bajtów określoną przez ten kod operacyjny. Ta sekwencja jest pobierana z pamięci, wartość na szczycie stosu (μs[0]) to pierwszy bajt, a wartość za nią (μs[1]) to długość.
H.2 Zestaw instrukcji
Zanim przejdziemy do ostatniej podsekcji EVM, 9.5, spójrzmy na same instrukcje. Są one zdefiniowane w Dodatku H.2, który zaczyna się na s. 29. Wszystko, co nie jest określone jako zmieniające się wraz z tym konkretnym kodem operacyjnym, powinno pozostać takie samo. Zmienne, które się zmieniają, są określone jako <coś>′.
Spójrzmy na przykład na kod operacyjny ADD (opens in a new tab).
| Wartość | Mnemonik | δ | α | Opis |
|---|---|---|---|---|
| 0x01 | ADD | 2 | 1 | Operacja dodawania. |
| μ′s[0] ≡ μs[0] + μs[1] |
δ to liczba wartości, które zdejmujemy ze stosu. W tym przypadku dwie, ponieważ dodajemy dwie górne wartości.
α to liczba wartości, które odkładamy na stos. W tym przypadku jedna, suma.
Zatem nowy wierzchołek stosu (μ′s[0]) jest sumą starego wierzchołka stosu (μs[0]) i starej wartości pod nim (μs[1]).
Zamiast przechodzić przez wszystkie kody operacyjne w postaci „listy przyprawiającej o zawrót głowy”, ten artykuł wyjaśnia tylko te kody operacyjne, które wprowadzają coś nowego.
| Wartość | Mnemonik | δ | α | Opis |
|---|---|---|---|---|
| 0x20 | KECCAK256 | 2 | 1 | Oblicz hasz Keccak-256. |
| μ′s[0] ≡ KEC(μm[μs[0] . . . (μs[0] + μs[1] − 1)]) | ||||
| μ′i ≡ M(μi,μs[0],μs[1]) |
Jest to pierwszy kod operacyjny, który uzyskuje dostęp do pamięci (w tym przypadku tylko do odczytu). Może on jednak wykraczać poza obecne granice pamięci, więc musimy zaktualizować μi. Robimy to za pomocą funkcji M zdefiniowanej w równaniu 328 na s. 29.
| Wartość | Mnemonik | δ | α | Opis |
|---|---|---|---|---|
| 0x31 | BALANCE | 1 | 1 | Pobierz saldo danego konta. |
| ... |
Adres, którego saldo musimy znaleźć to μs[0] mod 2160. Wierzchołek stosu to adres, ale ponieważ adresy mają tylko 160 bitów, obliczamy wartość modulo (opens in a new tab) 2160.
Jeśli σ[μs[0] mod 2160] ≠ ∅, oznacza to, że istnieją informacje o tym adresie. W takim przypadku σ[μs[0] mod 2160]b to saldo dla tego adresu. Jeśli σ[μs[0] mod 2160] = ∅, oznacza to, że ten adres jest niezainicjalizowany, a saldo wynosi zero. Listę pól informacyjnych konta można zobaczyć w sekcji 4.1 na s. 4.
Drugie równanie, A'a ≡ Aa ∪ {μs[0] mod 2160}, jest związane z różnicą w kosztach między dostępem do ciepłej pamięci trwałej (pamięci, do której ostatnio uzyskano dostęp i która prawdopodobnie jest w pamięci podręcznej) a zimną pamięcią trwałą (pamięcią, do której nie było dostępu i która prawdopodobnie znajduje się w wolniejszej pamięci, której odzyskanie jest droższe). Aa to lista adresów, do których wcześniej uzyskano dostęp w ramach transakcji, a zatem dostęp do nich powinien być tańszy, zgodnie z definicją w sekcji 6.1 na s. 8. Więcej na ten temat można przeczytać w EIP-2929 (opens in a new tab).
| Wartość | Mnemonik | δ | α | Opis |
|---|---|---|---|---|
| 0x8F | DUP16 | 16 | 17 | Duplikuj 16. element stosu. |
| μ′s[0] ≡ μs[15] |
Należy pamiętać, że aby użyć dowolnego elementu stosu, musimy go zdjąć, co oznacza, że musimy również zdjąć wszystkie elementy stosu znajdujące się nad nim. W przypadku DUP<n> (opens in a new tab) i SWAP<n> (opens in a new tab) oznacza to konieczność zdjęcia, a następnie odłożenia na stos do szesnastu wartości.
9.5 Cykl wykonania
Teraz, gdy mamy już wszystkie części, możemy w końcu zrozumieć, jak udokumentowany jest cykl wykonania EVM.
Równanie (155) mówi, że biorąc pod uwagę stan:
- σ (globalny stan blockchaina)
- μ (stan EVM)
- A (podstan, zmiany, które mają nastąpić po zakończeniu transakcji)
- I (środowisko wykonawcze)
Nowy stan to (σ', μ', A', I').
Równania (156)-(158) definiują stos i zmianę w nim spowodowaną przez kod operacyjny (μs). Równanie (159) to zmiana w gazie (μg). Równanie (160) to zmiana w liczniku programu (μpc). Wreszcie równania (161)-(164) określają, że pozostałe parametry pozostają takie same, chyba że zostały wyraźnie zmienione przez kod operacyjny.
W ten sposób EVM jest w pełni zdefiniowana.
Wnioski
Notacja matematyczna jest precyzyjna i pozwoliła Yellow Paper na określenie każdego szczegółu Ethereum. Ma to jednak pewne wady:
- Może być zrozumiana tylko przez ludzi, co oznacza, że testy zgodności (opens in a new tab) muszą być pisane ręcznie.
- Programiści rozumieją kod komputerowy. Mogą rozumieć notację matematyczną lub nie.
Być może z tych powodów nowsze specyfikacje warstwy konsensusu (opens in a new tab) są napisane w Pythonie. Istnieją specyfikacje warstwy wykonawczej w Pythonie (opens in a new tab), ale nie są one kompletne. Dopóki cały Yellow Paper nie zostanie również przetłumaczony na język Python lub podobny, Yellow Paper będzie nadal w użyciu i warto umieć go czytać.
Strona ostatnio zaktualizowana: 3 marca 2026