Zum Hauptinhalt springen

Das Yellow Paper und seine EVM-Spezifikationen verstehen

evm
Fortgeschritten
qbzzt
15. Mai 2022
19 Minuten Lesezeit

Das Yellow Paper (opens in a new tab) ist die formale Spezifikation für Ethereum. Abgesehen von Änderungen durch den EIP-Prozess enthält es die genaue Beschreibung, wie alles funktioniert. Es ist als mathematische Abhandlung verfasst, die Terminologie enthält, mit der Programmierer möglicherweise nicht vertraut sind. In diesem Artikel lernen Sie, wie man es liest, und im weiteren Sinne auch andere verwandte mathematische Abhandlungen.

Welches Yellow Paper?

Wie fast alles andere bei Ethereum entwickelt sich auch das Yellow Paper im Laufe der Zeit weiter. Um mich auf eine bestimmte Version beziehen zu können, habe ich die zum Zeitpunkt des Schreibens aktuelle Version hochgeladen. Die von mir verwendeten Abschnitts-, Seiten- und Gleichungsnummern beziehen sich auf diese Version. Es ist eine gute Idee, es in einem anderen Fenster geöffnet zu haben, während Sie dieses Dokument lesen.

Warum die EVM?

Das ursprüngliche Yellow Paper wurde direkt zu Beginn der Entwicklung von Ethereum verfasst. Es beschreibt den ursprünglichen Proof-of-Work-basierten Konsensmechanismus, der ursprünglich zur Sicherung des Netzwerks verwendet wurde. Ethereum hat jedoch Proof-of-Work abgeschaltet und im September 2022 begonnen, einen Proof-of-Stake-basierten Konsens zu verwenden. Dieses Tutorial konzentriert sich auf die Teile des Yellow Papers, die die Ethereum Virtual Machine definieren. Die EVM blieb durch den Übergang zu Proof-of-Stake unverändert (mit Ausnahme des Rückgabewerts des DIFFICULTY-Opcodes).

9 Ausführungsmodell

Dieser Abschnitt (S. 12-14) enthält den Großteil der Definition der EVM.

Der Begriff Systemzustand (system state) umfasst alles, was Sie über das System wissen müssen, um es auszuführen. Bei einem typischen Computer bedeutet dies den Speicher, den Inhalt von Registern usw.

Eine Turingmaschine (opens in a new tab) ist ein Berechnungsmodell. Im Wesentlichen handelt es sich um eine vereinfachte Version eines Computers, von der bewiesen ist, dass sie dieselbe Fähigkeit zur Ausführung von Berechnungen besitzt wie ein normaler Computer (alles, was ein Computer berechnen kann, kann auch eine Turingmaschine berechnen und umgekehrt). Dieses Modell erleichtert es, verschiedene Theoreme darüber zu beweisen, was berechenbar ist und was nicht.

Der Begriff Turing-vollständig (opens in a new tab) bezeichnet einen Computer, der dieselben Berechnungen wie eine Turingmaschine ausführen kann. Turingmaschinen können in Endlosschleifen geraten, die EVM jedoch nicht, da ihr das Gas ausgehen würde. Daher ist sie nur quasi-Turing-vollständig.

9.1 Grundlagen

Dieser Abschnitt vermittelt die Grundlagen der EVM und wie sie im Vergleich zu anderen Berechnungsmodellen abschneidet.

Eine Kellermaschine (opens in a new tab) (Stack Machine) ist ein Computer, der Zwischendaten nicht in Registern, sondern in einem Stack (Stapelspeicher) (opens in a new tab) speichert. Dies ist die bevorzugte Architektur für virtuelle Maschinen, da sie einfach zu implementieren ist, was bedeutet, dass Fehler und Sicherheitslücken viel unwahrscheinlicher sind. Der Speicher im Stack ist in 256-Bit-Wörter unterteilt. Dies wurde gewählt, weil es für die zentralen kryptografischen Operationen von Ethereum wie Keccak-256-Hashing und Berechnungen mit elliptischen Kurven praktisch ist. Die maximale Größe des Stacks beträgt 1024 Elemente (1024 x 256 Bit). Wenn Opcodes ausgeführt werden, beziehen sie ihre Parameter normalerweise aus dem Stack. Es gibt Opcodes speziell für die Neuorganisation von Elementen im Stack, wie z. B. POP (entfernt das oberste Element vom Stack), DUP_N (dupliziert das N-te Element im Stack) usw.

