evm2rv prend en entrée le bytecode d’exécution EVM (runtime), c’est-à-dire la sortie de solc --bin-runtime, et le transforme en programme Rand. Il écrit du C, et rand-guest build compile ce C en une image RV32IM. L’image prend le même vecteur d’entrée que l’invité interpréteur EVM de Rand, evm.bin, et publie les mêmes huit mots de sortie. Elle exécute le contrat de façon native au lieu de l’interpréter opcode par opcode. Cette page présente l’ensemble du parcours sur un ERC-20.
Ce que c’est
| entrée | bytecode runtime EVM, en texte hexadécimal (.hex) ou en octets bruts |
| sortie | un crate C d’enrobage (shim) ; rand-guest build en fait un conteneur d’image |
| où il s’exécute | hors chaîne, sur la machine du développeur ; la chaîne ne voit que l’image |
| ce qui doit correspondre | le statut, les huit mots de sortie et le gas_used de evm.bin |
| dépôt | evm2rv/ dans le dépôt des circuits |
| chaîne d’outils figée | Rust 1.98.1, clang 23.1.1, le crate cc 1.4.6 |
Le traducteur prend du code d’exécution, non du code de création. Une preuve couvre un appel à un contrat déployé ; le constructeur ne s’exécute donc jamais. L’état que le constructeur écrirait fait plutôt partie de l’état préalable, sous forme de témoins de stockage.
La traduction comporte deux étapes. Toutes deux donnent les mêmes résultats et le même gas ; chacune est un programme à part, avec son propre hc.
| étape | option | ce qu’elle fait |
|---|---|---|
| une | --stage 1 | chaque opcode sur une pile en mémoire |
| deux | --stage 2, par défaut | les mots de pile de chaque bloc dans des variables locales C, constantes repliées à la traduction |
Le vecteur d’entrée est celui de l’interpréteur : le code, les données d’appel (calldata), l’appelant et les autres mots d’environnement, la limite de gas, la racine de l’état préalable, et un témoin de stockage par emplacement que l’appel touche. Les sorties sont out[0], le statut (1 succès, 0 revert, 2 arrêt exceptionnel), et out[1..8], le condensat EVM_OUT portant sur le hachage du code, les deux racines d’état, les données de retour et les journaux.
Modèle de confiance
Un vérificateur contrôle un contrat traduit en deux étapes.
hc == translate(bytecode). Relancezevm2rvsur le bytecode publié avec le même--stageet le même--chain-id, reconstruisez avec la chaîne d’outils figée, puis comparezhc. Le crate généré figeccexactement et embarque son propreCargo.lock.- La garde de code lie
hcau bytecode. La logique traduite est compilée dans l’image, maisCODECOPYetCODESIZElisent le code du vecteur d’entrée. Sans contrôle, un mêmehcpourrait s’exécuter avec des octets de code choisis par l’appelant.contract.cporte donc un condensat Poseidon2 du bytecode source (sa longueur et ses octets), et le shim hache de la même façon le code du vecteur d’entrée avant d’exécuter le moindre code traduit. Tout autre code est refusé : statut 2,gas_used0, arrêtOutOfBounds.
Le condensat EVM_OUT lie aussi le keccak256 du code, comme celui de l’interpréteur. La chaîne n’enregistre ni le bytecode source ni son hachage. Pour permettre à d’autres de vérifier hc, publiez le bytecode, ou le code source Solidity et la version exacte de solc.
La garde coûte 1 008 cycles sur le transfert. Un code différent d’un seul octet de la source est refusé en 30 418 cycles à l’étape deux (palier 16).
Couverture, et ce qui provoque un trap
Opcodes. Le jeu Shanghai. Compilez avec --evm-version shanghai : les cibles plus récentes émettent MCOPY et d’autres opcodes de Cancun, qui provoquent un trap.
Opcodes d’environnement :
| opcode | traduction |
|---|---|
ADDRESS, CALLER, CALLVALUE, CALLDATASIZE, CODESIZE | depuis le vecteur d’entrée, comme les lit l’interpréteur |
CHAINID | la valeur de --chain-id, intégrée à la traduction, de sorte que hc la lie ; obligatoire lorsque le code contient CHAINID |
ORIGIN | égal à CALLER (un seul appel, pas de relais) |
GASPRICE, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, SELFBALANCE, BASEFEE, BLOCKHASH | trap (statut 2), comme dans l’interpréteur |
Les neuf opcodes de contexte de bloc provoquent un trap tant qu’aucune conception ne permet de les lier à un bloc. Une entrée privée ne peut pas porter un fait de bloc, car un vérificateur ne peut pas ouvrir H_IN. Tant que cette décision n’est pas prise, un contrat qui lit l’heure ou le numéro de bloc (une échéance de permit, par exemple) provoque un trap.
Appels. CALL, CALLCODE, DELEGATECALL et STATICCALL exécutent une précompilation lorsque la cible est l’adresse 1 à 9. Toute autre cible provoque un trap. Une valeur non nulle sur CALL ou CALLCODE provoque un trap : il n’existe pas de modèle de solde.
Précompilations. Les neuf s’exécutent en logiciel. Certaines sont trop lentes pour être prouvées aujourd’hui, car une preuve contient au plus 2²⁰ lignes :
| précompilation | vecteur | cycles | tient dans 2²⁰ |
|---|---|---|---|
| 1 ecrecover | ValidKey | 14 510 525 | non |
| 2 sha256 | ”abc” / 200 octets | 2 791 / 5 289 | oui |
| 3 ripemd160 | ”abc” | 12 195 | oui |
| 4 identity | 100 octets | 4 498 | oui |
| 5 modexp | nagydani-1-square / -1-pow0x10001 | 122 332 / 874 548 | oui |
| 5 modexp | eip_example1 (256 bits) | 11 170 118 | non |
| 5 modexp | nagydani-5-pow0x10001 (1 024 octets) | 55 076 950 | non |
| 6 bn256 add | chfast1 | 978 262 | oui, de justesse |
| 7 bn256 mul | chfast1 | 3 060 669 | non |
| 8 bn256 pairing | 1 paire / 2 paires | 1 853 656 212 / 2 019 000 469 | non |
| 9 blake2f | 12 tours | 14 493 | oui, jusqu’à environ 1 800 tours |
La base et le module de modexp sont plafonnés à 1 024 octets chacun.
Divergences acceptées. Le statut, les huit mots et le gas_used doivent correspondre. Trois différences sont acceptées : le type d’arrêt peut différer (le gas statique est facturé en tête de chaque bloc, de sorte que OutOfGas et un autre arrêt peuvent s’échanger ; les deux donnent le statut 2 avec tout le gas consommé) ; la traduction implémente CHAINID, ORIGIN et les appels de précompilations, que l’interpréteur fait échouer par un trap ; et la garde de code refuse tout code autre que sa source.
Les chiffres
Appels ERC-20, garde de code incluse. Cycles issus de rand-guest run ; palier entre parenthèses.
| appel | interpréteur (evm.bin) | étape une | étape deux (par défaut) |
|---|---|---|---|
transfer(BOB, 250) | 121 638 (18) | 80 211 (18) | 66 235 (18) |
approve(BOB, 5) | 85 645 (18) | 56 261 (16) | 48 119 (16) |
transferFrom(ALICE, BOB, 100) | 161 434 (18) | 110 074 (18) | 88 824 (18) |
| programme | mots | hc |
|---|---|---|
evm.bin, l’interpréteur | 18 009 | 7e1aea2b…854c08 |
| ERC-20, étape une | 15 804 | 9cdb79e7…2fd048c8 |
| ERC-20, étape deux | 11 686 | a0feae92a7311c9562495717100eb7aea71270a38ed435d06dc31387e0ea8ff6 |
L’étape deux exécute les trois appels en 54,5 %, 56,2 % et 55,0 % des cycles de l’interpréteur. Le transfert reste au palier 18 : ses 66 235 cycles plus 3 155 lignes de condensat font 69 390 lignes, soit 3 855 de plus que les 65 535 du palier 16.
Environ 60 % d’un transfert à l’étape deux (environ 39 928 cycles) correspondent au socle que les deux images partagent : décodage de l’entrée, vérification des témoins de stockage, appels keccak256 de l’ABI et condensat de sortie. Aucun travail de traduction ne descend sous ce plancher.
Pas à pas : un ERC-20, du Solidity à des sorties identiques
Chaque commande s’exécute depuis la racine d’une copie du dépôt des circuits à 7ef3220, avec la chaîne d’outils de Deploy a Rust program et clang 23.1.1 de Deploy a RISC-V program. Les sorties longues sont coupées là où figure ….
0. Construire les outils
(cd evm2rv && cargo +1.98.1 build --release)
(cd rand-guest && cargo +1.98.1 build --release)
1. Compiler le Solidity en bytecode d’exécution
Le contrat est le fichier versionné guests-compiled/evm/contracts/ERC20.sol : un ERC-20 autonome avec la disposition de stockage d’OpenZeppelin v4, pragma solidity 0.8.37. Utilisez exactement solc 0.8.37 ; une autre version optimise différemment et produit un bytecode différent.
| version | v0.8.37 depuis github.com/ethereum/solidity/releases |
| ressource macOS | solc-macos, sha256 a27396e7732aa52e80ff89ad7bd8a2e46fec2a6dcc4ef20cd16e5e0c502d6821 |
| ressource Linux | solc-static-linux, de la même version |
$ curl -sSL -o solc https://github.com/ethereum/solidity/releases/download/v0.8.37/solc-macos
$ shasum -a 256 solc
a27396e7732aa52e80ff89ad7bd8a2e46fec2a6dcc4ef20cd16e5e0c502d6821 solc
$ chmod +x solc && ./solc --version
solc, the solidity compiler commandline interface
Version: 0.8.37+commit.f401782d.Darwin.appleclang
$ (cd guests-compiled/evm && ../../solc --optimize --optimize-runs 200 --evm-version shanghai \
--bin-runtime contracts/ERC20.sol) | tee solc.out
======= contracts/ERC20.sol:ERC20 =======
Binary of the runtime part:
608060405234801561000f575f5ffd5b5060043610610060575f3560e01c8063095ea7b31461006457806318160ddd1461008c57806323b872dd1461…
$ tail -1 solc.out > evm2rv/target/mytoken.hex
La dernière ligne est le bytecode d’exécution, de 1 296 octets. Il est identique octet pour octet au fichier versionné guests-compiled/evm/contracts/erc20.runtime.hex ; SOLC=$PWD/solc bash guests-compiled/evm/contracts/build.sh le vérifie et affiche contracts: erc20.runtime.hex reproduces.
Les options comptent :
--bin-runtime: le code déployé, non le code de création.--evm-version shanghai: la cible par défaut de 0.8.37 émetMCOPY, qui provoque un trap.--optimize --optimize-runs 200: un profil pour contrat déployé.
2. Traduire
$ evm2rv/target/release/evm2rv evm2rv/target/mytoken.hex --out evm2rv/target/mytoken --chain-id 12
1296 code bytes: 74 blocks, 798 opcodes, 51 jumpdests (stage 2)
CHAINID is the constant 12
no trapping opcodes present
wrote evm2rv/target/mytoken (crate mytoken): contract.c, Cargo.toml, Cargo.lock, build.rs, src/main.rs, shim.ld
--outdoit se trouver dans une copie du dépôt des circuits. Sans lui,evm2rvse contente d’analyser le code.- Tout opcode provoquant un trap serait listé ici comme avertissement, avec son pc.
- Cette session a été capturée avec
--chain-id 12. Cet ERC-20 ne contient pasCHAINID;--chain-idne change donc pas sonhc. Un contrat qui litCHAINIDexige l’option ; réglez-la sur la chaîne où vous déploierez (--chain-id 19pour la chaîne en production).
contract.c se termine par le condensat de la garde de code :
const uint32_t *evm_code_digest(void) {
static const uint32_t d[8] = {0xf679280fu, 0x86c10e83u, 0x7e8f1f39u, 0xa2377de3u, 0x8a2e282fu, 0xc54cad42u, 0xddfaf009u, 0x7d28a8afu};
return d;
}
3. Construire l’image
Le plafond par défaut est de 4 096 mots ; passez donc --max-words 65535.
$ rand-guest/target/release/rand-guest build evm2rv/target/mytoken --max-words 65535
…
11686 words against a cap of 65535 (fits); 0 ecall(s) with a non-static a7
OK
wrote evm2rv/target/mytoken/image.bin and its .sha256 (11686 words, hc a0feae92a7311c9562495717100eb7aea71270a38ed435d06dc31387e0ea8ff6, program id f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d)
$ rand-guest/target/release/rand-guest info evm2rv/target/mytoken/image.bin
form: image container
text 11147 words at 0x10000; data 408 words (205 non-zero) at 0x1ae2c; prologue 539 words; program 11686 words from base_pc 0xf794
hc a0feae92a7311c9562495717100eb7aea71270a38ed435d06dc31387e0ea8ff6
program id f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d
11686 words against a cap of 4096: does not fit
Voici le hc publié de l’étape deux. Le nom du crate n’y change rien.
4. Construire un vecteur d’entrée et exécuter un transfert
L’exemple erc20_vector affiche les vecteurs du test de parité sous forme de mots d’entrée : transfer (ALICE envoie 250 de ses 1 000 à BOB), approve (ALICE autorise BOB à dépenser 5) et transferFrom (BOB déplace 100 des 1 000 d’ALICE au titre d’une allocation de 300). Pour votre propre contrat, construisez un EvmCall de la même façon en Rust ; l’exemple sert de modèle.
$ (cd evm2rv && cargo +1.98.1 run -q --release --example erc20_vector -- transfer > target/transfer.words)
921 input words
$ rand-guest/target/release/rand-guest run evm2rv/target/mytoken/image.bin --input $(cat evm2rv/target/transfer.words)
out[0] = 1
out[1] = 513227413
out[2] = 3087537901
out[3] = 995619457
out[4] = 4004100029
out[5] = 234390638
out[6] = 3262201797
out[7] = 3842361427
cycles 66235
tier 18
5. Comparer avec l’interpréteur
La même entrée sur l’invité interpréteur :
$ rand-guest/target/release/rand-guest run guests-compiled/bin/evm.bin --input $(cat evm2rv/target/transfer.words)
out[0] = 1
out[1] = 513227413
out[2] = 3087537901
out[3] = 995619457
out[4] = 4004100029
out[5] = 234390638
out[6] = 3262201797
out[7] = 3842361427
cycles 121638
tier 18
And the interpreter run natively on the host, the tests’ oracle:
Et l’interpréteur exécuté nativement sur l’hôte, l’oracle des tests :
$ (cd evm2rv && cargo +1.98.1 run -q --release --example erc20_vector -- transfer --expected)
out[0] = 1
out[1] = 513227413
…
out[7] = 3842361427
interpreter (native): status 1, Return, gas_used 29956
La vérification en une seule commande :
$ diff <(rand-guest/target/release/rand-guest run evm2rv/target/mytoken/image.bin --input $(cat evm2rv/target/transfer.words) | grep '^out') \
<(rand-guest/target/release/rand-guest run guests-compiled/bin/evm.bin --input $(cat evm2rv/target/transfer.words) | grep '^out') \
&& echo "the eight words are identical"
the eight words are identical
Répétez les étapes 4 et 5 avec approve et transferFrom ; les deux donnent des mots identiques. cd evm2rv && cargo +1.98.1 test --release exécute ces vecteurs et cinq autres sous les deux étapes, et vérifie aussi gas_used (70 tests, plus 2 preuves ignorées).
| traduit (étape deux) | interpréteur (evm.bin) | |
|---|---|---|
| huit mots de sortie | identiques | identiques |
| cycles, transfer | 66 235 | 121 638 |
| palier | 18 | 18 |
| mots de programme | 11 686 | 18 009 |
6. Déployer
rand program deploy evm2rv/target/mytoken/image.bin
Le wallet affiche l’identifiant du programme, le nombre de mots et hc, puis vérifie le plafond de la chaîne avant de produire la moindre preuve. L’image compte 11 686 mots : il faut donc une chaîne dont le genesis fixe max_program_words à au moins 11 686. La chain 12 appliquait la valeur par défaut, 4 096, et le déploiement y a donc été refusé. La chain 13, la chaîne v0.4, fixait max_program_words à 65 535, et toutes les chaînes suivantes conservent cette valeur. La chain 20, la chaîne en production (genesis 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135), conserve aussi les limites de la chain 13 pour les blocs, l’enveloppe d’appel et les entrées publiques, et réduit de moitié le plafond de preuve à 4 MiB (4 194 304 octets, depuis la chain 17 ; les chains 13 à 16 fixaient 8 MiB). Le plafond de mots n’est plus la seule barrière. Le genesis de la chain 20 active hardening_v6, et sous ce régime le déploiement d’un nombre de mots de programme supérieur à ce qu’un appel peut contenir au plafond d’appel du palier 14 est invalide (ProgramUncallable) : au plus 8 180 mots sans entrée publique. Cette image compte 11 686 mots ; la chain 20 refuse donc le déploiement, et refuserait aussi les appels : chaque appel ERC-20 est prouvé au palier 16 ou 18, au-dessus du plafond d’appel du palier 14 (étape 7). L’agrégation de blocs est désactivée sur la chain 20 (son genesis n’a pas de section aggregation) ; ses nœuds d’amorçage sont listés dans Exploiter un validateur. Les frais de déploiement sont 0.001 + 0.0001 × 11,686 = 1,1696 RAND, soit les frais qu’a payés le déploiement sur la chain 13 ci-dessous.
Ce déploiement a eu lieu sur la chain 13 le 2026-09-19, alors qu’elle était la chaîne en production (profil FRI de production, genesis 8123ccac1883a45750e4df6964fb7cd3f0b321798cde4c0ef406a0293939ece3), depuis le wallet genesis shielded-1. Chaque chaîne suivante (14 à 19) a été créée comme une chaîne neuve, sans les programmes de la chain 13, et l’image n’a été redéployée sur aucune d’elles ; la transaction et la hauteur ci-dessous sont donc celles de la chain 13 et ne se résolvent plus dans l’explorateur. L’identifiant du programme est un hachage de base_pc et des seuls mots : la même image obtient donc le même identifiant sur toute chaîne qui admet le déploiement. La sortie du wallet, avec ses lignes de progression omises (…) :
$ rand program deploy <image.bin>
program id: f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d (11686 words, hc 92aefea0951c31a717574962aeb70e10a37012a7d035d48e8713c36df68feae0)
…
proved in 92.2s: tier 14, 1419655 bytes
submitted deploy c7a66dc4d9cdb7f9bfc4bde01f618c356e5abad7097e6bf7dc04e2e49c53acd0
…
| identifiant du programme | f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d |
| image | 11 686 mots ; hc a0feae92… selon la notation de rand-guest, 92aefea0… selon celle du wallet |
| lot de frais (lot 2 entrées-2 sorties de la chain 13) | palier 14, 1 419 655 octets, prouvé en 92,2 s |
| tx de déploiement (chain 13) | c7a66dc4d9cdb7f9bfc4bde01f618c356e5abad7097e6bf7dc04e2e49c53acd0 |
| validée à la hauteur (chain 13) | 381 |
Depuis la chain 14, le lot de paiement est l’unique lot à 4 entrées et 4 sorties d’actifs masqués que porte chaque transaction, prouvé par rapport à la liaison de la transaction. Il reste au palier 14 : environ 100 s et environ 1,42 MB avec le profil de production sur les chains 14 et 15. Depuis la chain 17, un lot porte aussi une preuve d’autorisation de palier 10 (autorisation scindée) ; sur les chains 18 et 19, le lot de frais d’une transaction représente donc environ 1,49 MB de preuve de lot et environ 1,37 MB de preuve d’autorisation.
7. Appeler
Un appel de transfert porte le vecteur de 921 mots en entrées privées, sous le plafond d’entrées de n’importe quelle chaîne (4 295 mots par défaut, 16 071 sur la chain 20), et aucun mot public. Il ne peut pas être soumis à la chain 20 : le programme n’y est pas déployable (étape 6), le transfert est prouvé au palier 18, et depuis fullnode v0.5.6 (2026-09-25) un nœud refuse toute preuve d’appel au-dessus du palier 14 avec CallTierTooHigh, avant de construire une clé de vérification. La commande ci-dessous produit la preuve en local et la chaîne en refuse le résultat ; les mesures sont des preuves hors chaîne. rand call prend chaque mot sous la forme d’un --input répété :
rand call f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d \
$(for w in $(cat evm2rv/target/transfer.words); do printf -- '--input %s ' "$w"; done)
Mesure désormais effectuée. Une preuve de palier 18 a été interrompue sur un portable de 48 GB à 24,7 GB, mais sur un droplet de 128 GB (m-16vcpu-128gb) l’appel transfer traduit a été prouvé : 85,0 GB de RSS maximale, 3 230,5 s (53,8 min), une preuve de 811 600 octets. Elle a été vérifiée en 101,4 s sur le droplet et en 50,2 s avec 3,66 GB de RSS sur un portable Apple silicon de 48 GB (mémoire de preuve). Sous le barème fixe par palier du registre, la règle des chaînes sans section gas (jusqu’à la chain 17), les frais d’appel au palier 18 étaient de 0,0024 RAND. Les chains 18 et 19 tarifient un appel selon sa limite de gas déclarée et ses octets, 100 unités par gas et 800 par KiB aux prix du genesis, et n’admettent aucun appel au-dessus du palier 14.
L’appel sur la chain 13, 2026-09-19
L’appel approve(BOB, 5) (649 mots privés, palier 16) a été exécuté sur la chain 13 le 2026-09-19, alors qu’elle était la chaîne en production, avec le profil FRI de production, depuis le même wallet. Aucun appel n’a été fait sur une chaîne ultérieure, où le programme n’est pas déployé, et la chain 20 refuse cette preuve d’appel de palier 16 (CallTierTooHigh). La sortie du wallet, avec ses lignes de progression omises (…) :
$ rand call f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d --input <w0> --input <w1> … # the 649 words of approve(BOB, 5)
…
proved in 388.7s: tier 16, 3412405 bytes, outputs [1, 942495465, 790002515, 1351749335, 1059083501, 2923046783, 2814575942, 696258848]
…
proved in 96.0s: tier 14, 1420423 bytes
submitted call 279e1f62abf3f177ca71b08c633e051c37bc4720118c79ddb00936f036c4bfa7
…
| preuve d’appel | palier 16, 3 412 405 octets, prouvée en 388,7 s |
| lot de frais (lot 2 entrées-2 sorties de la chain 13) | palier 14, 1 420 423 octets, prouvé en 96,0 s |
| enveloppe scellée | 2 700 octets |
| tx d’appel (chain 13) | 279e1f62abf3f177ca71b08c633e051c37bc4720118c79ddb00936f036c4bfa7 |
| ancrée à / validée à | hauteur 802 / hauteur 919 |
| frais | 0,00357 RAND |
reçu h_pub | null |
| sorties du reçu | égales aux huit mots de l’interpréteur pour le même vecteur |
| durée totale, RSS maximale | 622,8 s, 22,9 GB, sur un portable M-series de 48 GB |
- La preuve de production fait 3 412 405 octets, soit plus que le plafond de preuve de 2 MiB de la chain 12. La chain 12 ne pouvait pas accepter cet appel ; le plafond de 8 MiB de la chain 13 le pouvait. Les chains 14 à 16 ont gardé le même plafond, et les chains 17 à 20 fixent 4 MiB (4 194 304 octets), sous lequel une preuve de cette taille tient encore. Depuis v0.5.6, un nœud refuse l’appel selon son palier avant d’en examiner la taille.
- Les frais incluent le terme d’octets du barème de la chain 13 : la preuve de 3,41 MB dépasse la franchise de 2 MiB + 18 432 octets. Depuis la chain 18, chaque appel paie par KiB dès l’octet 0.
Les huit mots du reçu sont ceux de l’étape 4. La chaîne ne conserve aucun état EVM : l’appelant fournit la racine de l’état préalable et les témoins de stockage, et la racine de l’état postérieur figure dans le condensat EVM_OUT.
Limites
- Pas de contexte de bloc. Les neuf opcodes de contexte de bloc déclenchent un trap tant qu’aucune conception de liaison au bloc n’existe.
- Pas de transferts de valeur. Un
CALLde valeur non nulle déclenche un trap. Les appels vers des contrats autres que les précompilés déclenchent un trap. - ecrecover, la multiplication bn256, le pairing et les grands modexp ne peuvent pas être prouvés aujourd’hui. Chacun dépasse le plafond de 2²⁰ lignes du palier ; il leur faut des tables de coprocesseur.
- Pas d’état EVM sur la chaîne. Un appel est une seule preuve sur une seule exécution ; c’est l’appelant qui porte l’état.
- Ni le déploiement ni aucun appel ERC-20 n’est admissible sur la chain 20. Avec le
hardening_v6de la chain 20, un déploiement de plus de mots qu’un appel de palier 14 ne peut en contenir est invalide (ProgramUncallable; 8 180 mots au plus), et l’image en compte 11 686. Depuis fullnode v0.5.6, une preuve d’appel doit être de palier 14 ou inférieur (CallTierTooHigh) ;approvese prouve au palier 16,transferettransferFromau palier 18. Le même contrôle plafonne les tables de hachage qu’un appel peut déclarer : 2¹² lignes Keccak (128 permutations) et 2¹³ lignes SHA-256 (128 compressions). Les appels se prouvent néanmoins hors chaîne, comme ci-dessous. - L’index de stockage a une profondeur de 32 bits.
evm-coreplace une feuille de stockage de contrat selon les 32 bits de poids fort dekeccak256(slot); un slot qui entre en collision avec un slot choisi coûte donc environ 2³² hachages Keccak à trouver, et tant qu’il contient une valeur non nulle, le slot choisi est gelé (ni falsification, ni valeur perdue). Ce risque est dormant :evm.bincompte 18 009 mots, et un appel au plafond du palier 14 en contient au plus 8 184, de sorte qu’aucun runtime EVM n’est appelable sur aucune chaîne. Ledocs/confidential.mddu fullnode consigne la condition préalable à côté du plafond d’appel (audit v6, ZKV-6 ; surmaindepuis le 2026-09-30, après le tag v0.6.7) : avant qu’une chaîne ne lève le plafond d’appel du palier 14 ou n’admette un runtime EVM assez petit pour être appelé, l’index de stockage doit être approfondi etevm.binde nouveau épinglé. - Prouvé. Une preuve de transfert de première étape (palier 18) a été tuée par SIGKILL sur un portable de 48 Go à 24,7 Go après 1 016 s, mais l’appel
transferde seconde étape a été prouvé sur un droplet de 128 Go (m-16vcpu-128gb) : 85,0 Go de RSS en pic, 3 230,5 s (53,8 min), une preuve de 811 600 octets, vérifiée en 101,4 s (droplet) et en 50,2 s à 3,66 Go de RSS (portable de 48 Go). La preuve de transfert de l’interpréteurevm.bins’en est approchée de très près : 85,5 Go, 3 143,3 s (52,4 min), une preuve de 805 108 octets — et les deux preuves portent des mots de sortie publics identiques, ce qui confirme la parité sur le prouveur réel.approve(palier 16, traduit) est bien moins coûteux : 21,7 Go, 786,7 s (13,1 min), une preuve de 798 930 octets. La traduction ne réduit le coût de preuve que lorsqu’elle fait passer un appel dans un palier inférieur. - La chaîne n’enregistre pas le bytecode. Publiez-le afin que d’autres puissent reconstruire
hc.