Rand Protocol
Documentación
Programas RISC-V

El transpilador de Ethereum

evm2rv traduce el bytecode de ejecución de la EVM al ISA RISC-V de Rand: qué es, el modelo de confianza y su protección del código, los opcodes y precompilados que cubre y dónde produce una trampa, las cifras medidas, y un recorrido completo de un ERC-20 desde Solidity, pasando por solc --bin-runtime, hasta resultados que coinciden con el intérprete de la EVM palabra por palabra.

evm2rv toma el bytecode de ejecución (runtime) de EVM, la salida de solc --bin-runtime, y lo convierte en un programa de Rand. Escribe C, y rand-guest build compila ese C en una imagen RV32IM. La imagen recibe el mismo vector de entrada que el guest intérprete de EVM de Rand, evm.bin, y publica las mismas ocho palabras de salida. Ejecuta el contrato de forma nativa en lugar de interpretarlo opcode por opcode. Esta página muestra el recorrido completo sobre un ERC-20.

Qué es

entradabytecode de ejecución de EVM, como texto hexadecimal (.hex) o bytes sin procesar
salidaun crate shim en C; rand-guest build lo convierte en un contenedor de imagen
dónde se ejecutafuera de la cadena, en la máquina del desarrollador; la cadena solo ve la imagen
qué debe coincidirel estado, las ocho palabras de salida y gas_used de evm.bin
repositorioevm2rv/ en el repositorio de circuitos
toolchain fijadoRust 1.98.1, clang 23.1.1, el crate cc 1.4.6

El traductor toma código de ejecución, no código de creación. Una prueba cubre una llamada a un contrato ya desplegado, así que el constructor nunca se ejecuta. El estado que escribiría el constructor forma parte, en cambio, del estado previo, como testigos de almacenamiento.

Hay dos etapas de traducción. Ambas dan los mismos resultados y el mismo gas; cada una es su propio programa, con su propio hc.

etapaopciónqué hace
uno--stage 1cada opcode sobre una pila en memoria
dos--stage 2, la predeterminadalas palabras de pila de cada bloque en variables locales de C, con las constantes plegadas en la traducción

El vector de entrada es el del intérprete: el código, el calldata, el llamador y otras palabras del entorno, el límite de gas, la raíz del estado previo y un testigo de almacenamiento por cada slot que toca la llamada. Las salidas son out[0], el estado (1 éxito, 0 revert, 2 detención excepcional), y out[1..8], el digest EVM_OUT sobre el hash del código, ambas raíces de estado, los datos devueltos y los logs.

Modelo de confianza

Un verificador comprueba un contrato traducido en dos pasos.

  1. hc == translate(bytecode). Vuelve a ejecutar evm2rv sobre el bytecode publicado con el mismo --stage y --chain-id, recompila con el toolchain fijado y compara hc. El crate generado fija cc de forma exacta y lleva su propio Cargo.lock.
  2. La guarda de código vincula hc con el bytecode. La lógica traducida se compila dentro de la imagen, pero CODECOPY y CODESIZE leen el código del vector de entrada. Sin una comprobación, un mismo hc podría ejecutarse con bytes de código elegidos por el llamador. Por eso contract.c lleva un digest Poseidon2 del bytecode fuente (su longitud y sus bytes), y el shim calcula el hash del código del vector de entrada de la misma forma antes de que se ejecute código traducido alguno. Cualquier otro código se rechaza: estado 2, gas_used 0, detención OutOfBounds.

El digest EVM_OUT también vincula el keccak256 del código, igual que el del intérprete. La cadena no registra el bytecode fuente ni su hash. Para que otros puedan comprobar hc, publica el bytecode, o el código fuente en Solidity y la compilación exacta de solc.

La guarda cuesta 1,008 ciclos en la transferencia. Un código que difiere en un solo byte de la fuente se rechaza en 30,418 ciclos en la etapa dos (tier 16).

Cobertura y qué aborta