Die EVM verfügt außerdem über einen flüchtigen Bereich namens Memory (Speicher), der zum Speichern von Daten während der Ausführung verwendet wird. Dieser Speicher ist in 32-Byte-Wörter unterteilt. Alle Speicherorte werden mit null initialisiert. Wenn Sie diesen Yul (opens in a new tab)-Code ausführen, um dem Speicher ein Wort hinzuzufügen, füllt er 32 Byte Speicher, indem er den leeren Platz im Wort mit Nullen auffüllt, d. h. er erstellt ein Wort – mit Nullen an den Positionen 0-29, 0x60 an 30 und 0xA7 an 31.

1mstore(0, 0x60A7)

mstore ist einer von drei Opcodes, die die EVM für die Interaktion mit dem Speicher bereitstellt – er lädt ein Wort in den Speicher. Die anderen beiden sind mstore8, das ein einzelnes Byte in den Speicher lädt, und mload, das ein Wort aus dem Speicher in den Stack verschiebt.

Die EVM verfügt außerdem über ein separates, nichtflüchtiges Storage-Modell (Speicher), das als Teil des Systemzustands verwaltet wird – dieser Speicher ist in Wort-Arrays organisiert (im Gegensatz zu wortadressierbaren Byte-Arrays im Stack). In diesem Storage bewahren Smart Contracts persistente Daten auf – ein Smart Contract kann nur mit seinem eigenen Storage interagieren. Der Storage ist in Schlüssel-Wert-Zuordnungen (Key-Value Mappings) organisiert.

Obwohl es in diesem Abschnitt des Yellow Papers nicht erwähnt wird, ist es auch nützlich zu wissen, dass es eine vierte Art von Speicher gibt. Calldata ist ein byteadressierbarer Nur-Lese-Speicher, der verwendet wird, um den Wert zu speichern, der mit dem data-Parameter einer Transaktion übergeben wird. Die EVM verfügt über spezifische Opcodes zur Verwaltung von calldata. calldatasize gibt die Größe der Daten zurück. calldataload lädt die Daten in den Stack. calldatacopy kopiert die Daten in den Speicher (Memory).

Die standardmäßige Von-Neumann-Architektur (opens in a new tab) speichert Code und Daten im selben Speicher. Die EVM folgt diesem Standard aus Sicherheitsgründen nicht – die gemeinsame Nutzung von flüchtigem Speicher macht es möglich, Programmcode zu ändern. Stattdessen wird Code im Storage gespeichert.

Es gibt nur zwei Fälle, in denen Code aus dem Speicher (Memory) ausgeführt wird:

  • Wenn ein Smart Contract einen anderen Smart Contract erstellt (unter Verwendung von CREATE (opens in a new tab) oder CREATE2 (opens in a new tab)), stammt der Code für den Konstruktor des Smart Contracts aus dem Speicher.
  • Während der Erstellung jedes Smart Contracts wird der Konstruktor-Code ausgeführt und gibt dann den Code des eigentlichen Smart Contracts zurück, ebenfalls aus dem Speicher.

Der Begriff der außergewöhnlichen Ausführung (exceptional execution) bezeichnet eine Ausnahme, die dazu führt, dass die Ausführung des aktuellen Smart Contracts angehalten wird.

9.2 Gebührenübersicht

Dieser Abschnitt erklärt, wie die Gasgebühren berechnet werden. Es gibt drei Kostenpunkte:

Opcode-Kosten

Die inhärenten Kosten des spezifischen Opcodes. Um diesen Wert zu erhalten, suchen Sie die Kostengruppe des Opcodes in Anhang H (S. 28, unter Gleichung (327)) und finden Sie die Kostengruppe in Gleichung (324). Dies ergibt eine Kostenfunktion, die in den meisten Fällen Parameter aus Anhang G (S. 27) verwendet.

Zum Beispiel ist der Opcode CALLDATACOPY (opens in a new tab) ein Mitglied der Gruppe Wcopy. Die Opcode-Kosten für diese Gruppe betragen Gverylow+Gcopy×⌈μs[2]÷32⌉. Ein Blick auf Anhang G zeigt, dass beide Konstanten 3 sind, was uns 3+3×⌈μs[2]÷32⌉ ergibt.

