Passer au contenu principal

Comprendre les spécifications de l'EVM du livre jaune

evm
Intermédiaire
qbzzt
15 mai 2022
21 minutes de lecture

Le livre jaune (s'ouvre dans un nouvel onglet) est la spécification formelle d'Ethereum. Sauf lorsqu'il est modifié par le processus des EIP, il contient la description exacte du fonctionnement de chaque élément. Il est rédigé comme un article mathématique, ce qui inclut une terminologie avec laquelle les programmeurs peuvent ne pas être familiers. Dans cet article, vous apprendrez comment le lire, et par extension, d'autres articles mathématiques connexes.

Quel livre jaune ?

Comme presque tout le reste dans Ethereum, le livre jaune évolue avec le temps. Pour pouvoir faire référence à une version spécifique, j'ai mis en ligne la version actuelle au moment de la rédaction (s'ouvre dans un nouvel onglet). Les numéros de section, de page et d'équation que j'utilise feront référence à cette version. Il est recommandé de l'avoir ouvert dans une autre fenêtre pendant la lecture de ce document.

Pourquoi l'EVM ?

Le livre jaune original a été rédigé tout au début du développement d'Ethereum. Il décrit le mécanisme de consensus original basé sur la preuve de travail (PoW) qui était initialement utilisé pour sécuriser le réseau. Cependant, Ethereum a abandonné la preuve de travail et a commencé à utiliser un consensus basé sur la preuve d'enjeu (PoS) en septembre 2022. Ce tutoriel se concentrera sur les parties du livre jaune définissant la machine virtuelle Ethereum (EVM). L'EVM n'a pas été modifiée par la transition vers la preuve d'enjeu (à l'exception de la valeur de retour du code d'opération DIFFICULTY).

9 Modèle d'exécution

Cette section (p. 14-16) inclut la majeure partie de la définition de l'EVM.

Le terme état du système inclut tout ce que vous devez savoir sur le système pour l'exécuter. Dans un ordinateur typique, cela signifie la mémoire, le contenu des registres, etc.

