Rand Protocol
Documentation
Programmes RISC-V

Le transpileur Ethereum

evm2rv traduit le bytecode d'exécution EVM vers l'ISA RISC-V de Rand : ce que c'est, le modèle de confiance et sa protection du code, les opcodes et precompiles couverts et les cas où il s'interrompt, les chiffres mesurés, et un exemple complet d'ERC-20, du Solidity à `solc --bin-runtime` jusqu'à des sorties identiques mot pour mot à celles de l'interpréteur EVM.

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éebytecode runtime EVM, en texte hexadécimal (.hex) ou en octets bruts
sortieun crate C d’enrobage (shim) ; rand-guest build en fait un conteneur d’image
où il s’exécutehors chaîne, sur la machine du développeur ; la chaîne ne voit que l’image
ce qui doit correspondrele statut, les huit mots de sortie et le gas_used de evm.bin
dépôtevm2rv/ dans le dépôt des circuits
chaîne d’outils figéeRust 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.

étapeoptionce qu’elle fait
une--stage 1chaque opcode sur une pile en mémoire
deux--stage 2, par défautles 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.

  1. hc == translate(bytecode). Relancez evm2rv sur le bytecode publié avec le même --stage et le même --chain-id, reconstruisez avec la chaîne d’outils figée, puis comparez hc. Le crate généré fige cc exactement et embarque son propre Cargo.lock.
  2. La garde de code lie hc au bytecode. La logique traduite est compilée dans l’image, mais CODECOPY et CODESIZE lisent le code du vecteur d’entrée. Sans contrôle, un même hc pourrait s’exécuter avec des octets de code choisis par l’appelant. contract.c porte 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_used 0, arrêt OutOfBounds.

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 :

opcodetraduction
ADDRESS, CALLER, CALLVALUE, CALLDATASIZE, CODESIZEdepuis le vecteur d’entrée, comme les lit l’interpréteur
CHAINIDla 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, BLOCKHASHtrap (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écompilationvecteurcyclestient dans 2²⁰
1 ecrecoverValidKey14 510 525non
2 sha256”abc” / 200 octets2 791 / 5 289oui
3 ripemd160”abc”12 195oui
4 identity100 octets4 498oui
5 modexpnagydani-1-square / -1-pow0x10001122 332 / 874 548oui
5 modexpeip_example1 (256 bits)11 170 118non
5 modexpnagydani-5-pow0x10001 (1 024 octets)55 076 950non
6 bn256 addchfast1978 262oui, de justesse
7 bn256 mulchfast13 060 669non
8 bn256 pairing1 paire / 2 paires1 853 656 212 / 2 019 000 469non
9 blake2f12 tours14 493oui, 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.

appelinterpré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)
programmemotshc
evm.bin, l’interpréteur18 0097e1aea2b…854c08
ERC-20, étape une15 8049cdb79e7…2fd048c8
ERC-20, étape deux11 686a0feae92a7311c9562495717100eb7aea71270a38ed435d06dc31387e0ea8ff6

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.

versionv0.8.37 depuis github.com/ethereum/solidity/releases
ressource macOSsolc-macos, sha256 a27396e7732aa52e80ff89ad7bd8a2e46fec2a6dcc4ef20cd16e5e0c502d6821
ressource Linuxsolc-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 émet MCOPY, 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
  • --out doit se trouver dans une copie du dépôt des circuits. Sans lui, evm2rv se 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 pas CHAINID ; --chain-id ne change donc pas son hc. Un contrat qui lit CHAINID exige l’option ; réglez-la sur la chaîne où vous déploierez (--chain-id 19 pour 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 sortieidentiquesidentiques
cycles, transfer66 235121 638
palier1818
mots de programme11 68618 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 programmef074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d
image11 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’appelpalier 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ée2 700 octets
tx d’appel (chain 13)279e1f62abf3f177ca71b08c633e051c37bc4720118c79ddb00936f036c4bfa7
ancrée à / validée àhauteur 802 / hauteur 919
frais0,00357 RAND
reçu h_pubnull
sorties du reçuégales aux huit mots de l’interpréteur pour le même vecteur
durée totale, RSS maximale622,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 CALL de 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_v6 de 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) ; approve se prouve au palier 16, transfer et transferFrom au 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-core place une feuille de stockage de contrat selon les 32 bits de poids fort de keccak256(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.bin compte 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. Le docs/confidential.md du fullnode consigne la condition préalable à côté du plafond d’appel (audit v6, ZKV-6 ; sur main depuis 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 et evm.bin de 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 transfer de 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éteur evm.bin s’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.

Dernière vérification par rapport au commit c0878c3f de fullnode (chaîne 21) le 2026-10-12, et circuits `7ef3220` (`evm2rv/README.md`, `guests-compiled/evm/contracts/SOLC.md`, `docs/superpowers/specs/2026-09-18-evm-to-rv32-translator-design.md`); the walkthrough re-run on 2026-09-19 with solc 0.8.37. Lorsque le code et cette page divergent, c'est le code qui a raison ; signalez-le-nous.