Wir müssen noch den Ausdruck ⌈μs[2]÷32⌉ entschlüsseln. Der äußerste Teil, ⌈ <Wert> ⌉, ist die Aufrundungsfunktion (Ceiling-Funktion), eine Funktion, die für einen gegebenen Wert die kleinste ganze Zahl zurückgibt, die nicht kleiner als der Wert ist. Zum Beispiel ⌈2.5⌉ = ⌈3⌉ = 3. Der innere Teil ist μs[2]÷32. Betrachtet man Abschnitt 3 (Konventionen) auf S. 3, so ist μ der Maschinenzustand. Der Maschinenzustand ist in Abschnitt 9.4.1 auf S. 13 definiert. Gemäß diesem Abschnitt ist einer der Parameter des Maschinenzustands s für den Stack. Zusammengenommen scheint es, dass μs[2] die Position #2 im Stack ist. Betrachtet man den Opcode (opens in a new tab), so ist Position #2 im Stack die Größe der Daten in Bytes. Betrachtet man die anderen Opcodes in der Gruppe Wcopy, CODECOPY (opens in a new tab) und RETURNDATACOPY (opens in a new tab), so haben diese ebenfalls eine Datengröße an derselben Position. Also ist ⌈μs[2]÷32⌉ die Anzahl der 32-Byte-Wörter, die erforderlich sind, um die zu kopierenden Daten zu speichern. Zusammenfassend betragen die inhärenten Kosten von CALLDATACOPY (opens in a new tab) 3 Gas plus 3 pro kopiertem Datenwort.

Laufende Kosten

Die Kosten für die Ausführung des aufgerufenen Codes.

Kosten für die Speichererweiterung

Die Kosten für die Erweiterung des Speichers (falls erforderlich).

In Gleichung 324 wird dieser Wert als Cmemi')-Cmemi) geschrieben. Wenn wir uns Abschnitt 9.4.1 noch einmal ansehen, sehen wir, dass μi die Anzahl der Wörter im Speicher ist. Also ist μi die Anzahl der Wörter im Speicher vor dem Opcode und μi' die Anzahl der Wörter im Speicher nach dem Opcode.

Die Funktion Cmem ist in Gleichung 326 definiert: Cmem(a) = Gmemory × a + ⌊a2 ÷ 512⌋. ⌊x⌋ ist die Abrundungsfunktion (Floor-Funktion), eine Funktion, die für einen gegebenen Wert die größte ganze Zahl zurückgibt, die nicht größer als der Wert ist. Zum Beispiel ⌊2.5⌋ = ⌊2⌋ = 2. Wenn a < √512, ist a2 < 512, und das Ergebnis der Abrundungsfunktion ist null. Für die ersten 22 Wörter (704 Bytes) steigen die Kosten also linear mit der Anzahl der benötigten Speicherwörter. Jenseits dieses Punktes ist ⌊a2 ÷ 512⌋ positiv. Wenn der benötigte Speicher groß genug ist, sind die Gaskosten proportional zum Quadrat der Speichermenge.

Hinweis: Diese Faktoren beeinflussen nur die inhärenten Gaskosten – sie berücksichtigen nicht den Gebührenmarkt oder Trinkgelder an Validatoren, die bestimmen, wie viel ein Endbenutzer zahlen muss – dies sind nur die reinen Kosten für die Ausführung einer bestimmten Operation auf der EVM.

Mehr über Gas lesen.

9.3 Ausführungsumgebung

Die Ausführungsumgebung ist ein Tupel, I, das Informationen enthält, die nicht Teil des Blockchain-Status oder der EVM sind.

ParameterOpcode für den Zugriff auf die DatenSolidity-Code für den Zugriff auf die Daten
IaADDRESS (opens in a new tab)address(this)
IoORIGIN (opens in a new tab)tx.origin
IpGASPRICE (opens in a new tab)tx.gasprice
IdCALLDATALOAD (opens in a new tab), etc.msg.data
IsCALLER (opens in a new tab)msg.sender
IvCALLVALUE (opens in a new tab)msg.value
IbCODECOPY (opens in a new tab)address(this).code
IHBlock-Header-Felder, wie NUMBER (opens in a new tab) und DIFFICULTY (opens in a new tab)block.number, block.difficulty, etc.
IeTiefe des Aufruf-Stacks für Aufrufe zwischen Smart Contracts (einschließlich der Erstellung von Smart Contracts)
IwDarf die EVM den Status ändern oder läuft sie statisch?