Une machine de Turing (s'ouvre dans un nouvel onglet) est un modèle de calcul. Essentiellement, il s'agit d'une version simplifiée d'un ordinateur, dont il est prouvé qu'elle a la même capacité à exécuter des calculs qu'un ordinateur normal (tout ce qu'un ordinateur peut calculer, une machine de Turing peut le calculer et vice versa). Ce modèle facilite la démonstration de divers théorèmes sur ce qui est calculable et ce qui ne l'est pas.

Le terme Turing-complet (s'ouvre dans un nouvel onglet) désigne un ordinateur capable d'exécuter les mêmes calculs qu'une machine de Turing. Les machines de Turing peuvent entrer dans des boucles infinies, ce qui est impossible pour l'EVM car elle manquerait de gaz, elle n'est donc que quasi-Turing-complète.

9.1 Bases

Cette section présente les bases de l'EVM et la compare à d'autres modèles de calcul.

Une machine à pile (s'ouvre dans un nouvel onglet) est un ordinateur qui stocke les données intermédiaires non pas dans des registres, mais dans une pile (s'ouvre dans un nouvel onglet). C'est l'architecture privilégiée pour les machines virtuelles car elle est facile à implémenter, ce qui signifie que les bugs et les vulnérabilités de sécurité sont beaucoup moins probables. La mémoire de la pile est divisée en mots de 256 bits. Ce choix a été fait car il est pratique pour les opérations cryptographiques de base d'Ethereum telles que le hachage Keccak-256 et les calculs sur courbe elliptique. La taille maximale de la pile est de 1024 éléments (1024 x 256 bits). Lorsque les codes d'opération sont exécutés, ils obtiennent généralement leurs paramètres de la pile. Il existe des codes d'opération spécifiquement conçus pour réorganiser les éléments dans la pile, tels que POP (supprime l'élément en haut de la pile), DUP_N (duplique le N-ième élément de la pile), etc.

L'EVM dispose également d'un espace volatil appelé mémoire qui est utilisé pour stocker des données pendant l'exécution. Cette mémoire est organisée en mots de 32 octets. Tous les emplacements de mémoire sont initialisés à zéro. Si vous exécutez ce code Yul (s'ouvre dans un nouvel onglet) pour ajouter un mot à la mémoire, il remplira 32 octets de mémoire en complétant l'espace vide du mot avec des zéros, c'est-à-dire qu'il crée un mot - avec des zéros aux emplacements 0-29, 0x60 à 30, et 0xA7 à 31.

mstore(0, 0x60A7)

mstore est l'un des trois codes d'opération fournis par l'EVM pour interagir avec la mémoire - il charge un mot dans la mémoire. Les deux autres sont mstore8 qui charge un seul octet dans la mémoire, et mload qui déplace un mot de la mémoire vers la pile.

L'EVM possède également un modèle de stockage non volatil distinct qui est maintenu dans le cadre de l'état du système - cette mémoire est organisée en tableaux de mots (par opposition aux tableaux d'octets adressables par mot dans la pile). C'est dans ce stockage que les contrats conservent les données persistantes - un contrat ne peut interagir qu'avec son propre stockage. Le stockage est organisé en correspondances clé-valeur.

Bien que cela ne soit pas mentionné dans cette section du livre jaune, il est également utile de savoir qu'il existe un quatrième type de mémoire. Les données d'appel (calldata) sont une mémoire en lecture seule adressable par octet utilisée pour stocker la valeur transmise avec le paramètre data d'une transaction. L'EVM possède des codes d'opération spécifiques pour gérer les calldata. calldatasize renvoie la taille des données. calldataload charge les données dans la pile. calldatacopy copie les données dans la mémoire.

L'architecture de von Neumann (s'ouvre dans un nouvel onglet) standard stocke le code et les données dans la même mémoire. L'EVM ne suit pas cette norme pour des raisons de sécurité - le partage de la mémoire volatile permet de modifier le code du programme. Au lieu de cela, le code est sauvegardé dans le stockage.

Il n'y a que deux cas dans lesquels le code est exécuté à partir de la mémoire :

Le terme exécution exceptionnelle désigne une exception qui provoque l'arrêt de l'exécution du contrat en cours.

9.2 Aperçu des frais

Cette section explique comment les frais de gaz sont calculés. Il y a trois coûts :

Coût du code d'opération

Le coût inhérent du code d'opération spécifique. Pour obtenir cette valeur, trouvez le groupe de coût du code d'opération dans l'Annexe H (p. 29, sous l'équation (329)), et trouvez le groupe de coût dans l'équation (326). Cela vous donne une fonction de coût, qui dans la plupart des cas utilise des paramètres de l'Annexe G (p. 28).

Par exemple, le code d'opération CALLDATACOPY (s'ouvre dans un nouvel onglet) est membre du groupe Wcopy. Le coût du code d'opération pour ce groupe est Gverylow+Gcopy×⌈μs[2]÷32⌉. En regardant l'Annexe G, nous voyons que les deux constantes sont 3, ce qui nous donne 3+3×⌈μs[2]÷32⌉.

