Porozumění specifikacím EVM v yellow paperu
Yellow paper (otevře se v nové kartě) je formální specifikace Etherea. Kromě případů, kdy je pozměněn procesem EIP, obsahuje přesný popis toho, jak vše funguje. Je napsán jako matematický článek, což zahrnuje terminologii, která programátorům nemusí být povědomá. V tomto článku se dozvíte, jak jej číst, a v širším smyslu i další související matematické články.
Který yellow paper?
Jako téměř všechno ostatní v Ethereu se i yellow paper postupem času vyvíjí. Abych mohl odkazovat na konkrétní verzi, nahrál jsem aktuální verzi v době psaní (otevře se v nové kartě). Čísla sekcí, stránek a rovnic, která používám, budou odkazovat na tuto verzi. Při čtení tohoto dokumentu je dobré mít jej otevřený v jiném okně.
Proč EVM?
Původní yellow paper byl napsán hned na začátku vývoje Etherea. Popisuje původní mechanismus konsensu založený na důkazu prací (PoW), který se původně používal k zabezpečení sítě. Ethereum však v září 2022 vypnulo důkaz prací a začalo používat konsensus založený na důkazu podílem (PoS). Tento tutoriál se zaměří na části yellow paperu definující virtuální stroj Etherea (EVM). EVM zůstal přechodem na důkaz podílem nezměněn (s výjimkou návratové hodnoty operačního kódu DIFFICULTY).
9 Model provádění
Tato sekce (str. 14-16) obsahuje většinu definice EVM.
Pojem stav systému (system state) zahrnuje vše, co potřebujete o systému vědět, abyste jej mohli spustit. V typickém počítači to znamená paměť, obsah registrů atd.
Turingův stroj (otevře se v nové kartě) je výpočetní model. V podstatě se jedná o zjednodušenou verzi počítače, u které je dokázáno, že má stejnou schopnost provádět výpočty jako běžný počítač (vše, co dokáže spočítat počítač, dokáže spočítat i Turingův stroj a naopak). Tento model usnadňuje dokazování různých teorémů o tom, co je a co není vyčíslitelné.
Pojem Turingovsky úplný (otevře se v nové kartě) (Turing-complete) označuje počítač, který dokáže provádět stejné výpočty jako Turingův stroj. Turingovy stroje se mohou dostat do nekonečných smyček, což EVM nemůže, protože by mu došel gas, takže je pouze kvazi-Turingovsky úplný.
9.1 Základy
Tato sekce popisuje základy EVM a jeho srovnání s jinými výpočetními modely.
Zásobníkový stroj (otevře se v nové kartě) je počítač, který neukládá mezivýsledky do registrů, ale do zásobníku (otevře se v nové kartě). Toto je preferovaná architektura pro virtuální stroje, protože se snadno implementuje, což znamená, že chyby a bezpečnostní zranitelnosti jsou mnohem méně pravděpodobné. Paměť v zásobníku je rozdělena na 256bitová slova. To bylo zvoleno, protože je to výhodné pro základní kryptografické operace Etherea, jako je hashování Keccak-256 a výpočty na eliptické křivce. Maximální velikost zásobníku je 1024 položek (1024 x 256 bitů). Když se provádějí operační kódy, obvykle získávají své parametry ze zásobníku. Existují operační kódy určené speciálně pro reorganizaci prvků v zásobníku, jako je POP (odstraní položku z vrcholu zásobníku), DUP_N (duplikuje N-tou položku v zásobníku) atd.
EVM má také volatilní prostor zvaný paměť (memory), který se používá k ukládání dat během provádění. Tato paměť je organizována do 32bajtových slov. Všechna paměťová místa jsou inicializována na nulu. Pokud spustíte tento kód v jazyce Yul (otevře se v nové kartě) pro přidání slova do paměti, zaplní 32 bajtů paměti tím, že prázdné místo ve slově vyplní nulami, tj. vytvoří jedno slovo – s nulami na pozicích 0-29, 0x60 na 30 a 0xA7 na 31.
mstore(0, 0x60A7)
mstore je jedním ze tří operačních kódů, které EVM poskytuje pro interakci s pamětí – načte slovo do paměti. Další dva jsou mstore8, který načte jeden bajt do paměti, a mload, který přesune slovo z paměti do zásobníku.
EVM má také samostatný nevolatilní model úložiště (storage), který je udržován jako součást stavu systému – tato paměť je organizována do polí slov (na rozdíl od polí bajtů adresovatelných po slovech v zásobníku). V tomto úložišti kontrakty uchovávají trvalá data – kontrakt může interagovat pouze se svým vlastním úložištěm. Úložiště je organizováno v mapování klíč-hodnota.
Ačkoli to v této sekci yellow paperu není zmíněno, je také užitečné vědět, že existuje čtvrtý typ paměti. Data volání (calldata) je bajtově adresovatelná paměť pouze pro čtení, která se používá k uložení hodnoty předané s parametrem data transakce. EVM má specifické operační kódy pro správu calldata. calldatasize vrací velikost dat. calldataload načte data do zásobníku. calldatacopy zkopíruje data do paměti.
Standardní von Neumannova architektura (otevře se v nové kartě) ukládá kód a data do stejné paměti. EVM se tímto standardem z bezpečnostních důvodů neřídí – sdílení volatilní paměti by umožnilo měnit kód programu. Místo toho se kód ukládá do úložiště.
Existují pouze dva případy, kdy se kód spouští z paměti:
- Když kontrakt vytvoří jiný kontrakt (pomocí
CREATE(otevře se v nové kartě) neboCREATE2(otevře se v nové kartě)), kód pro konstruktor kontraktu pochází z paměti. - Během vytváření jakéhokoli kontraktu se spustí kód konstruktoru a poté se vrátí s kódem samotného kontraktu, rovněž z paměti.
Pojem výjimečné provedení (exceptional execution) znamená výjimku, která způsobí zastavení provádění aktuálního kontraktu.
9.2 Přehled poplatků
Tato sekce vysvětluje, jak se počítají poplatky za gas. Existují tři druhy nákladů:
Cena operačního kódu
Základní cena konkrétního operačního kódu. Chcete-li získat tuto hodnotu, najděte nákladovou skupinu operačního kódu v příloze H (str. 29, pod rovnicí (329)) a najděte nákladovou skupinu v rovnici (326). Tím získáte nákladovou funkci, která ve většině případů používá parametry z přílohy G (str. 28).
Například operační kód CALLDATACOPY (otevře se v nové kartě) je členem skupiny Wcopy. Cena operačního kódu pro tuto skupinu je Gverylow+Gcopy×⌈μs[2]÷32⌉. Při pohledu do přílohy G vidíme, že obě konstanty jsou 3, což nám dává 3+3×⌈μs[2]÷32⌉.
Stále musíme rozluštit výraz ⌈μs[2]÷32⌉. Vnější část, ⌈ <hodnota> ⌉ je funkce horní celá část (ceiling), funkce, která pro danou hodnotu vrací nejmenší celé číslo, které stále není menší než tato hodnota. Například ⌈2.5⌉ = ⌈3⌉ = 3. Vnitřní část je μs[2]÷32. Podíváme-li se na sekci 3 (Konvence) na str. 3, μ je stav stroje. Stav stroje je definován v sekci 9.4.1 na str. 15. Podle této sekce je jedním z parametrů stavu stroje s pro zásobník. Když to dáme všechno dohromady, zdá se, že μs[2] je pozice č. 2 v zásobníku. Podíváme-li se na operační kód (otevře se v nové kartě), pozice č. 2 v zásobníku je velikost dat v bajtech. Podíváme-li se na další operační kódy ve skupině Wcopy, CODECOPY (otevře se v nové kartě) a RETURNDATACOPY (otevře se v nové kartě), mají také velikost dat na stejné pozici. Takže ⌈μs[2]÷32⌉ je počet 32bajtových slov potřebných k uložení kopírovaných dat. Když to všechno shrneme, základní cena CALLDATACOPY (otevře se v nové kartě) je 3 gas plus 3 za každé kopírované slovo dat.
Cena za spuštění
Cena za spuštění kódu, který voláme.
- V případě
CREATE(otevře se v nové kartě) aCREATE2(otevře se v nové kartě) jde o konstruktor nového kontraktu. - V případě
CALL(otevře se v nové kartě),CALLCODE(otevře se v nové kartě),STATICCALL(otevře se v nové kartě) neboDELEGATECALL(otevře se v nové kartě) jde o kontrakt, který voláme.
Cena za rozšíření paměti
Cena za rozšíření paměti (pokud je to nutné).
V rovnici 326 je tato hodnota zapsána jako Cmem(μi')-Cmem(μi). Podíváme-li se znovu na sekci 9.4.1, vidíme, že μi je počet slov v paměti. Takže μi je počet slov v paměti před operačním kódem a μi' je počet slov v paměti po operačním kódu.
Funkce Cmem je definována v rovnici 328: Cmem(a) = Gmemory × a + ⌊a2 ÷ 512⌋. ⌊x⌋ je funkce dolní celá část (floor), funkce, která pro danou hodnotu vrací největší celé číslo, které stále není větší než tato hodnota. Například ⌊2.5⌋ = ⌊2⌋ = 2. Když a < √512, a2 < 512 a výsledek funkce dolní celá část je nula. Takže pro prvních 22 slov (704 bajtů) cena roste lineárně s počtem požadovaných paměťových slov. Za tímto bodem je ⌊a2 ÷ 512⌋ kladné. Když je požadovaná paměť dostatečně velká, cena za gas je úměrná druhé mocnině množství paměti.
Poznámka: Tyto faktory ovlivňují pouze základní cenu za gas – nezohledňují trh s poplatky ani spropitné pro validátory, které určují, kolik musí koncový uživatel zaplatit – jedná se pouze o hrubou cenu za spuštění konkrétní operace v EVM.
9.3 Prostředí provádění
Prostředí provádění (execution environment) je n-tice, I, která obsahuje informace, jež nejsou součástí stavu blockchainu ani EVM.
| Parametr | Operační kód pro přístup k datům | Kód v Solidity pro přístup k datům |
|---|---|---|
| Ia | ADDRESS (otevře se v nové kartě) | address(this) |
| Io | ORIGIN (otevře se v nové kartě) | tx.origin |
| Ip | GASPRICE (otevře se v nové kartě) | tx.gasprice |
| Id | CALLDATALOAD (otevře se v nové kartě) atd. | msg.data |
| Is | CALLER (otevře se v nové kartě) | msg.sender |
| Iv | CALLVALUE (otevře se v nové kartě) | msg.value |
| Ib | CODECOPY (otevře se v nové kartě) | address(this).code |
| IH | Pole hlavičky bloku, jako je NUMBER (otevře se v nové kartě) a DIFFICULTY (otevře se v nové kartě) | block.number, block.difficulty atd. |
| Ie | Hloubka zásobníku volání pro volání mezi kontrakty (včetně vytvoření kontraktu) | |
| Iw | Zda má EVM povoleno měnit stav, nebo zda běží staticky |
K pochopení zbytku sekce 9 je nezbytných několik dalších parametrů:
| Parametr | Definováno v sekci | Význam |
|---|---|---|
| σ | 2 (str. 2, rovnice 1) | Stav blockchainu |
| g | 9.3 (str. 14) | Zbývající gas |
| A | 6.1 (str. 9) | Nahromaděný podstav (změny naplánované na konec transakce) |
| o | 9.3 (str. 14) | Výstup – vrácený výsledek v případě interní transakce (když jeden kontrakt volá jiný) a volání view funkcí (když pouze žádáte o informace, takže není nutné čekat na transakci) |
9.4 Přehled provádění
Nyní, když máme všechny přípravy za sebou, můžeme konečně začít pracovat na tom, jak EVM funguje.
Rovnice 146-151 nám dávají počáteční podmínky pro spuštění EVM:
| Symbol | Počáteční hodnota | Význam |
|---|---|---|
| μg | g | Zbývající gas |
| μpc | 0 | Čítač programu (program counter), adresa další instrukce k provedení |
| μm | (0, 0, ...) | Paměť, inicializovaná na samé nuly |
| μi | 0 | Nejvyšší použité místo v paměti |
| μs | () | Zásobník, zpočátku prázdný |
| μo | ∅ | Výstup, prázdná množina, dokud se nezastavíme buď s návratovými daty (RETURN (otevře se v nové kartě) nebo REVERT (otevře se v nové kartě)), nebo bez nich (STOP (otevře se v nové kartě) nebo SELFDESTRUCT (otevře se v nové kartě)). |
Rovnice 152 nám říká, že v každém okamžiku během provádění existují čtyři možné podmínky a co s nimi dělat:
Z(σ,μ,A,I). Z představuje funkci, která testuje, zda operace vytváří neplatný přechod stavu (viz výjimečné zastavení). Pokud se vyhodnotí jako pravda (True), nový stav je identický se starým (kromě toho, že se spálí gas), protože změny nebyly implementovány.- Pokud je prováděným operačním kódem
REVERT(otevře se v nové kartě), nový stav je stejný jako starý stav, část gasu je ztracena. - Pokud je sekvence operací dokončena, což je signalizováno pomocí
RETURN(otevře se v nové kartě)), stav se aktualizuje na nový stav. - Pokud se nenacházíme v jedné z koncových podmínek 1-3, pokračujte v provádění.
9.4.1 Stav stroje
Tato sekce podrobněji vysvětluje stav stroje. Specifikuje, že w je aktuální operační kód. Pokud je μpc menší než ||Ib||, což je délka kódu, pak je tento bajt (Ib[μpc]) operačním kódem. V opačném případě je operační kód definován jako STOP (otevře se v nové kartě).
Jelikož se jedná o zásobníkový stroj (otevře se v nové kartě), musíme sledovat počet položek vyjmutých (δ) a vložených (α) každým operačním kódem.
9.4.2 Výjimečné zastavení
Tato sekce definuje funkci Z, která specifikuje, kdy dojde k abnormálnímu ukončení. Jedná se o booleovskou (otevře se v nové kartě) funkci, takže používá ∨ pro logické nebo (OR) (otevře se v nové kartě) a ∧ pro logické a (AND) (otevře se v nové kartě).
K výjimečnému zastavení (exceptional halt) dojde, pokud je splněna kterákoli z těchto podmínek:
-
μg < C(σ,μ,A,I) Jak jsme viděli v sekci 9.2, C je funkce, která specifikuje cenu za gas. Nezbývá dostatek gasu na pokrytí dalšího operačního kódu.
-
δw=∅ Pokud je počet položek vyjmutých ze zásobníku pro operační kód nedefinovaný, pak je nedefinovaný i samotný operační kód.
-
|| μs || < δw Podtečení zásobníku (stack underflow), v zásobníku není dostatek položek pro aktuální operační kód.
-
w = JUMP ∧ μs[0]∉D(Ib) Operační kód je
JUMP(otevře se v nové kartě) a adresa neníJUMPDEST(otevře se v nové kartě). Skoky jsou platné pouze tehdy, když je cílemJUMPDEST(otevře se v nové kartě). -
w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) Operační kód je
JUMPI(otevře se v nové kartě), podmínka je pravdivá (nenulová), takže by mělo dojít ke skoku, a adresa neníJUMPDEST(otevře se v nové kartě). Skoky jsou platné pouze tehdy, když je cílemJUMPDEST(otevře se v nové kartě). -
w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || Operační kód je
RETURNDATACOPY(otevře se v nové kartě). V tomto operačním kódu je prvek zásobníku μs[1] offset, od kterého se má číst ve vyrovnávací paměti návratových dat, a prvek zásobníku μs[2] je délka dat. Tato podmínka nastane, když se pokusíte číst za koncem vyrovnávací paměti návratových dat. Všimněte si, že pro data volání (calldata) nebo pro samotný kód podobná podmínka neexistuje. Když se pokusíte číst za koncem těchto vyrovnávacích pamětí, dostanete jen nuly. -
|| μs || - δw + αw > 1024
Přetečení zásobníku. Pokud spuštění operačního kódu povede k zásobníku s více než 1024 položkami, dojde k přerušení.
-
¬Iw ∧ W(w,μ) Běžíme staticky (¬ je negace (otevře se v nové kartě) a Iw je pravda, když máme povoleno měnit stav blockchainu)? Pokud ano a pokoušíme se o operaci měnící stav, nemůže k ní dojít.
Funkce W(w,μ) je definována později v rovnici 159. W(w,μ) je pravda, pokud je splněna jedna z těchto podmínek:
-
w ∈ {CREATE, CREATE2, SSTORE, SELFDESTRUCT} Tyto operační kódy mění stav, ať už vytvořením nového kontraktu, uložením hodnoty nebo zničením aktuálního kontraktu.
-
LOG0≤w ∧ w≤LOG4 Pokud jsme voláni staticky, nemůžeme emitovat záznamy do logu. Všechny operační kódy pro logy jsou v rozsahu mezi
LOG0(A0) (otevře se v nové kartě) aLOG4(A4) (otevře se v nové kartě). Číslo za operačním kódem logu určuje, kolik témat (topics) záznam v logu obsahuje. -
w=CALL ∧ μs[2]≠0 Když jste statičtí, můžete zavolat jiný kontrakt, ale pokud tak učiníte, nemůžete na něj převést ETH.
-
-
w = SSTORE ∧ μg ≤ Gcallstipend Nemůžete spustit
SSTORE(otevře se v nové kartě), pokud nemáte více než Gcallstipend (definováno jako 2300 v příloze G) gasu.
9.4.3 Platnost cíle skoku
Zde formálně definujeme, co jsou operační kódy JUMPDEST (otevře se v nové kartě). Nemůžeme jen hledat bajtovou hodnotu 0x5B, protože by mohla být uvnitř PUSH (a tedy by šlo o data, nikoli o operační kód).
V rovnici (162) definujeme funkci N(i,w). První parametr, i, je pozice operačního kódu. Druhý, w, je samotný operační kód. Pokud w∈[PUSH1, PUSH32], znamená to, že operační kód je PUSH (hranaté závorky definují rozsah, který zahrnuje koncové body). V takovém případě je další operační kód na i+2+(w−PUSH1). Pro PUSH1 (otevře se v nové kartě) se musíme posunout o dva bajty (samotný PUSH a jednobajtová hodnota), pro PUSH2 (otevře se v nové kartě) se musíme posunout o tři bajty, protože jde o dvoubajtovou hodnotu atd. Všechny ostatní operační kódy EVM jsou dlouhé pouze jeden bajt, takže ve všech ostatních případech N(i,w)=i+1.
Tato funkce se používá v rovnici (161) k definování DJ(c,i), což je množina (otevře se v nové kartě) všech platných cílů skoku v kódu c, počínaje pozicí operačního kódu i. Tato funkce je definována rekurzivně. Pokud i≥||c||, znamená to, že jsme na konci kódu nebo za ním. Už nenajdeme žádné další cíle skoku, takže stačí vrátit prázdnou množinu.
Ve všech ostatních případech se podíváme na zbytek kódu tak, že přejdeme na další operační kód a získáme množinu začínající od něj. c[i] je aktuální operační kód, takže N(i,c[i]) je pozice dalšího operačního kódu. DJ(c,N(i,c[i])) je tedy množina platných cílů skoku, která začíná na dalším operačním kódu. Pokud aktuální operační kód není JUMPDEST, stačí vrátit tuto množinu. Pokud to je JUMPDEST, zahrňte jej do výsledné množiny a tu vraťte.
9.4.4 Normální zastavení
Funkce zastavení H může vracet tři typy hodnot.
- Pokud nejsme v operačním kódu zastavení, vraťte ∅, prázdnou množinu. Podle konvence je tato hodnota interpretována jako booleovská nepravda (false).
- Pokud máme operační kód zastavení, který neprodukuje výstup (buď
STOP(otevře se v nové kartě) neboSELFDESTRUCT(otevře se v nové kartě)), vraťte jako návratovou hodnotu sekvenci o velikosti nula bajtů. Všimněte si, že se to velmi liší od prázdné množiny. Tato hodnota znamená, že se EVM skutečně zastavil, jen nejsou k dispozici žádná návratová data ke čtení. - Pokud máme operační kód zastavení, který produkuje výstup (buď
RETURN(otevře se v nové kartě) neboREVERT(otevře se v nové kartě)), vraťte sekvenci bajtů specifikovanou tímto operačním kódem. Tato sekvence je převzata z paměti, hodnota na vrcholu zásobníku (μs[0]) je první bajt a hodnota za ní (μs[1]) je délka.
H.2 Sada instrukcí
Než přejdeme k poslední podsekci EVM, 9.5, podívejme se na samotné instrukce. Jsou definovány v příloze H.2, která začíná na str. 30. Očekává se, že cokoli, co není specifikováno jako měnící se s daným konkrétním operačním kódem, zůstane stejné. Proměnné, které se mění, jsou specifikovány jako <něco>′.
Podívejme se například na operační kód ADD (otevře se v nové kartě).
| Hodnota | Mnemotechnika | δ | α | Popis |
|---|---|---|---|---|
| 0x01 | ADD | 2 | 1 | Operace sčítání. |
| μ′s[0] ≡ μs[0] + μs[1] |
δ je počet hodnot, které vyjmeme ze zásobníku. V tomto případě dvě, protože sčítáme dvě vrchní hodnoty.
α je počet hodnot, které vložíme zpět. V tomto případě jedna, součet.
Takže nový vrchol zásobníku (μ′s[0]) je součet starého vrcholu zásobníku (μs[0]) a staré hodnoty pod ním (μs[1]).
Místo toho, abychom procházeli všechny operační kódy v nudném seznamu, tento článek vysvětluje pouze ty operační kódy, které přinášejí něco nového.
| Hodnota | Mnemotechnika | δ | α | Popis |
|---|---|---|---|---|
| 0x20 | KECCAK256 | 2 | 1 | Vypočítá hash Keccak-256. |
| μ′s[0] ≡ KEC(μm[μs[0] . . . (μs[0] + μs[1] − 1)]) | ||||
| μ′i ≡ M(μi,μs[0],μs[1]) |
Toto je první operační kód, který přistupuje k paměti (v tomto případě pouze pro čtení). Může se však rozšířit za aktuální limity paměti, takže musíme aktualizovat μi. Děláme to pomocí funkce M definované v rovnici 330 na str. 30.
| Hodnota | Mnemotechnika | δ | α | Popis |
|---|---|---|---|---|
| 0x31 | BALANCE | 1 | 1 | Získá zůstatek daného účtu. |
| ... |
Adresa, jejíž zůstatek potřebujeme zjistit, je μs[0] mod 2160. Vrchol zásobníku je adresa, ale protože adresy mají pouze 160 bitů, vypočítáme hodnotu modulo (otevře se v nové kartě) 2160.
Pokud σ[μs[0] mod 2160] ≠ ∅, znamená to, že o této adrese existují informace. V takovém případě je σ[μs[0] mod 2160]b zůstatek pro tuto adresu. Pokud σ[μs[0] mod 2160] = ∅, znamená to, že tato adresa není inicializována a zůstatek je nula. Seznam polí s informacemi o účtu si můžete prohlédnout v sekci 4.1 na str. 4.
Druhá rovnice, A'a ≡ Aa ∪ {μs[0] mod 2160}, souvisí s rozdílem v ceně mezi přístupem k teplému úložišti (úložiště, ke kterému se nedávno přistupovalo a je pravděpodobně v mezipaměti) a studenému úložišti (úložiště, ke kterému se nepřistupovalo a je pravděpodobně v pomalejším úložišti, jehož načtení je dražší). Aa je seznam adres, ke kterým transakce dříve přistupovala, a proto by k nim měl být přístup levnější, jak je definováno v sekci 6.1 na str. 9. Více si o tomto tématu můžete přečíst v EIP-2929 (otevře se v nové kartě).
| Hodnota | Mnemotechnika | δ | α | Popis |
|---|---|---|---|---|
| 0x8F | DUP16 | 16 | 17 | Duplikuje 16. položku zásobníku. |
| μ′s[0] ≡ μs[15] |
Všimněte si, že k použití jakékoli položky zásobníku ji musíme vyjmout, což znamená, že musíme vyjmout i všechny položky zásobníku nad ní. V případě DUP<n> (otevře se v nové kartě) a SWAP<n> (otevře se v nové kartě) to znamená, že musíme vyjmout a poté vložit až šestnáct hodnot.
9.5 Cyklus provádění
Nyní, když máme všechny části, můžeme konečně pochopit, jak je dokumentován cyklus provádění EVM.
Rovnice (164) říká, že pro daný stav:
- σ (globální stav blockchainu)
- μ (stav EVM)
- A (podstav, změny, které nastanou po skončení transakce)
- I (prostředí provádění)
Nový stav je (σ', μ', A', I').
Rovnice (165)-(167) definují zásobník a jeho změnu v důsledku operačního kódu (μs). Rovnice (168) je změna gasu (μg). Rovnice (169) je změna čítače programu (μpc). Nakonec rovnice (170)-(173) specifikují, že ostatní parametry zůstávají stejné, pokud nejsou výslovně změněny operačním kódem.
Tímto je EVM plně definován.
Závěr
Matematický zápis je přesný a umožnil yellow paperu specifikovat každý detail Etherea. Má však i některé nevýhody:
- Mohou mu porozumět pouze lidé, což znamená, že testy shody (otevře se v nové kartě) musí být psány ručně.
- Programátoři rozumí počítačovému kódu. Matematickému zápisu rozumět mohou, ale nemusí.
Možná z těchto důvodů jsou novější specifikace vrstvy konsensu (otevře se v nové kartě) napsány v jazyce Python. Existují specifikace exekuční vrstvy v Pythonu (otevře se v nové kartě), ale nejsou kompletní. Dokud a pokud nebude celý yellow paper také přeložen do Pythonu nebo podobného jazyka, bude yellow paper i nadále sloužit a je užitečné umět jej číst.