Einige weitere Parameter sind notwendig, um den Rest von Abschnitt 9 zu verstehen:

ParameterDefiniert in AbschnittBedeutung
σ2 (S. 2, Gleichung 1)Der Status der Blockchain
g9.3 (S. 13)Verbleibendes Gas
A6.1 (S. 8)Aufgelaufener Substatus (Änderungen, die für das Ende der Transaktion geplant sind)
o9.3 (S. 13)Ausgabe – das zurückgegebene Ergebnis im Falle einer internen Transaktion (wenn ein Smart Contract einen anderen aufruft) und bei Aufrufen von View-Funktionen (wenn Sie nur nach Informationen fragen, sodass nicht auf eine Transaktion gewartet werden muss)

9.4 Ausführungsübersicht

Nachdem wir nun alle Vorbereitungen getroffen haben, können wir uns endlich damit befassen, wie die EVM funktioniert.

Die Gleichungen 137-142 geben uns die Anfangsbedingungen für die Ausführung der EVM:

SymbolAnfangswertBedeutung
μggVerbleibendes Gas
μpc0Programmierzähler (Program Counter), die Adresse der nächsten auszuführenden Anweisung
μm(0, 0, ...)Speicher (Memory), komplett mit Nullen initialisiert
μi0Höchster verwendeter Speicherort
μs()Der Stack, anfangs leer
μoDie Ausgabe, eine leere Menge, bis und sofern wir nicht entweder mit Rückgabedaten (RETURN (opens in a new tab) oder REVERT (opens in a new tab)) oder ohne diese (STOP (opens in a new tab) oder SELFDESTRUCT (opens in a new tab)) anhalten.

Gleichung 143 besagt, dass es zu jedem Zeitpunkt während der Ausführung vier mögliche Bedingungen gibt und was mit ihnen zu tun ist:

  1. Z(σ,μ,A,I). Z stellt eine Funktion dar, die testet, ob eine Operation einen ungültigen Statusübergang erzeugt (siehe außergewöhnliches Anhalten). Wenn sie als Wahr (True) ausgewertet wird, ist der neue Status identisch mit dem alten (außer dass Gas verbrannt wird), da die Änderungen nicht implementiert wurden.
  2. Wenn der ausgeführte Opcode REVERT (opens in a new tab) ist, entspricht der neue Status dem alten Status, etwas Gas geht verloren.
  3. Wenn die Abfolge der Operationen abgeschlossen ist, was durch ein RETURN (opens in a new tab) signalisiert wird, wird der Status auf den neuen Status aktualisiert.
  4. Wenn wir uns nicht bei einer der Endbedingungen 1-3 befinden, wird die Ausführung fortgesetzt.

9.4.1 Maschinenzustand

Dieser Abschnitt erklärt den Maschinenzustand genauer. Er legt fest, dass w der aktuelle Opcode ist. Wenn μpc kleiner als ||Ib||, die Länge des Codes, ist, dann ist dieses Byte (Ibpc]) der Opcode. Andernfalls wird der Opcode als STOP (opens in a new tab) definiert.

Da es sich um eine Kellermaschine (opens in a new tab) handelt, müssen wir die Anzahl der Elemente verfolgen, die von jedem Opcode entnommen (δ) und hinzugefügt (α) werden.

9.4.2 Außergewöhnliches Anhalten

Dieser Abschnitt definiert die Funktion Z, die angibt, wann ein abnormaler Abbruch vorliegt. Dies ist eine Boolesche (opens in a new tab) Funktion, daher verwendet sie für ein logisches Oder (opens in a new tab) und für ein logisches Und (opens in a new tab).