Nous devons encore déchiffrer l'expression ⌈μs[2]÷32⌉. La partie la plus externe, ⌈ <value> ⌉ est la fonction plafond, une fonction qui, pour une valeur donnée, renvoie le plus petit entier qui n'est pas inférieur à la valeur. Par exemple, ⌈2.5⌉ = ⌈3⌉ = 3. La partie interne est μs[2]÷32. En regardant la section 3 (Conventions) à la p. 3, μ est l'état de la machine. L'état de la machine est défini dans la section 9.4.1 à la p. 15. Selon cette section, l'un des paramètres de l'état de la machine est s pour la pile. En rassemblant tout cela, il semble que μs[2] soit l'emplacement n°2 dans la pile. En regardant le code d'opération (s'ouvre dans un nouvel onglet), l'emplacement n°2 dans la pile est la taille des données en octets. En regardant les autres codes d'opération du groupe Wcopy, CODECOPY (s'ouvre dans un nouvel onglet) et RETURNDATACOPY (s'ouvre dans un nouvel onglet), ils ont également une taille de données au même emplacement. Donc ⌈μs[2]÷32⌉ est le nombre de mots de 32 octets requis pour stocker les données copiées. En rassemblant tout cela, le coût inhérent de CALLDATACOPY (s'ouvre dans un nouvel onglet) est de 3 gaz plus 3 par mot de données copiées.

Coût d'exécution

Le coût d'exécution du code que nous appelons.

Coût d'extension de la mémoire

Le coût d'extension de la mémoire (si nécessaire).

Dans l'équation 326, cette valeur est écrite comme Cmemi')-Cmemi). En regardant à nouveau la section 9.4.1, nous voyons que μi est le nombre de mots en mémoire. Donc μi est le nombre de mots en mémoire avant le code d'opération et μi' est le nombre de mots en mémoire après le code d'opération.

La fonction Cmem est définie dans l'équation 328 : Cmem(a) = Gmemory × a + ⌊a2 ÷ 512⌋. ⌊x⌋ est la fonction plancher, une fonction qui, pour une valeur donnée, renvoie le plus grand entier qui n'est pas supérieur à la valeur. Par exemple, ⌊2.5⌋ = ⌊2⌋ = 2. Lorsque a < √512, a2 < 512, et le résultat de la fonction plancher est zéro. Donc pour les 22 premiers mots (704 octets), le coût augmente linéairement avec le nombre de mots mémoire requis. Au-delà de ce point, ⌊a2 ÷ 512⌋ est positif. Lorsque la mémoire requise est suffisamment élevée, le coût en gaz est proportionnel au carré de la quantité de mémoire.

Remarque : ces facteurs n'influencent que le coût inhérent en gaz - cela ne prend pas en compte le marché des frais ou les pourboires aux validateurs qui déterminent combien un utilisateur final doit payer - il s'agit simplement du coût brut d'exécution d'une opération particulière sur l'EVM.

En savoir plus sur le gaz.

9.3 Environnement d'exécution

L'environnement d'exécution est un n-uplet, I, qui inclut des informations qui ne font pas partie de l'état de la chaîne de blocs ou de l'EVM.