Opcodes. El conjunto Shanghai. Compila con --evm-version shanghai: los destinos más recientes emiten MCOPY y otros opcodes de Cancun, que abortan.

Opcodes de entorno:

opcodetraducción
ADDRESS, CALLER, CALLVALUE, CALLDATASIZE, CODESIZEdel vector de entrada, tal como los lee el intérprete
CHAINIDel valor de --chain-id, incorporado en la traducción, de modo que hc lo vincula; obligatorio cuando el código contiene CHAINID
ORIGINigual a CALLER (una llamada, sin relayer)
GASPRICE, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, SELFBALANCE, BASEFEE, BLOCKHASHabortan (estado 2), como en el intérprete

Los nueve opcodes de contexto de bloque abortan hasta que exista un diseño para vincularlos a un bloque. Una entrada privada no puede transportar un hecho de bloque, porque un verificador no puede abrir H_IN. Mientras no se tome esa decisión, un contrato que lee la hora o el número de bloque (por ejemplo, el plazo de un permit) aborta.

Llamadas. CALL, CALLCODE, DELEGATECALL y STATICCALL ejecutan un precompilado cuando el destino es la dirección 1 a 9. Cualquier otro destino aborta. Un valor distinto de cero en CALL o CALLCODE aborta: no hay un modelo de saldos.

Precompilados. Los nueve se ejecutan en software. Algunos son hoy demasiado lentos para probarse, porque una prueba admite como máximo 2²⁰ filas:

precompiladovectorcicloscabe en 2²⁰
1 ecrecoverValidKey14,510,525no
2 sha256”abc” / 200 bytes2,791 / 5,289sí
3 ripemd160”abc”12,195sí
4 identity100 bytes4,498sí
5 modexpnagydani-1-square / -1-pow0x10001122,332 / 874,548sí
5 modexpeip_example1 (256 bits)11,170,118no
5 modexpnagydani-5-pow0x10001 (1,024 bytes)55,076,950no
6 bn256 addchfast1978,262sí, por poco
7 bn256 mulchfast13,060,669no
8 bn256 pairing1 par / 2 pares1,853,656,212 / 2,019,000,469no
9 blake2f12 rondas14,493sí, hasta unas 1,800 rondas

La base y el módulo de modexp están limitados a 1,024 bytes cada uno.

Divergencias aceptadas. El estado, las ocho palabras y gas_used deben coincidir. Se aceptan tres diferencias: el tipo de detención puede variar (el gas estático se cobra en la cabecera de cada bloque, por lo que OutOfGas y otra detención pueden intercambiarse; ambas son estado 2 con todo el gas consumido); la traducción implementa CHAINID, ORIGIN y las llamadas a precompilados, que el intérprete aborta; y la guarda de código rechaza cualquier código distinto de su fuente.

Las cifras

Llamadas ERC-20, con la guarda de código incluida. Ciclos de rand-guest run; tier entre paréntesis.

llamadaintérprete (evm.bin)etapa unoetapa dos (predeterminada)
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)
programapalabrashc
evm.bin, el intérprete18,0097e1aea2b…854c08
ERC-20, etapa uno15,8049cdb79e7…2fd048c8
ERC-20, etapa dos11,686a0feae92a7311c9562495717100eb7aea71270a38ed435d06dc31387e0ea8ff6

La etapa dos ejecuta las tres llamadas en el 54.5 %, el 56.2 % y el 55.0 % de los ciclos del intérprete. La transferencia permanece en el tier 18: sus 66,235 ciclos más 3,155 filas de digest son 69,390 filas, 3,855 por encima de las 65,535 del tier 16.

Cerca del 60 % de una transferencia en la etapa dos (unos 39,928 ciclos) es el arnés que comparten ambas imágenes: decodificar la entrada, verificar los testigos de almacenamiento, las llamadas a keccak256 del ABI y el digest de salida. Ningún trabajo de traducción baja de ese piso.