Wir haben ein außergewöhnliches Anhalten, wenn eine dieser Bedingungen wahr ist:

  • μg < C(σ,μ,A,I) Wie wir in Abschnitt 9.2 gesehen haben, ist C die Funktion, die die Gaskosten angibt. Es ist nicht mehr genug Gas übrig, um den nächsten Opcode abzudecken.

  • δw=∅ Wenn die Anzahl der für einen Opcode entnommenen Elemente undefiniert ist, dann ist der Opcode selbst undefiniert.

  • || μs || < δw Stack-Unterlauf (Stack Underflow), nicht genügend Elemente im Stack für den aktuellen Opcode.

  • w = JUMP ∧ μs[0]∉D(Ib) Der Opcode ist JUMP (opens in a new tab) und die Adresse ist kein JUMPDEST (opens in a new tab). Sprünge (Jumps) sind nur gültig, wenn das Ziel ein JUMPDEST (opens in a new tab) ist.

  • w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) Der Opcode ist JUMPI (opens in a new tab), die Bedingung ist wahr (ungleich null), sodass der Sprung erfolgen sollte, und die Adresse ist kein JUMPDEST (opens in a new tab). Sprünge sind nur gültig, wenn das Ziel ein JUMPDEST (opens in a new tab) ist.

  • w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || Der Opcode ist RETURNDATACOPY (opens in a new tab). Bei diesem Opcode ist das Stack-Element μs[1] der Offset, ab dem im Rückgabedatenpuffer gelesen werden soll, und das Stack-Element μs[2] ist die Datenlänge. Diese Bedingung tritt auf, wenn Sie versuchen, über das Ende des Rückgabedatenpuffers hinaus zu lesen. Beachten Sie, dass es für die Calldata oder für den Code selbst keine ähnliche Bedingung gibt. Wenn Sie versuchen, über das Ende dieser Puffer hinaus zu lesen, erhalten Sie einfach Nullen.

  • || μs || - δw + αw > 1024

    Stack-Überlauf (Stack Overflow). Wenn die Ausführung des Opcodes zu einem Stack von über 1024 Elementen führt, wird abgebrochen.

  • ¬Iw ∧ W(w,μ) Laufen wir statisch (¬ ist Negation (opens in a new tab) und Iw ist wahr, wenn wir den Blockchain-Status ändern dürfen)? Wenn ja, und wir versuchen eine statusändernde Operation, kann diese nicht stattfinden.

    Die Funktion W(w,μ) wird später in Gleichung 150 definiert. W(w,μ) ist wahr, wenn eine dieser Bedingungen wahr ist:

    • w ∈ {CREATE, CREATE2, SSTORE, SELFDESTRUCT} Diese Opcodes ändern den Status, entweder durch Erstellen eines neuen Smart Contracts, Speichern eines Werts oder Zerstören des aktuellen Smart Contracts.

    • LOG0≤w ∧ w≤LOG4 Wenn wir statisch aufgerufen werden, können wir keine Protokolleinträge (Log Entries) ausgeben. Die Log-Opcodes liegen alle im Bereich zwischen LOG0 (A0) (opens in a new tab) und LOG4 (A4) (opens in a new tab). Die Zahl nach dem Log-Opcode gibt an, wie viele Themen (Topics) der Protokolleintrag enthält.

    • w=CALL ∧ μs[2]≠0 Sie können einen anderen Smart Contract aufrufen, wenn Sie statisch sind, aber wenn Sie dies tun, können Sie keine ETH an ihn übertragen.

  • w = SSTORE ∧ μg ≤ Gcallstipend Sie können SSTORE (opens in a new tab) nicht ausführen, es sei denn, Sie haben mehr als Gcallstipend (in Anhang G als 2300 definiert) Gas.

9.4.3 Gültigkeit des Sprungziels

Hier definieren wir formal, was die JUMPDEST (opens in a new tab)-Opcodes sind. Wir können nicht einfach nach dem Bytewert 0x5B suchen, da er sich innerhalb eines PUSH befinden könnte (und somit Daten und kein Opcode wäre).

In Gleichung (153) definieren wir eine Funktion, N(i,w). Der erste Parameter, i, ist die Position des Opcodes. Der zweite, w, ist der Opcode selbst. Wenn w∈[PUSH1, PUSH32], bedeutet das, dass der Opcode ein PUSH ist (eckige Klammern definieren einen Bereich, der die Endpunkte einschließt). In diesem Fall befindet sich der nächste Opcode bei i+2+(w−PUSH1). Für PUSH1 (opens in a new tab) müssen wir um zwei Bytes vorrücken (das PUSH selbst und der Ein-Byte-Wert), für PUSH2 (opens in a new tab) müssen wir um drei Bytes vorrücken, da es sich um einen Zwei-Byte-Wert handelt, usw. Alle anderen EVM-Opcodes sind nur ein Byte lang, also ist in allen anderen Fällen N(i,w)=i+1.