ParamètreCode d'opération pour accéder aux donnéesCode Solidity pour accéder aux données
IaADDRESS (s'ouvre dans un nouvel onglet)address(this)
IoORIGIN (s'ouvre dans un nouvel onglet)tx.origin
IpGASPRICE (s'ouvre dans un nouvel onglet)tx.gasprice
IdCALLDATALOAD (s'ouvre dans un nouvel onglet), etc.msg.data
IsCALLER (s'ouvre dans un nouvel onglet)msg.sender
IvCALLVALUE (s'ouvre dans un nouvel onglet)msg.value
IbCODECOPY (s'ouvre dans un nouvel onglet)address(this).code
IHChamps de l'en-tête de bloc, tels que NUMBER (s'ouvre dans un nouvel onglet) et DIFFICULTY (s'ouvre dans un nouvel onglet)block.number, block.difficulty, etc.
IeProfondeur de la pile d'appels pour les appels entre contrats (y compris la création de contrat)
IwL'EVM est-elle autorisée à modifier l'état, ou s'exécute-t-elle de manière statique

Quelques autres paramètres sont nécessaires pour comprendre le reste de la section 9 :

ParamètreDéfini dans la sectionSignification
σ2 (p. 2, équation 1)L'état de la chaîne de blocs
g9.3 (p. 14)Gaz restant
A6.1 (p. 9)Sous-état accumulé (modifications prévues pour la fin de la transaction)
o9.3 (p. 14)Sortie - le résultat renvoyé dans le cas d'une transaction interne (lorsqu'un contrat en appelle un autre) et des appels aux fonctions de vue (lorsque vous demandez simplement des informations, il n'est donc pas nécessaire d'attendre une transaction)

9.4 Aperçu de l'exécution

Maintenant que nous avons tous les préliminaires, nous pouvons enfin commencer à travailler sur le fonctionnement de l'EVM.

Les équations 146-151 nous donnent les conditions initiales pour l'exécution de l'EVM :

SymboleValeur initialeSignification
μggGaz restant
μpc0Compteur de programme, l'adresse de la prochaine instruction à exécuter
μm(0, 0, ...)Mémoire, initialisée avec des zéros
μi0Emplacement mémoire le plus élevé utilisé
μs()La pile, initialement vide
μoLa sortie, ensemble vide jusqu'à ce que nous nous arrêtions avec des données de retour (RETURN (s'ouvre dans un nouvel onglet) ou REVERT (s'ouvre dans un nouvel onglet)) ou sans (STOP (s'ouvre dans un nouvel onglet) ou SELFDESTRUCT (s'ouvre dans un nouvel onglet)).

L'équation 152 nous indique qu'il y a quatre conditions possibles à chaque instant pendant l'exécution, et ce qu'il faut en faire :

  1. Z(σ,μ,A,I). Z représente une fonction qui teste si une opération crée une transition d'état invalide (voir arrêt exceptionnel). Si elle est évaluée à Vrai, le nouvel état est identique à l'ancien (sauf que le gaz est brûlé) car les modifications n'ont pas été implémentées.
  2. Si le code d'opération en cours d'exécution est REVERT (s'ouvre dans un nouvel onglet), le nouvel état est le même que l'ancien état, une partie du gaz est perdue.
  3. Si la séquence d'opérations est terminée, comme indiqué par un RETURN (s'ouvre dans un nouvel onglet)), l'état est mis à jour vers le nouvel état.
  4. Si nous ne sommes pas dans l'une des conditions de fin 1-3, continuez l'exécution.

9.4.1 État de la machine

Cette section explique l'état de la machine plus en détail. Elle précise que w est le code d'opération actuel. Si μpc est inférieur à ||Ib||, la longueur du code, alors cet octet (Ibpc]) est le code d'opération. Sinon, le code d'opération est défini comme STOP (s'ouvre dans un nouvel onglet).

Comme il s'agit d'une machine à pile (s'ouvre dans un nouvel onglet), nous devons garder une trace du nombre d'éléments retirés (δ) et ajoutés (α) par chaque code d'opération.

9.4.2 Arrêt exceptionnel

Cette section définit la fonction Z, qui spécifie quand nous avons une terminaison anormale. Il s'agit d'une fonction booléenne (s'ouvre dans un nouvel onglet), elle utilise donc pour un OU logique (s'ouvre dans un nouvel onglet) et pour un ET logique (s'ouvre dans un nouvel onglet).

Nous avons un arrêt exceptionnel si l'une de ces conditions est vraie :

  • μg < C(σ,μ,A,I) Comme nous l'avons vu dans la section 9.2, C est la fonction qui spécifie le coût en gaz. Il ne reste pas assez de gaz pour couvrir le prochain code d'opération.

  • δw=∅ Si le nombre d'éléments retirés pour un code d'opération n'est pas défini, alors le code d'opération lui-même n'est pas défini.

  • || μs || < δw Sous-dépassement de pile, pas assez d'éléments dans la pile pour le code d'opération actuel.

  • w = JUMP ∧ μs[0]∉D(Ib) Le code d'opération est JUMP (s'ouvre dans un nouvel onglet) et l'adresse n'est pas un JUMPDEST (s'ouvre dans un nouvel onglet). Les sauts ne sont valides que lorsque la destination est un JUMPDEST (s'ouvre dans un nouvel onglet).

  • w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) Le code d'opération est JUMPI (s'ouvre dans un nouvel onglet), la condition est vraie (non nulle) donc le saut devrait se produire, et l'adresse n'est pas un JUMPDEST (s'ouvre dans un nouvel onglet). Les sauts ne sont valides que lorsque la destination est un JUMPDEST (s'ouvre dans un nouvel onglet).

  • w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || Le code d'opération est RETURNDATACOPY (s'ouvre dans un nouvel onglet). Dans ce code d'opération, l'élément de pile μs[1] est le décalage à partir duquel lire dans le tampon de données de retour, et l'élément de pile μs[2] est la longueur des données. Cette condition se produit lorsque vous essayez de lire au-delà de la fin du tampon de données de retour. Notez qu'il n'y a pas de condition similaire pour les données d'appel (calldata) ou pour le code lui-même. Lorsque vous essayez de lire au-delà de la fin de ces tampons, vous obtenez simplement des zéros.

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

    Dépassement de capacité de la pile. Si l'exécution du code d'opération entraîne une pile de plus de 1024 éléments, abandonnez.

  • ¬Iw ∧ W(w,μ) Sommes-nous en cours d'exécution statique (¬ est la négation (s'ouvre dans un nouvel onglet) et Iw est vrai lorsque nous sommes autorisés à modifier l'état de la chaîne de blocs) ? Si c'est le cas, et que nous essayons une opération de modification d'état, cela ne peut pas se produire.

    La fonction W(w,μ) est définie plus loin dans l'équation 159. W(w,μ) est vraie si l'une de ces conditions est vraie :

    • w ∈ {CREATE, CREATE2, SSTORE, SELFDESTRUCT} Ces codes d'opération modifient l'état, soit en créant un nouveau contrat, en stockant une valeur, ou en détruisant le contrat actuel.

    • LOG0≤w ∧ w≤LOG4 Si nous sommes appelés statiquement, nous ne pouvons pas émettre d'entrées de journal. Les codes d'opération de journal sont tous dans la plage comprise entre LOG0 (A0) (s'ouvre dans un nouvel onglet) et LOG4 (A4) (s'ouvre dans un nouvel onglet). Le nombre après le code d'opération de journal spécifie combien de sujets l'entrée de journal contient.

    • w=CALL ∧ μs[2]≠0 Vous pouvez appeler un autre contrat lorsque vous êtes statique, mais si vous le faites, vous ne pouvez pas lui transférer d'ETH.

  • w = SSTORE ∧ μg ≤ Gcallstipend Vous ne pouvez pas exécuter SSTORE (s'ouvre dans un nouvel onglet) à moins d'avoir plus de Gcallstipend (défini à 2300 dans l'Annexe G) gaz.

9.4.3 Validité de la destination de saut

Ici, nous définissons formellement ce que sont les codes d'opération JUMPDEST (s'ouvre dans un nouvel onglet). Nous ne pouvons pas simplement chercher la valeur d'octet 0x5B, car elle pourrait se trouver à l'intérieur d'un PUSH (et donc être des données et non un code d'opération).

Dans l'équation (162), nous définissons une fonction, N(i,w). Le premier paramètre, i, est l'emplacement du code d'opération. Le second, w, est le code d'opération lui-même. Si w∈[PUSH1, PUSH32], cela signifie que le code d'opération est un PUSH (les crochets définissent une plage qui inclut les extrémités). Dans ce cas, le prochain code d'opération est à i+2+(w−PUSH1). Pour PUSH1 (s'ouvre dans un nouvel onglet), nous devons avancer de deux octets (le PUSH lui-même et la valeur d'un octet), pour PUSH2 (s'ouvre dans un nouvel onglet), nous devons avancer de trois octets car il s'agit d'une valeur de deux octets, etc. Tous les autres codes d'opération de l'EVM ne font qu'un octet de long, donc dans tous les autres cas N(i,w)=i+1.

Cette fonction est utilisée dans l'équation (161) pour définir DJ(c,i), qui est l'ensemble (s'ouvre dans un nouvel onglet) de toutes les destinations de saut valides dans le code c, en commençant par l'emplacement du code d'opération i. Cette fonction est définie de manière récursive. Si i≥||c||, cela signifie que nous sommes à la fin ou après la fin du code. Nous n'allons plus trouver de destinations de saut, donc renvoyez simplement l'ensemble vide.

Dans tous les autres cas, nous examinons le reste du code en passant au code d'opération suivant et en obtenant l'ensemble à partir de celui-ci. c[i] est le code d'opération actuel, donc N(i,c[i]) est l'emplacement du code d'opération suivant. DJ(c,N(i,c[i])) est donc l'ensemble des destinations de saut valides qui commence au code d'opération suivant. Si le code d'opération actuel n'est pas un JUMPDEST, renvoyez simplement cet ensemble. S'il s'agit d'un JUMPDEST, incluez-le dans l'ensemble de résultats et renvoyez-le.

9.4.4 Arrêt normal

La fonction d'arrêt H peut renvoyer trois types de valeurs.

  • Si nous ne sommes pas dans un code d'opération d'arrêt, renvoyez , l'ensemble vide. Par convention, cette valeur est interprétée comme un faux booléen.
  • Si nous avons un code d'opération d'arrêt qui ne produit pas de sortie (soit STOP (s'ouvre dans un nouvel onglet) soit SELFDESTRUCT (s'ouvre dans un nouvel onglet)), renvoyez une séquence d'octets de taille zéro comme valeur de retour. Notez que c'est très différent de l'ensemble vide. Cette valeur signifie que l'EVM s'est réellement arrêtée, il n'y a simplement aucune donnée de retour à lire.
  • Si nous avons un code d'opération d'arrêt qui produit une sortie (soit RETURN (s'ouvre dans un nouvel onglet) soit REVERT (s'ouvre dans un nouvel onglet)), renvoyez la séquence d'octets spécifiée par ce code d'opération. Cette séquence est extraite de la mémoire, la valeur en haut de la pile (μs[0]) est le premier octet, et la valeur qui la suit (μs[1]) est la longueur.

H.2 Jeu d'instructions

Avant de passer à la dernière sous-section de l'EVM, 9.5, examinons les instructions elles-mêmes. Elles sont définies dans l'Annexe H.2 qui commence à la p. 30. Tout ce qui n'est pas spécifié comme changeant avec ce code d'opération spécifique est censé rester le même. Les variables qui changent sont spécifiées avec un <quelque chose>′.

Par exemple, regardons le code d'opération ADD (s'ouvre dans un nouvel onglet).

ValeurMnémoniqueδαDescription
0x01ADD21Opération d'addition.
μ′s[0] ≡ μs[0] + μs[1]

δ est le nombre de valeurs que nous retirons de la pile. Dans ce cas deux, car nous additionnons les deux valeurs supérieures.

α est le nombre de valeurs que nous rajoutons. Dans ce cas une, la somme.

Donc le nouveau sommet de la pile (μ′s[0]) est la somme de l'ancien sommet de la pile (μs[0]) et de l'ancienne valeur en dessous (μs[1]).

Au lieu de passer en revue tous les codes d'opération avec une "liste à en perdre la vue", cet article n'explique que les codes d'opération qui introduisent quelque chose de nouveau.

ValeurMnémoniqueδαDescription
0x20KECCAK25621Calculer le hash Keccak-256.
μ′s[0] ≡ KEC(μms[0] . . . (μs[0] + μs[1] − 1)])
μ′i ≡ M(μis[0],μs[1])

C'est le premier code d'opération qui accède à la mémoire (dans ce cas, en lecture seule). Cependant, il pourrait s'étendre au-delà des limites actuelles de la mémoire, nous devons donc mettre à jour μi. Nous le faisons en utilisant la fonction M définie dans l'équation 330 à la p. 30.

ValeurMnémoniqueδαDescription
0x31BALANCE11Obtenir le solde du compte donné.
...

L'adresse dont nous devons trouver le solde est μs[0] mod 2160. Le sommet de la pile est l'adresse, mais comme les adresses ne font que 160 bits, nous calculons la valeur modulo (s'ouvre dans un nouvel onglet) 2160.

Si σ[μs[0] mod 2160] ≠ ∅, cela signifie qu'il y a des informations sur cette adresse. Dans ce cas, σ[μs[0] mod 2160]b est le solde de cette adresse. Si σ[μs[0] mod 2160] = ∅, cela signifie que cette adresse n'est pas initialisée et que le solde est nul. Vous pouvez voir la liste des champs d'informations de compte dans la section 4.1 à la p. 4.

La deuxième équation, A'a ≡ Aa ∪ {μs[0] mod 2160}, est liée à la différence de coût entre l'accès au stockage chaud (stockage qui a été récemment consulté et qui est susceptible d'être mis en cache) et au stockage froid (stockage qui n'a pas été consulté et qui est susceptible de se trouver dans un stockage plus lent et plus coûteux à récupérer). Aa est la liste des adresses précédemment consultées par la transaction, qui devraient donc être moins chères d'accès, comme défini dans la section 6.1 à la p. 9. Vous pouvez en savoir plus sur ce sujet dans l'EIP-2929 (s'ouvre dans un nouvel onglet).

ValeurMnémoniqueδαDescription
0x8FDUP161617Dupliquer le 16ème élément de la pile.
μ′s[0] ≡ μs[15]

Notez que pour utiliser n'importe quel élément de la pile, nous devons le retirer, ce qui signifie que nous devons également retirer tous les éléments de la pile au-dessus de lui. Dans le cas de DUP<n> (s'ouvre dans un nouvel onglet) et SWAP<n> (s'ouvre dans un nouvel onglet), cela signifie devoir retirer puis rajouter jusqu'à seize valeurs.

9.5 Le cycle d'exécution

Maintenant que nous avons toutes les parties, nous pouvons enfin comprendre comment le cycle d'exécution de l'EVM est documenté.

L'équation (164) indique qu'étant donné l'état :

  • σ (état global de la chaîne de blocs)
  • μ (état de l'EVM)
  • A (sous-état, modifications à apporter à la fin de la transaction)
  • I (environnement d'exécution)

Le nouvel état est (σ', μ', A', I').

Les équations (165)-(167) définissent la pile et sa modification due à un code d'opération (μs). L'équation (168) est la modification du gaz (μg). L'équation (169) est la modification du compteur de programme (μpc). Enfin, les équations (170)-(173) spécifient que les autres paramètres restent les mêmes, à moins qu'ils ne soient explicitement modifiés par le code d'opération.

Avec cela, l'EVM est entièrement définie.

Conclusion

La notation mathématique est précise et a permis au livre jaune de spécifier chaque détail d'Ethereum. Cependant, elle présente quelques inconvénients :

  • Elle ne peut être comprise que par des humains, ce qui signifie que les tests de conformité (s'ouvre dans un nouvel onglet) doivent être écrits manuellement.
  • Les programmeurs comprennent le code informatique. Ils peuvent comprendre ou non la notation mathématique.

C'est peut-être pour ces raisons que les nouvelles spécifications de la couche de consensus (s'ouvre dans un nouvel onglet) sont écrites en Python. Il existe des spécifications de la couche d'exécution en Python (s'ouvre dans un nouvel onglet), mais elles ne sont pas complètes. Jusqu'à ce que l'intégralité du livre jaune soit également traduite en Python ou dans un langage similaire, le livre jaune continuera d'être utilisé, et il est utile de pouvoir le lire.