Comprendre les spécifications de l'EVM du livre jaune
Le livre jaune (opens in a new tab) 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. 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. 12-14) comprend 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 classique, cela signifie la mémoire, le contenu des registres, etc.
Une machine de Turing (opens in a new tab) 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 permet de prouver plus facilement divers théorèmes sur ce qui est calculable et ce qui ne l'est pas.
Le terme Turing-complet (opens in a new tab) 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 n'est pas le cas de 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 (opens in a new tab) est un ordinateur qui stocke les données intermédiaires non pas dans des registres, mais dans une pile (opens in a new tab). 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 (opens in a new tab) 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 (opens in a new tab) 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 :
- Lorsqu'un contrat crée un autre contrat (en utilisant
CREATE(opens in a new tab) ouCREATE2(opens in a new tab)), le code du constructeur du contrat provient de la mémoire. - Lors de la création de n'importe quel contrat, le code du constructeur s'exécute puis renvoie le code du contrat réel, également à 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 au 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. 28, sous l'équation (327)), et trouvez le groupe de coût dans l'équation (324). Cela vous donne une fonction de coût, qui dans la plupart des cas utilise les paramètres de l'Annexe G (p. 27).
Par exemple, le code d'opération CALLDATACOPY (opens in a new tab) 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, ⌈ <valeur> ⌉ 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. 13. 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 (opens in a new tab), 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 (opens in a new tab) et RETURNDATACOPY (opens in a new tab), 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 en cours de copie. En rassemblant tout, le coût inhérent de CALLDATACOPY (opens in a new tab) est de 3 gaz plus 3 par mot de données copié.
Coût d'exécution
Le coût d'exécution du code que nous appelons.
- Dans le cas de
CREATE(opens in a new tab) etCREATE2(opens in a new tab), le constructeur du nouveau contrat. - Dans le cas de
CALL(opens in a new tab),CALLCODE(opens in a new tab),STATICCALL(opens in a new tab), ouDELEGATECALL(opens in a new tab), le contrat que nous appelons.
Coût d'expansion de la mémoire
Le coût d'expansion de la mémoire (si nécessaire).
Dans l'équation 324, cette valeur est écrite comme Cmem(μi')-Cmem(μi). 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 326 : Cmem(a) = Gmemory × a + ⌊a2 ÷ 512⌋. ⌊x⌋ est la fonction partie entière par défaut (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 de façon linéaire 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 - ils ne prennent 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.
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ètre | Code d'opération pour accéder aux données | Code Solidity pour accéder aux données |
|---|---|---|
| Ia | ADDRESS (opens in a new tab) | address(this) |
| Io | ORIGIN (opens in a new tab) | tx.origin |
| Ip | GASPRICE (opens in a new tab) | tx.gasprice |
| Id | CALLDATALOAD (opens in a new tab), etc. | msg.data |
| Is | CALLER (opens in a new tab) | msg.sender |
| Iv | CALLVALUE (opens in a new tab) | msg.value |
| Ib | CODECOPY (opens in a new tab) | address(this).code |
| IH | Champs de l'en-tête de bloc, tels que NUMBER (opens in a new tab) et DIFFICULTY (opens in a new tab) | block.number, block.difficulty, etc. |
| Ie | Profondeur de la pile d'appels pour les appels entre contrats (y compris la création de contrat) | |
| Iw | L'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ètre | Défini dans la section | Signification |
|---|---|---|
| σ | 2 (p. 2, équation 1) | L'état de la chaîne de blocs |
| g | 9.3 (p. 13) | Gaz restant |
| A | 6.1 (p. 8) | Sous-état accumulé (modifications prévues à la fin de la transaction) |
| o | 9.3 (p. 13) | 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 137-142 nous donnent les conditions initiales pour exécuter l'EVM :
| Symbole | Valeur initiale | Signification |
|---|---|---|
| μg | g | Gaz restant |
| μpc | 0 | Compteur de programme, l'adresse de la prochaine instruction à exécuter |
| μm | (0, 0, ...) | Mémoire, initialisée avec des zéros |
| μi | 0 | Emplacement mémoire le plus élevé utilisé |
| μs | () | La pile, initialement vide |
| μo | ∅ | La sortie, ensemble vide jusqu'à ce que nous nous arrêtions avec des données de retour (RETURN (opens in a new tab) ou REVERT (opens in a new tab)) ou sans (STOP (opens in a new tab) ou SELFDESTRUCT (opens in a new tab)). |
L'équation 143 nous indique qu'il y a quatre conditions possibles à chaque instant pendant l'exécution, et ce qu'il faut en faire :
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 (True), le nouvel état est identique à l'ancien (sauf que le gaz est brûlé) car les modifications n'ont pas été implémentées.- Si le code d'opération en cours d'exécution est
REVERT(opens in a new tab), le nouvel état est le même que l'ancien état, une partie du gaz est perdue. - Si la séquence d'opérations est terminée, comme indiqué par un
RETURN(opens in a new tab)), l'état est mis à jour vers le nouvel état. - Si nous ne sommes pas dans l'une des conditions de fin 1 à 3, l'exécution continue.
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 (Ib[μpc]) est le code d'opération. Sinon, le code d'opération est défini comme STOP (opens in a new tab).
Comme il s'agit d'une machine à pile (opens in a new tab), 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 (opens in a new tab), elle utilise donc ∨ pour un OU logique (opens in a new tab) et ∧ pour un ET logique (opens in a new tab).
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(opens in a new tab) et l'adresse n'est pas unJUMPDEST(opens in a new tab). Les sauts ne sont valides que lorsque la destination est unJUMPDEST(opens in a new tab). -
w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) Le code d'opération est
JUMPI(opens in a new tab), la condition est vraie (non nulle) donc le saut devrait se produire, et l'adresse n'est pas unJUMPDEST(opens in a new tab). Les sauts ne sont valides que lorsque la destination est unJUMPDEST(opens in a new tab). -
w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || Le code d'opération est
RETURNDATACOPY(opens in a new tab). 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 (opens in a new tab) 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 changement d'état, cela ne peut pas se produire.
La fonction W(w,μ) est définie plus loin dans l'équation 150. 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 compris entre
LOG0(A0) (opens in a new tab) etLOG4(A4) (opens in a new tab). 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(opens in a new tab) à 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 (opens in a new tab). 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 une donnée et non un code d'opération).
Dans l'équation (153), 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 se trouve à i+2+(w−PUSH1). Pour PUSH1 (opens in a new tab), nous devons avancer de deux octets (le PUSH lui-même et la valeur d'un octet), pour PUSH2 (opens in a new tab), 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 (152) pour définir DJ(c,i), qui est l'ensemble (opens in a new tab) 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 nous renvoyons 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 prochain code d'opération. DJ(c,N(i,c[i])) est donc l'ensemble des destinations de saut valides qui commence au prochain code d'opération. Si le code d'opération actuel n'est pas un JUMPDEST, renvoyez simplement cet ensemble. S'il s'agit de 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(opens in a new tab) soitSELFDESTRUCT(opens in a new tab)), 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(opens in a new tab) soitREVERT(opens in a new tab)), 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. 29. 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 (opens in a new tab).
| Valeur | Mnémonique | δ | α | Description |
|---|---|---|---|---|
| 0x01 | ADD | 2 | 1 | Opé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 interminable, cet article n'explique que les codes d'opération qui introduisent quelque chose de nouveau.
| Valeur | Mnémonique | δ | α | Description |
|---|---|---|---|---|
| 0x20 | KECCAK256 | 2 | 1 | Calcule le hachage Keccak-256. |
| μ′s[0] ≡ KEC(μm[μs[0] . . . (μs[0] + μs[1] − 1)]) | ||||
| μ′i ≡ M(μi,μs[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 faisons cela en utilisant la fonction M définie dans l'équation 328 à la p. 29.
| Valeur | Mnémonique | δ | α | Description |
|---|---|---|---|---|
| 0x31 | BALANCE | 1 | 1 | Obtient 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 (opens in a new tab) 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'information du 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. 8. Vous pouvez en savoir plus sur ce sujet dans l'EIP-2929 (opens in a new tab).
| Valeur | Mnémonique | δ | α | Description |
|---|---|---|---|---|
| 0x8F | DUP16 | 16 | 17 | Duplique 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 qui se trouvent au-dessus de lui. Dans le cas de DUP<n> (opens in a new tab) et SWAP<n> (opens in a new tab), 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 (155) indique qu'étant donné l'état :
- σ (état global de la chaîne de blocs)
- μ (état de l'EVM)
- A (sous-état, modifications devant se produire à la fin de la transaction)
- I (environnement d'exécution)
Le nouvel état est (σ', μ', A', I').
Les équations (156)-(158) définissent la pile et sa modification due à un code d'opération (μs). L'équation (159) est la modification du gaz (μg). L'équation (160) est la modification du compteur de programme (μpc). Enfin, les équations (161)-(164) précisent 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é (opens in a new tab) 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 (opens in a new tab) sont écrites en Python. Il existe des spécifications de la couche d'exécution en Python (opens in a new tab), 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.