Diese Funktion wird in Gleichung (152) verwendet, um DJ(c,i) zu definieren, was die Menge (opens in a new tab) aller gültigen Sprungziele im Code c ist, beginnend mit der Opcode-Position i. Diese Funktion ist rekursiv definiert. Wenn i≥||c||, bedeutet das, dass wir uns am oder nach dem Ende des Codes befinden. Wir werden keine weiteren Sprungziele finden, also geben wir einfach die leere Menge zurück.

In allen anderen Fällen betrachten wir den Rest des Codes, indem wir zum nächsten Opcode gehen und die Menge ab diesem abrufen. c[i] ist der aktuelle Opcode, also ist N(i,c[i]) die Position des nächsten Opcodes. DJ(c,N(i,c[i])) ist daher die Menge der gültigen Sprungziele, die beim nächsten Opcode beginnt. Wenn der aktuelle Opcode kein JUMPDEST ist, geben Sie einfach diese Menge zurück. Wenn es JUMPDEST ist, nehmen Sie es in die Ergebnismenge auf und geben Sie diese zurück.

9.4.4 Normales Anhalten

Die Anhaltefunktion H kann drei Arten von Werten zurückgeben.

  • Wenn wir uns nicht in einem Halt-Opcode befinden, geben Sie , die leere Menge, zurück. Konventionsgemäß wird dieser Wert als Boolesches Falsch (False) interpretiert.
  • Wenn wir einen Halt-Opcode haben, der keine Ausgabe erzeugt (entweder STOP (opens in a new tab) oder SELFDESTRUCT (opens in a new tab)), geben Sie eine Sequenz der Größe null Bytes als Rückgabewert zurück. Beachten Sie, dass sich dies stark von der leeren Menge unterscheidet. Dieser Wert bedeutet, dass die EVM tatsächlich angehalten hat, es gibt nur keine Rückgabedaten zum Lesen.
  • Wenn wir einen Halt-Opcode haben, der eine Ausgabe erzeugt (entweder RETURN (opens in a new tab) oder REVERT (opens in a new tab)), geben Sie die durch diesen Opcode angegebene Byte-Sequenz zurück. Diese Sequenz wird aus dem Speicher entnommen, der Wert oben auf dem Stack (μs[0]) ist das erste Byte, und der Wert danach (μs[1]) ist die Länge.

H.2 Befehlssatz

Bevor wir zum letzten Unterabschnitt der EVM, 9.5, übergehen, schauen wir uns die Befehle selbst an. Sie sind in Anhang H.2 definiert, der auf S. 29 beginnt. Alles, was nicht als durch diesen spezifischen Opcode verändert angegeben ist, bleibt voraussichtlich gleich. Variablen, die sich ändern, werden mit <etwas>′ angegeben.

Schauen wir uns zum Beispiel den Opcode ADD (opens in a new tab) an.

WertMnemonicδαBeschreibung
0x01ADD21Additionsoperation.
μ′s[0] ≡ μs[0] + μs[1]

δ ist die Anzahl der Werte, die wir vom Stack entnehmen. In diesem Fall zwei, da wir die beiden obersten Werte addieren.

α ist die Anzahl der Werte, die wir zurücklegen. In diesem Fall einer, die Summe.

Die neue Stack-Spitze (μ′s[0]) ist also die Summe der alten Stack-Spitze (μs[0]) und des alten Werts darunter (μs[1]).

Anstatt alle Opcodes mit einer ermüdenden Liste durchzugehen, erklärt dieser Artikel nur die Opcodes, die etwas Neues einführen.

WertMnemonicδαBeschreibung
0x20KECCAK25621Keccak-256-Hash berechnen.
μ′s[0] ≡ KEC(μms[0] . . . (μs[0] + μs[1] − 1)])
μ′i ≡ M(μis[0],μs[1])

Dies ist der erste Opcode, der auf den Speicher zugreift (in diesem Fall nur lesend). Er könnte jedoch über die aktuellen Grenzen des Speichers hinausgehen, daher müssen wir μi aktualisieren. Wir tun dies mithilfe der Funktion M, die in Gleichung 328 auf S. 29 definiert ist.

