Zrozumienie specyfikacji EVM w żółtej księdze
Żółta księga (otwiera się w nowej karcie) to formalna specyfikacja Ethereum. Z wyjątkiem poprawek wprowadzonych przez proces EIP, zawiera ona dokładny opis tego, jak wszystko działa. Jest napisana w formie pracy matematycznej, co obejmuje terminologię, która może nie być znana programistom. Z tego artykułu dowiesz się, jak ją czytać, a co za tym idzie, jak czytać inne powiązane prace matematyczne.
Która żółta księga?
Jak prawie wszystko w Ethereum, żółta księga ewoluuje w czasie. Aby móc odnieść się do konkretnej wersji, przesłałem wersję aktualną w momencie pisania (otwiera się w nowej karcie). 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?
Oryginalna żółta księga została napisana na samym początku rozwoju Ethereum. Opisuje ona oryginalny mechanizm konsensusu oparty na dowodzie pracy (PoW), który był pierwotnie używany do zabezpieczania sieci. Jednakże we wrześniu 2022 roku Ethereum wyłączyło dowód pracy i zaczęło używać konsensusu opartego na dowodzie stawki (PoS). Ten samouczek skupi się na częściach żółtej księgi definiujących maszynę wirtualną Ethereum (EVM). EVM pozostała niezmieniona po przejściu na dowód stawki (z wyjątkiem wartości zwracanej przez kod operacji DIFFICULTY).
9 Model wykonawczy
Ta sekcja (str. 14-16) zawiera większość definicji EVM.
Termin stan systemu (system state) obejmuje wszystko, co musisz wiedzieć o systemie, aby go uruchomić. W typowym komputerze oznacza to pamięć, zawartość rejestrów itp.
Maszyna Turinga (otwiera się w nowej karcie) to model obliczeniowy. Zasadniczo jest to uproszczona wersja komputera, o której udowodniono, że ma taką samą zdolność do wykonywania obliczeń, jak normalny komputer (wszystko, co komputer może obliczyć, maszyna Turinga również może obliczyć i odwrotnie). Ten model ułatwia udowadnianie różnych twierdzeń na temat tego, co jest, a co nie jest obliczalne.
Termin kompletność w sensie Turinga (otwiera się w nowej karcie) (Turing-complete) 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 może, ponieważ zabrakłoby jej gazu, więc jest tylko quasi-kompletna w sensie Turinga.
9.1 Podstawy
Ta sekcja przedstawia podstawy EVM i jej porównanie z innymi modelami obliczeniowymi.
Maszyna stosowa (otwiera się w nowej karcie) to komputer, który przechowuje dane pośrednie nie w rejestrach, ale na stosie (otwiera się w nowej karcie). Jest to preferowana architektura dla maszyn wirtualnych, ponieważ jest łatwa do wdrożenia, 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 operacji są wykonywane, zazwyczaj pobierają swoje parametry ze stosu. Istnieją kody operacji przeznaczone specjalnie do reorganizacji elementów na stosie, takie jak POP (usuwa element ze szczytu stosu), DUP_N (duplikuje N-ty element na stosie) itp.
EVM posiada również ulotną przestrzeń zwaną pamięcią (memory), która służy do przechowywania danych podczas wykonywania. Ta pamięć jest zorganizowana w 32-bajtowe słowa. Wszystkie lokalizacje w pamięci są inicjowane zerami. Jeśli wykonasz ten kod Yul (otwiera się w nowej karcie), aby dodać słowo do pamięci, wypełni on 32 bajty pamięci, uzupełniając puste miejsce w słowie zerami, tj. utworzy jedno słowo - z zerami w lokalizacjach 0-29, 0x60 w 30 i 0xA7 w 31.
mstore(0, 0x60A7)
mstore to jeden z trzech kodów operacji, które EVM udostępnia do interakcji z pamięcią - ładuje on 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ż oddzielny, nieulotny model pamięci masowej (storage), który jest utrzymywany jako część stanu systemu - ta pamięć jest zorganizowana w tablice słów (w przeciwieństwie do adresowalnych słowami tablic bajtów na stosie). W tej pamięci masowej kontrakty przechowują trwałe dane - kontrakt może wchodzić w interakcje tylko z własną pamięcią masową. Pamięć masowa jest zorganizowana w mapowania klucz-wartość.
Chociaż nie wspomniano o tym w tej sekcji żółtej księgi, warto również wiedzieć, że istnieje czwarty rodzaj pamięci. Dane wywołania (calldata) to adresowalna bajtowo pamięć tylko do odczytu, używana do przechowywania wartości przekazanej z parametrem data transakcji. EVM posiada specyficzne kody operacji do zarządzania calldata. calldatasize zwraca rozmiar danych. calldataload ładuje dane na stos. calldatacopy kopiuje dane do pamięci.
Standardowa architektura von Neumanna (otwiera się w nowej karcie) przechowuje kod i dane w tej samej pamięci. EVM nie podąża za tym standardem ze względów bezpieczeństwa - współdzielenie pamięci ulotnej umożliwia zmianę kodu programu. Zamiast tego kod jest zapisywany w pamięci masowej.
Istnieją tylko dwa przypadki, w których kod jest wykonywany z pamięci:
- Kiedy kontrakt tworzy inny kontrakt (używając
CREATE(otwiera się w nowej karcie) lubCREATE2(otwiera się w nowej karcie)), 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 wyjątkowe wykonanie (exceptional execution) oznacza wyjątek, który powoduje zatrzymanie wykonywania bieżącego kontraktu.
9.2 Przegląd opłat
Ta sekcja wyjaśnia, jak obliczane są opłaty za gaz. Istnieją trzy koszty:
Koszt kodu operacji
Nieodłączny koszt konkretnego kodu operacji. Aby uzyskać tę wartość, znajdź grupę kosztów kodu operacji w Dodatku H (str. 29, pod równaniem (329)) i znajdź grupę kosztów w równaniu (326). Daje to funkcję kosztu, która w większości przypadków używa parametrów z Dodatku G (str. 28).
Na przykład kod operacji CALLDATACOPY (otwiera się w nowej karcie) jest członkiem grupy Wcopy. Koszt kodu operacji 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 (ceiling function), 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 str. 3, μ to stan maszyny. Stan maszyny jest zdefiniowany w sekcji 9.4.1 na str. 15. Zgodnie z tą sekcją, jednym z parametrów stanu maszyny jest s dla stosu. Biorąc to wszystko pod uwagę, wydaje się, że μs[2] to lokalizacja nr 2 na stosie. Patrząc na kod operacji (otwiera się w nowej karcie), lokalizacja nr 2 na stosie to rozmiar danych w bajtach. Patrząc na inne kody operacji w grupie Wcopy, CODECOPY (otwiera się w nowej karcie) i RETURNDATACOPY (otwiera się w nowej karcie), one również mają rozmiar danych w tej samej lokalizacji. Zatem ⌈μs[2]÷32⌉ to liczba 32-bajtowych słów wymaganych do przechowania kopiowanych danych. Podsumowując, nieodłączny koszt CALLDATACOPY (otwiera się w nowej karcie) to 3 jednostki gazu plus 3 za każde kopiowane słowo danych.
Koszt uruchomienia
Koszt uruchomienia kodu, który wywołujemy.
- W przypadku
CREATE(otwiera się w nowej karcie) iCREATE2(otwiera się w nowej karcie), konstruktor dla nowego kontraktu. - W przypadku
CALL(otwiera się w nowej karcie),CALLCODE(otwiera się w nowej karcie),STATICCALL(otwiera się w nowej karcie) lubDELEGATECALL(otwiera się w nowej karcie), kontrakt, który wywołujemy.
Koszt rozszerzenia pamięci
Koszt rozszerzenia pamięci (jeśli jest to konieczne).
W równaniu 326 ta wartość 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 operacji, a μi' to liczba słów w pamięci po kodzie operacji.
Funkcja Cmem jest zdefiniowana w równaniu 328: Cmem(a) = Gmemory × a + ⌊a2 ÷ 512⌋. ⌊x⌋ to funkcja podłogi (floor function), 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. Kiedy 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. Poza tym punktem ⌊a2 ÷ 512⌋ jest dodatnie. Kiedy wymagana pamięć jest wystarczająco duża, koszt gazu jest proporcjonalny do kwadratu ilości pamięci.
Uwaga, że te czynniki wpływają tylko na nieodłączny koszt gazu - nie biorą pod uwagę rynku opłat ani napiwków dla walidatorów, które określają, ile użytkownik końcowy musi zapłacić - jest to tylko surowy koszt uruchomienia konkretnej operacji w EVM.
9.3 Środowisko wykonawcze
Środowisko wykonawcze to krotka (tuple), I, która zawiera informacje niebędące częścią stanu blockchaina ani EVM.
| Parametr | Kod operacji dostępu do danych | Kod Solidity dostępu do danych |
|---|---|---|
| Ia | ADDRESS (otwiera się w nowej karcie) | address(this) |
| Io | ORIGIN (otwiera się w nowej karcie) | tx.origin |
| Ip | GASPRICE (otwiera się w nowej karcie) | tx.gasprice |
| Id | CALLDATALOAD (otwiera się w nowej karcie) itp. | msg.data |
| Is | CALLER (otwiera się w nowej karcie) | msg.sender |
| Iv | CALLVALUE (otwiera się w nowej karcie) | msg.value |
| Ib | CODECOPY (otwiera się w nowej karcie) | address(this).code |
| IH | Pola nagłówka bloku, takie jak NUMBER (otwiera się w nowej karcie) i DIFFICULTY (otwiera się w nowej karcie) | block.number, block.difficulty itp. |
| Ie | Głębokość stosu wywołań dla wywołań między kontraktami (w tym tworzenia kontraktów) | |
| Iw | Czy EVM ma pozwolenie na zmianę stanu, czy działa statycznie |
Kilka innych parametrów jest niezbędnych do zrozumienia reszty sekcji 9:
| Parametr | Zdefiniowano w sekcji | Znaczenie |
|---|---|---|
| σ | 2 (str. 2, równanie 1) | Stan blockchaina |
| g | 9.3 (str. 14) | Pozostały gaz |
| A | 6.1 (str. 9) | Narosły podstan (zmiany zaplanowane na moment zakończenia transakcji) |
| o | 9.3 (str. 14) | Wyjście - zwrócony wynik w przypadku transakcji wewnętrznej (gdy jeden kontrakt wywołuje inny) i wywołań funkcji widoku (gdy po prostu prosisz 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 wreszcie zacząć pracę nad tym, jak działa EVM.
Równania 146-151 dają 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ęć, zainicjowana samymi zerami |
| μi | 0 | Najwyższa użyta lokalizacja pamięci |
| μs | () | Stos, początkowo pusty |
| μo | ∅ | Wyjście, zbiór pusty, dopóki nie zatrzymamy się ze zwracanymi danymi (RETURN (otwiera się w nowej karcie) lub REVERT (otwiera się w nowej karcie)) lub bez nich (STOP (otwiera się w nowej karcie) lub SELFDESTRUCT (otwiera się w nowej karcie)). |
Równanie 152 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 wyjątkowe zatrzymanie). Jeśli jej wynikiem jest Prawda (True), nowy stan jest identyczny ze starym (z wyjątkiem tego, że gaz zostaje spalony), ponieważ zmiany nie zostały wdrożone.- Jeśli wykonywanym kodem operacji jest
REVERT(otwiera się w nowej karcie), nowy stan jest taki sam jak stary stan, a część gazu zostaje utracona. - Jeśli sekwencja operacji jest zakończona, co sygnalizuje
RETURN(otwiera się w nowej karcie)), stan jest aktualizowany do nowego stanu. - Jeśli nie znajdujemy się w żadnym z warunków końcowych 1-3, kontynuuj działanie.
9.4.1 Stan maszyny
Ta sekcja wyjaśnia stan maszyny bardziej szczegółowo. Określa, że w to bieżący kod operacji. Jeśli μpc jest mniejsze niż ||Ib||, długość kodu, to ten bajt (Ib[μpc]) jest kodem operacji. W przeciwnym razie kod operacji jest zdefiniowany jako STOP (otwiera się w nowej karcie).
Ponieważ jest to maszyna stosowa (otwiera się w nowej karcie), musimy śledzić liczbę elementów zdjętych (δ) i odłożonych (α) przez każdy kod operacji.
9.4.2 Wyjątkowe zatrzymanie
Ta sekcja definiuje funkcję Z, która określa, kiedy mamy do czynienia z nieprawidłowym zakończeniem. Jest to funkcja logiczna (Boolean) (otwiera się w nowej karcie), więc używa ∨ dla logicznego lub (otwiera się w nowej karcie) oraz ∧ dla logicznego i (otwiera się w nowej karcie).
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 określająca koszt gazu. Nie ma wystarczającej ilości gazu, aby pokryć następny kod operacji.
-
δw=∅ Jeśli liczba elementów zdjętych ze stosu dla kodu operacji jest niezdefiniowana, to sam kod operacji jest niezdefiniowany.
-
|| μs || < δw Niedomiar stosu (stack underflow), niewystarczająca liczba elementów na stosie dla bieżącego kodu operacji.
-
w = JUMP ∧ μs[0]∉D(Ib) Kodem operacji jest
JUMP(otwiera się w nowej karcie), a adres nie jestJUMPDEST(otwiera się w nowej karcie). Skoki są ważne tylko wtedy, gdy miejscem docelowym jestJUMPDEST(otwiera się w nowej karcie). -
w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) Kodem operacji jest
JUMPI(otwiera się w nowej karcie), warunek jest prawdziwy (niezerowy), więc skok powinien nastąpić, a adres nie jestJUMPDEST(otwiera się w nowej karcie). Skoki są ważne tylko wtedy, gdy miejscem docelowym jestJUMPDEST(otwiera się w nowej karcie). -
w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || Kodem operacji jest
RETURNDATACOPY(otwiera się w nowej karcie). W tym kodzie operacji element stosu μs[1] to przesunięcie (offset), od którego należy czytać w buforze zwracanych danych, a element stosu μs[2] to długość danych. Ten warunek występuje, gdy próbujesz czytać poza końcem bufora zwracanych danych. Zauważ, że nie ma podobnego warunku dla danych wywołania (calldata) ani dla samego kodu. Kiedy próbujesz czytać poza końcem tych buforów, po prostu otrzymujesz zera. -
|| μs || - δw + αw > 1024
Przepełnienie stosu. Jeśli uruchomienie kodu operacji spowoduje powstanie stosu o wielkości ponad 1024 elementów, przerwij.
-
¬Iw ∧ W(w,μ) Czy działamy statycznie (¬ to negacja (otwiera się w nowej karcie), a Iw jest prawdziwe, gdy mamy pozwolenie na zmianę stanu blockchaina)? Jeśli tak, a próbujemy wykonać operację zmieniającą stan, nie może się to wydarzyć.
Funkcja W(w,μ) jest zdefiniowana później w równaniu 159. W(w,μ) jest prawdziwe, jeśli jeden z tych warunków jest prawdziwy:
-
w ∈ {CREATE, CREATE2, SSTORE, SELFDESTRUCT} Te kody operacji 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 logów. Wszystkie kody operacji logów znajdują się w przedziale od
LOG0(A0) (otwiera się w nowej karcie) doLOG4(A4) (otwiera się w nowej karcie). Liczba po kodzie operacji logu określa, ile tematów (topics) zawiera wpis logu. -
w=CALL ∧ μs[2]≠0 Możesz wywołać inny kontrakt, gdy jesteś statyczny, ale jeśli to zrobisz, nie możesz przetransferować do niego ETH.
-
-
w = SSTORE ∧ μg ≤ Gcallstipend Nie możesz uruchomić
SSTORE(otwiera się w nowej karcie), chyba że masz więcej niż Gcallstipend (zdefiniowane jako 2300 w Dodatku G) gazu.
9.4.3 Ważność miejsca docelowego skoku
Tutaj formalnie definiujemy, czym są kody operacji JUMPDEST (otwiera się w nowej karcie). Nie możemy po prostu szukać wartości bajtu 0x5B, ponieważ może on znajdować się wewnątrz PUSH (i w związku z tym być danymi, a nie kodem operacji).
W równaniu (162) definiujemy funkcję N(i,w). Pierwszy parametr, i, to lokalizacja kodu operacji. Drugi, w, to sam kod operacji. Jeśli w∈[PUSH1, PUSH32], oznacza to, że kodem operacji jest PUSH (nawiasy kwadratowe definiują przedział, który obejmuje punkty końcowe). W takim przypadku następny kod operacji znajduje się pod adresem i+2+(w−PUSH1). Dla PUSH1 (otwiera się w nowej karcie) musimy przesunąć się o dwa bajty (sam PUSH i jednobajtowa wartość), dla PUSH2 (otwiera się w nowej karcie) musimy przesunąć się o trzy bajty, ponieważ jest to wartość dwubajtowa itp. Wszystkie inne kody operacji EVM mają długość tylko jednego bajtu, więc we wszystkich innych przypadkach N(i,w)=i+1.
Ta funkcja jest używana w równaniu (161) do zdefiniowania DJ(c,i), co jest zbiorem (otwiera się w nowej karcie) wszystkich ważnych miejsc docelowych skoku w kodzie c, zaczynając od lokalizacji kodu operacji i. Ta funkcja jest zdefiniowana rekurencyjnie. Jeśli i≥||c||, oznacza to, że jesteśmy na końcu lub za końcem kodu. Nie znajdziemy już więcej miejsc docelowych skoku, więc po prostu zwracamy zbiór pusty.
We wszystkich innych przypadkach patrzymy na resztę kodu, przechodząc do następnego kodu operacji i pobierając zbiór zaczynający się od niego. c[i] to bieżący kod operacji, więc N(i,c[i]) to lokalizacja następnego kodu operacji. DJ(c,N(i,c[i])) to zatem zbiór ważnych miejsc docelowych skoku, który zaczyna się od następnego kodu operacji. Jeśli bieżący kod operacji nie jest JUMPDEST, po prostu zwróć ten zbiór. Jeśli jest to JUMPDEST, dołącz go do zbioru wynikowego 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 operacji zatrzymania, zwróć ∅, zbiór pusty. Zgodnie z konwencją, ta wartość jest interpretowana jako logiczny fałsz.
- Jeśli mamy kod operacji zatrzymania, który nie generuje wyjścia (albo
STOP(otwiera się w nowej karcie), alboSELFDESTRUCT(otwiera się w nowej karcie)), zwróć sekwencję o rozmiarze zero bajtów jako wartość zwracaną. Zauważ, że bardzo różni się to od zbioru pustego. Ta wartość oznacza, że EVM naprawdę się zatrzymała, po prostu nie ma żadnych zwracanych danych do odczytania. - Jeśli mamy kod operacji zatrzymania, który generuje wyjście (albo
RETURN(otwiera się w nowej karcie), alboREVERT(otwiera się w nowej karcie)), zwróć sekwencję bajtów określoną przez ten kod operacji. Ta sekwencja jest pobierana z pamięci, wartość na szczycie stosu (μs[0]) to pierwszy bajt, a wartość po niej (μs[1]) to długość.
H.2 Zestaw instrukcji
Zanim przejdziemy do ostatniej podsekcji EVM, 9.5, przyjrzyjmy się samym instrukcjom. Są one zdefiniowane w Dodatku H.2, który zaczyna się na str. 30. Oczekuje się, że wszystko, co nie zostało określone jako zmieniające się wraz z tym konkretnym kodem operacji, pozostanie takie samo. Zmienne, które ulegają zmianie, są określone jako <coś>′.
Na przykład spójrzmy na kod operacji ADD (otwiera się w nowej karcie).
| 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 z powrotem. W tym przypadku jedna, suma.
Zatem nowy szczyt stosu (μ′s[0]) to suma starego szczytu stosu (μs[0]) i starej wartości pod nim (μs[1]).
Zamiast przeglądać wszystkie kody operacji w postaci nużącej listy, ten artykuł wyjaśnia tylko te kody operacji, które wprowadzają coś nowego.
| Wartość | Mnemonik | δ | α | Opis |
|---|---|---|---|---|
| 0x20 | KECCAK256 | 2 | 1 | Oblicz hash Keccak-256. |
| μ′s[0] ≡ KEC(μm[μs[0] . . . (μs[0] + μs[1] − 1)]) | ||||
| μ′i ≡ M(μi,μs[0],μs[1]) |
Jest to pierwszy kod operacji, 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 330 na str. 30.
| Wartość | Mnemonik | δ | α | Opis |
|---|---|---|---|---|
| 0x31 | BALANCE | 1 | 1 | Pobierz saldo podanego konta. |
| ... |
Adres, którego saldo musimy znaleźć, to μs[0] mod 2160. Szczyt stosu to adres, ale ponieważ adresy mają tylko 160 bitów, obliczamy wartość modulo (otwiera się w nowej karcie) 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 niezainicjowany, a saldo wynosi zero. Listę pól informacji o koncie można zobaczyć w sekcji 4.1 na str. 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 masowej (pamięci masowej, do której niedawno uzyskano dostęp i która prawdopodobnie znajduje się w pamięci podręcznej) a zimnej pamięci masowej (pamięci masowej, do której nie uzyskano dostępu i która prawdopodobnie znajduje się w wolniejszej pamięci masowej, której pobranie jest droższe). Aa to lista adresów, do których transakcja uzyskała wcześniej dostęp, a zatem dostęp do nich powinien być tańszy, jak zdefiniowano w sekcji 6.1 na str. 9. Możesz przeczytać więcej na ten temat w EIP-2929 (otwiera się w nowej karcie).
| Wartość | Mnemonik | δ | α | Opis |
|---|---|---|---|---|
| 0x8F | DUP16 | 16 | 17 | Zduplikuj 16. element stosu. |
| μ′s[0] ≡ μs[15] |
Zauważ, ż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> (otwiera się w nowej karcie) i SWAP<n> (otwiera się w nowej karcie) oznacza to konieczność zdjęcia, a następnie odłożenia do szesnastu wartości.
9.5 Cykl wykonania
Teraz, gdy mamy już wszystkie części, możemy wreszcie zrozumieć, jak udokumentowany jest cykl wykonania EVM.
Równanie (164) 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 (165)-(167) definiują stos i jego zmianę spowodowaną kodem operacji (μs). Równanie (168) to zmiana gazu (μg). Równanie (169) to zmiana licznika programu (μpc). Wreszcie równania (170)-(173) określają, że pozostałe parametry pozostają takie same, chyba że zostaną wyraźnie zmienione przez kod operacji.
Dzięki temu EVM jest w pełni zdefiniowana.
Wnioski
Notacja matematyczna jest precyzyjna i pozwoliła żółtej księdze określić każdy szczegół Ethereum. Ma jednak pewne wady:
- Może być zrozumiana tylko przez ludzi, co oznacza, że testy zgodności (otwiera się w nowej karcie) muszą być pisane ręcznie.
- Programiści rozumieją kod komputerowy. Mogą, ale nie muszą rozumieć notacji matematycznej.
Być może z tych powodów nowsze specyfikacje warstwy konsensusu (otwiera się w nowej karcie) są napisane w języku Python. Istnieją specyfikacje warstwy wykonawczej w języku Python (otwiera się w nowej karcie), ale nie są one kompletne. Dopóki cała żółta księga nie zostanie również przetłumaczona na język Python lub podobny, żółta księga będzie nadal w użyciu i warto umieć ją czytać.