Recorrido: un ERC-20, de Solidity a salidas coincidentes

Cada comando se ejecuta desde la raíz de una copia del repositorio de circuitos en 7ef3220, con el toolchain de Deploy a Rust program y clang 23.1.1 de Deploy a RISC-V program. La salida larga se recorta donde aparece ….

0. Compilar las herramientas

(cd evm2rv && cargo +1.98.1 build --release)
(cd rand-guest && cargo +1.98.1 build --release)

1. Compilar el Solidity a bytecode de ejecución

El contrato es el guests-compiled/evm/contracts/ERC20.sol incluido en el repositorio: un ERC-20 autocontenido con el diseño de almacenamiento de OpenZeppelin v4, pragma solidity 0.8.37. Usa exactamente solc 0.8.37; otra versión optimiza de forma distinta y produce un bytecode diferente.

versiónv0.8.37 de github.com/ethereum/solidity/releases
recurso para macOSsolc-macos, sha256 a27396e7732aa52e80ff89ad7bd8a2e46fec2a6dcc4ef20cd16e5e0c502d6821
recurso para Linuxsolc-static-linux, de la misma versión
$ 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 última línea es el bytecode de ejecución, de 1,296 bytes. Es idéntico byte a byte al guests-compiled/evm/contracts/erc20.runtime.hex incluido en el repositorio; SOLC=$PWD/solc bash guests-compiled/evm/contracts/build.sh lo comprueba e imprime contracts: erc20.runtime.hex reproduces.

Las opciones importan:

  • --bin-runtime: el código desplegado, no el código de creación.
  • --evm-version shanghai: el destino predeterminado de 0.8.37 emite MCOPY, que aborta.
  • --optimize --optimize-runs 200: un perfil para contratos desplegados.

2. Traducir

$ 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 debe estar dentro de una copia del repositorio de circuitos. Sin él, evm2rv solo analiza el código.
  • Cualquier opcode que aborte aparecería aquí como una advertencia, con su pc.
  • Esta sesión se capturó con --chain-id 12. Este ERC-20 no tiene CHAINID, así que --chain-id no cambia su hc. Un contrato que lee CHAINID exige la opción; fíjala en la cadena en la que vayas a desplegar (--chain-id 19 para la cadena en producción).

contract.c termina con el digest de la guarda de código:

const uint32_t *evm_code_digest(void) {
    static const uint32_t d[8] = {0xf679280fu, 0x86c10e83u, 0x7e8f1f39u, 0xa2377de3u, 0x8a2e282fu, 0xc54cad42u, 0xddfaf009u, 0x7d28a8afu};
    return d;
}

3. Compilar la imagen

El límite predeterminado es de 4,096 palabras, así que pasa --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

Este es el hc publicado de la etapa dos. El nombre del crate no lo afecta.

4. Construir un vector de entrada y ejecutar una transferencia

El ejemplo erc20_vector imprime los vectores de la prueba de paridad como palabras de entrada: transfer (ALICE envía a BOB 250 de sus 1,000), approve (ALICE permite que BOB gaste 5) y transferFrom (BOB mueve 100 de los 1,000 de ALICE contra una autorización de 300). Para tu propio contrato, construye un EvmCall de la misma manera en Rust; el ejemplo sirve de plantilla.