WertMnemonicδαBeschreibung
0x31BALANCE11Guthaben des angegebenen Kontos abrufen.
...

Die Adresse, deren Guthaben wir finden müssen, ist μs[0] mod 2160. Die Spitze des Stacks ist die Adresse, aber da Adressen nur 160 Bit lang sind, berechnen wir den Wert Modulo (opens in a new tab) 2160.

Wenn σ[μs[0] mod 2160] ≠ ∅, bedeutet das, dass Informationen zu dieser Adresse vorliegen. In diesem Fall ist σ[μs[0] mod 2160]b das Guthaben für diese Adresse. Wenn σ[μs[0] mod 2160] = ∅, bedeutet das, dass diese Adresse nicht initialisiert ist und das Guthaben null beträgt. Sie können die Liste der Kontoinformationsfelder in Abschnitt 4.1 auf S. 4 sehen.

Die zweite Gleichung, A'a ≡ Aa ∪ {μs[0] mod 2160}, bezieht sich auf den Kostenunterschied zwischen dem Zugriff auf Warm Storage (Speicher, auf den kürzlich zugegriffen wurde und der wahrscheinlich zwischengespeichert ist) und Cold Storage (Speicher, auf den nicht zugegriffen wurde und der sich wahrscheinlich in einem langsameren Speicher befindet, dessen Abruf teurer ist). Aa ist die Liste der Adressen, auf die zuvor von der Transaktion zugegriffen wurde und deren Zugriff daher günstiger sein sollte, wie in Abschnitt 6.1 auf S. 8 definiert. Sie können mehr über dieses Thema in EIP-2929 (opens in a new tab) lesen.

WertMnemonicδαBeschreibung
0x8FDUP16161716. Stack-Element duplizieren.
μ′s[0] ≡ μs[15]

Beachten Sie, dass wir jedes Stack-Element, das wir verwenden möchten, entnehmen müssen, was bedeutet, dass wir auch alle darüber liegenden Stack-Elemente entnehmen müssen. Im Falle von DUP<n> (opens in a new tab) und SWAP<n> (opens in a new tab) bedeutet dies, dass bis zu sechzehn Werte entnommen und dann wieder hinzugefügt werden müssen.

9.5 Der Ausführungszyklus

Nachdem wir nun alle Teile haben, können wir endlich verstehen, wie der Ausführungszyklus der EVM dokumentiert ist.

Gleichung (155) besagt, dass bei gegebenem Status:

  • σ (globaler Blockchain-Status)
  • μ (EVM-Status)
  • A (Substatus, Änderungen, die am Ende der Transaktion eintreten sollen)
  • I (Ausführungsumgebung)

Der neue Status ist (σ', μ', A', I').

Die Gleichungen (156)-(158) definieren den Stack und dessen Änderung durch einen Opcode (μs). Gleichung (159) ist die Änderung des Gases (μg). Gleichung (160) ist die Änderung des Programmierzählers (μpc). Schließlich legen die Gleichungen (161)-(164) fest, dass die anderen Parameter gleich bleiben, sofern sie nicht explizit durch den Opcode geändert werden.

Damit ist die EVM vollständig definiert.

Fazit

Die mathematische Notation ist präzise und hat es dem Yellow Paper ermöglicht, jedes Detail von Ethereum zu spezifizieren. Sie hat jedoch einige Nachteile:

  • Sie kann nur von Menschen verstanden werden, was bedeutet, dass Konformitätstests (opens in a new tab) manuell geschrieben werden müssen.
  • Programmierer verstehen Computercode. Sie verstehen möglicherweise mathematische Notation, oder auch nicht.

Vielleicht aus diesen Gründen sind die neueren Spezifikationen der Konsensebene (opens in a new tab) in Python geschrieben. Es gibt Spezifikationen der Ausführungsebene in Python (opens in a new tab), aber sie sind nicht vollständig. Bis das gesamte Yellow Paper ebenfalls in Python oder eine ähnliche Sprache übersetzt wird, bleibt das Yellow Paper in Gebrauch, und es ist hilfreich, es lesen zu können.

Letzte Aktualisierung der Seite: 1. Februar 2026

War dieses Tutorial hilfreich?