$ (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. Comparar con el intérprete

La misma entrada en el guest intérprete:

$ 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

Y el intérprete ejecutado de forma nativa en el host, el oráculo de las pruebas:

$ (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 verificación en un solo comando:

$ 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

Repita los pasos 4 y 5 con approve y transferFrom; ambos dan palabras idénticas. cd evm2rv && cargo +1.98.1 test --release ejecuta estos y otros cinco vectores en ambas etapas, y también comprueba gas_used (70 pruebas, más 2 pruebas ignoradas).

traducido (etapa dos)intérprete (evm.bin)
ocho palabras de salidaidénticasidénticas
ciclos, transfer66,235121,638
nivel1818
palabras del programa11,68618,009

6. Despliegue

rand program deploy evm2rv/target/mytoken/image.bin

La wallet imprime el id del programa, el número de palabras y hc, y luego comprueba el límite de la cadena antes de generar cualquier prueba. La imagen tiene 11,686 palabras, por lo que necesita una cadena cuyo génesis fije max_program_words en al menos 11,686. La cadena 12 ejecutaba el valor por defecto, 4,096, así que allí el despliegue fue rechazado. La cadena 13, la cadena de la v0.4, fijó max_program_words en 65,535, y todas las cadenas posteriores mantienen ese valor. La cadena 20, la cadena activa (génesis 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135), mantiene además los límites de bloque, de sobre de llamada y de entrada pública de la cadena 13, y reduce a la mitad el límite de prueba, hasta 4 MiB (4,194,304 bytes, desde la cadena 17; las cadenas 13 a 16 fijaron 8 MiB). El límite de palabras ya no es la única barrera. El génesis de la cadena 20 fija hardening_v6, y bajo esa opción un despliegue con más palabras de programa de las que cabe en una llamada con el límite de llamada del nivel 14 es inválido (ProgramUncallable): como máximo 8,180 palabras sin entrada pública. Esta imagen tiene 11,686 palabras, así que la cadena 20 rechaza el despliegue, y rechazaría también las llamadas: toda llamada ERC-20 se prueba en el nivel 16 o 18, por encima del límite de llamada del nivel 14 (paso 7). La agregación de bloques está desactivada en la cadena 20 (su génesis no tiene sección aggregation); sus nodos de arranque se enumeran en Running a validator. La comisión de despliegue es 0.001 + 0.0001 × 11,686 = 1.1696 RAND, la comisión que pagó el despliegue en la cadena 13 que se muestra a continuación.

Este despliegue se ejecutó en la cadena 13 el 2026-09-19, mientras la cadena 13 era la cadena activa (perfil FRI de producción, génesis 8123ccac1883a45750e4df6964fb7cd3f0b321798cde4c0ef406a0293939ece3), desde la wallet génesis shielded-1. Todas las cadenas posteriores (14 a 19) se crearon como cadenas nuevas sin los programas de la cadena 13, y la imagen no se ha vuelto a desplegar en ninguna de ellas, por lo que la transacción y la altura que figuran abajo son las de la cadena 13 y ya no se resuelven en el explorador. El id del programa es un hash de base_pc y de las palabras únicamente, de modo que la misma imagen obtiene el mismo id en cualquier cadena que admita el despliegue. La salida de la wallet, con sus líneas de progreso omitidas (…):

$ rand program deploy <image.bin>
program id: f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d (11686 words, hc 92aefea0951c31a717574962aeb70e10a37012a7d035d48e8713c36df68feae0)
…
proved in 92.2s: tier 14, 1419655 bytes
submitted deploy c7a66dc4d9cdb7f9bfc4bde01f618c356e5abad7097e6bf7dc04e2e49c53acd0
…
id del programaf074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d
imagen11,686 palabras; hc a0feae92… en la notación de rand-guest, 92aefea0… en la de la wallet
paquete de comisión (paquete 2-in-2-out de la cadena 13)nivel 14, 1,419,655 bytes, probado en 92.2 s
tx de despliegue (cadena 13)c7a66dc4d9cdb7f9bfc4bde01f618c356e5abad7097e6bf7dc04e2e49c53acd0
confirmada en la altura (cadena 13)381

Desde la cadena 14, el paquete de pago es el único paquete 4-in-4-out de activos ocultos que lleva toda transacción, probado contra el binding de la transacción. Sigue siendo de nivel 14: unos 100 s y unos 1.42 MB con el perfil de producción en las cadenas 14 y 15. Desde la cadena 17, un paquete lleva además una prueba de autorización de nivel 10 (autorización dividida), de modo que en las cadenas 18 y 19 el paquete de comisión de una transacción son unos 1.49 MB de prueba de paquete y unos 1.37 MB de prueba de autorización.

7. Llamada

Una llamada de transferencia lleva el vector de 921 palabras como entradas privadas, dentro del límite de entrada de cualquier cadena (4,295 palabras por defecto, 16,071 en la cadena 20), y ninguna palabra pública. No se puede enviar a la cadena 20: el programa no se puede desplegar allí (paso 6), la transferencia se prueba en el nivel 18, y desde fullnode v0.5.6 (2026-09-25) un nodo rechaza cualquier prueba de llamada por encima del nivel 14 con CallTierTooHigh, antes de construir una clave del verificador. El comando siguiente genera la prueba en local y la cadena rechaza el resultado; las mediciones son pruebas fuera de la cadena. rand call toma cada palabra como un --input repetido:

rand call f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d \
    $(for w in $(cat evm2rv/target/transfer.words); do printf -- '--input %s ' "$w"; done)

Ahora medido. Una prueba de nivel 18 fue terminada en un portátil de 48 GB al llegar a 24.7 GB, pero en un droplet de 128 GB (m-16vcpu-128gb) la llamada transfer traducida sí se probó: 85.0 GB de RSS máximo, 3,230.5 s (53.8 min), una prueba de 811,600 bytes. Se verificó en 101.4 s en el droplet y en 50.2 s con 3.66 GB de RSS en un portátil Apple-silicon de 48 GB (memoria de pruebas). Bajo el calendario plano de niveles del ledger, la regla en las cadenas sin sección gas (hasta la cadena 17), la comisión de llamada en el nivel 18 era de 0.0024 RAND. Las cadenas 18 y 19 fijan en cambio el precio de una llamada según su límite de gas declarado y sus bytes, 100 unidades por gas y 800 por KiB con los precios del génesis, y no admiten ninguna llamada por encima del nivel 14.

La llamada en la cadena 13, 2026-09-19

La llamada approve(BOB, 5) (649 palabras privadas, nivel 16) se ejecutó en la cadena 13 el 2026-09-19, mientras era la cadena activa, con el perfil FRI de producción, desde la misma wallet. No se ha hecho ninguna llamada en ninguna cadena posterior, donde el programa no está desplegado, y la cadena 20 rechaza esta prueba de llamada de nivel 16 (CallTierTooHigh). La salida de la wallet, con sus líneas de progreso omitidas (…):

$ 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
…
prueba de la llamadanivel 16, 3,412,405 bytes, probada en 388.7 s
paquete de comisión (paquete 2-in-2-out de la cadena 13)nivel 14, 1,420,423 bytes, probado en 96.0 s
sobre sellado2,700 bytes
tx de la llamada (cadena 13)279e1f62abf3f177ca71b08c633e051c37bc4720118c79ddb00936f036c4bfa7
anclada en / confirmada enaltura 802 / altura 919
comisión0.00357 RAND
recibo h_pubnull
salidas del reciboiguales a las ocho palabras del intérprete para el mismo vector
tiempo total, RSS máximo622.8 s, 22.9 GB, en un portátil M-series de 48 GB
  • La prueba de producción tiene 3,412,405 bytes, por encima del límite de prueba de 2 MiB de la cadena 12. La cadena 12 no podía aceptar esta llamada; el límite de 8 MiB de la cadena 13 sí. Las cadenas 14 a 16 mantuvieron el mismo límite, y las cadenas 17 a 20 fijaron 4 MiB (4,194,304 bytes), al que una prueba de este tamaño todavía se ajusta. Desde v0.5.6, un nodo rechaza la llamada por su nivel antes de mirar su tamaño.
  • La comisión incluye el término por bytes del calendario de la cadena 13: la prueba de 3.41 MB supera la franquicia gratuita de 2 MiB + 18,432 bytes. Desde la cadena 18, toda llamada paga por KiB desde el byte 0.

Las ocho palabras del recibo son las del paso 4. La cadena no guarda ningún estado de EVM: el llamante aporta la raíz del estado previo y los testigos de almacenamiento, y la raíz del estado posterior está dentro del resumen EVM_OUT.

Límites

  • Sin contexto de bloque. Los nueve opcodes de contexto de bloque provocan un trap hasta que exista un diseño de vinculación de bloques.
  • Sin transferencias de valor. Un CALL con valor distinto de cero provoca un trap. Las llamadas a contratos que no sean precompilados provocan un trap.
  • Hoy no se pueden probar ecrecover, bn256 mul, el pairing ni modexp de gran tamaño. Cada uno supera el límite de 2²⁰ filas del tier; necesitan tablas de coprocesador.
  • Sin estado de EVM en la cadena. Una llamada es una prueba sobre una ejecución; el llamador porta el estado.
  • Ni el despliegue ni ninguna llamada ERC-20 son admisibles en la chain 20. Bajo el hardening_v6 de la chain 20, un despliegue de más palabras de las que cabe en una llamada de tier 14 no es válido (ProgramUncallable; 8.180 palabras como máximo), y la imagen tiene 11.686. Desde fullnode v0.5.6, una prueba de llamada debe ser de tier 14 o inferior (CallTierTooHigh); approve se prueba en el tier 16, y transfer y transferFrom en el tier 18. La misma comprobación limita las tablas de hash que una llamada puede declarar: 2¹² filas de Keccak (128 permutaciones) y 2¹³ filas de SHA-256 (128 compresiones). Las llamadas aún se prueban fuera de la cadena, como se indica a continuación.
  • El índice de almacenamiento tiene 32 bits de profundidad. evm-core ubica una hoja de almacenamiento del contrato según los 32 bits superiores de keccak256(slot), de modo que encontrar un slot que colisione con uno elegido cuesta unos 2³² hashes de Keccak, y mientras este contenga un valor distinto de cero, el slot elegido queda congelado (sin falsificación y sin pérdida de valor). Esto está latente: evm.bin tiene 18.009 palabras, y una llamada en el límite del tier 14 admite como máximo 8.184, por lo que ningún runtime de EVM es invocable en ninguna chain. El docs/confidential.md del fullnode registra la precondición junto al límite de llamadas (auditoría v6, ZKV-6; en main desde el 2026-09-30, después de la etiqueta v0.6.7): antes de que una chain levante el límite de llamadas del tier 14 o admita un runtime de EVM lo bastante pequeño para ser invocado, el índice de almacenamiento debe profundizarse y evm.bin volver a fijarse.
  • Probado. Una prueba de transferencia de la primera etapa (tier 18) fue terminada con SIGKILL en un portátil de 48 GB a los 24,7 GB tras 1.016 s, pero la llamada transfer de la segunda etapa se probó en un droplet de 128 GB (m-16vcpu-128gb): 85,0 GB de RSS máximo, 3.230,5 s (53,8 min), una prueba de 811.600 bytes, verificada en 101,4 s (droplet) y en 50,2 s con 3,66 GB de RSS (portátil de 48 GB). La prueba de transferencia con el evm.bin del intérprete coincidió de cerca: 85,5 GB, 3.143,3 s (52,4 min), una prueba de 805.108 bytes, y las dos pruebas llevan palabras de salida públicas idénticas, lo que confirma la paridad en el prover real. approve (tier 16, traducido) es mucho más barato: 21,7 GB, 786,7 s (13,1 min), una prueba de 798.930 bytes. La traducción solo reduce el costo de prueba cuando coloca una llamada en un tier más pequeño.
  • La cadena no registra el bytecode. Publíquelo para que otros puedan reconstruir hc.

Verificado por última vez con el commit c0878c3f del fullnode (cadena 21) el 2026-10-12, y 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. Cuando el código y esta página no coinciden, el código tiene razón; avísenos.