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
| entrada | bytecode de ejecución de EVM, como texto hexadecimal (.hex) o bytes sin procesar |
| salida | un crate shim en C; rand-guest build lo convierte en un contenedor de imagen |
| dónde se ejecuta | fuera de la cadena, en la máquina del desarrollador; la cadena solo ve la imagen |
| qué debe coincidir | el estado, las ocho palabras de salida y gas_used de evm.bin |
| repositorio | evm2rv/ en el repositorio de circuitos |
| toolchain fijado | Rust 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.
| etapa | opción | qué hace |
|---|---|---|
| uno | --stage 1 | cada opcode sobre una pila en memoria |
| dos | --stage 2, la predeterminada | las 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.
hc == translate(bytecode). Vuelve a ejecutarevm2rvsobre el bytecode publicado con el mismo--stagey--chain-id, recompila con el toolchain fijado y comparahc. El crate generado fijaccde forma exacta y lleva su propioCargo.lock.- La guarda de código vincula
hccon el bytecode. La lógica traducida se compila dentro de la imagen, peroCODECOPYyCODESIZEleen el código del vector de entrada. Sin una comprobación, un mismohcpodría ejecutarse con bytes de código elegidos por el llamador. Por esocontract.clleva 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_used0, detenciónOutOfBounds.
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:
| opcode | traducción |
|---|---|
ADDRESS, CALLER, CALLVALUE, CALLDATASIZE, CODESIZE | del vector de entrada, tal como los lee el intérprete |
CHAINID | el valor de --chain-id, incorporado en la traducción, de modo que hc lo vincula; obligatorio cuando el código contiene CHAINID |
ORIGIN | igual a CALLER (una llamada, sin relayer) |
GASPRICE, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, SELFBALANCE, BASEFEE, BLOCKHASH | abortan (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:
| precompilado | vector | ciclos | cabe en 2²⁰ |
|---|---|---|---|
| 1 ecrecover | ValidKey | 14,510,525 | no |
| 2 sha256 | ”abc” / 200 bytes | 2,791 / 5,289 | sí |
| 3 ripemd160 | ”abc” | 12,195 | sí |
| 4 identity | 100 bytes | 4,498 | sí |
| 5 modexp | nagydani-1-square / -1-pow0x10001 | 122,332 / 874,548 | sí |
| 5 modexp | eip_example1 (256 bits) | 11,170,118 | no |
| 5 modexp | nagydani-5-pow0x10001 (1,024 bytes) | 55,076,950 | no |
| 6 bn256 add | chfast1 | 978,262 | sí, por poco |
| 7 bn256 mul | chfast1 | 3,060,669 | no |
| 8 bn256 pairing | 1 par / 2 pares | 1,853,656,212 / 2,019,000,469 | no |
| 9 blake2f | 12 rondas | 14,493 | sí, 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.
| llamada | intérprete (evm.bin) | etapa uno | etapa 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) |
| programa | palabras | hc |
|---|---|---|
evm.bin, el intérprete | 18,009 | 7e1aea2b…854c08 |
| ERC-20, etapa uno | 15,804 | 9cdb79e7…2fd048c8 |
| ERC-20, etapa dos | 11,686 | a0feae92a7311c9562495717100eb7aea71270a38ed435d06dc31387e0ea8ff6 |
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ón | v0.8.37 de github.com/ethereum/solidity/releases |
| recurso para macOS | solc-macos, sha256 a27396e7732aa52e80ff89ad7bd8a2e46fec2a6dcc4ef20cd16e5e0c502d6821 |
| recurso para Linux | solc-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 emiteMCOPY, 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
--outdebe estar dentro de una copia del repositorio de circuitos. Sin él,evm2rvsolo 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 tieneCHAINID, así que--chain-idno cambia suhc. Un contrato que leeCHAINIDexige la opción; fíjala en la cadena en la que vayas a desplegar (--chain-id 19para 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 salida | idénticas | idénticas |
| ciclos, transfer | 66,235 | 121,638 |
| nivel | 18 | 18 |
| palabras del programa | 11,686 | 18,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 programa | f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d |
| imagen | 11,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 llamada | nivel 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 sellado | 2,700 bytes |
| tx de la llamada (cadena 13) | 279e1f62abf3f177ca71b08c633e051c37bc4720118c79ddb00936f036c4bfa7 |
| anclada en / confirmada en | altura 802 / altura 919 |
| comisión | 0.00357 RAND |
recibo h_pub | null |
| salidas del recibo | iguales a las ocho palabras del intérprete para el mismo vector |
| tiempo total, RSS máximo | 622.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
CALLcon 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_v6de 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);approvese prueba en el tier 16, ytransferytransferFromen 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-coreubica una hoja de almacenamiento del contrato según los 32 bits superiores dekeccak256(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.bintiene 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. Eldocs/confidential.mddel fullnode registra la precondición junto al límite de llamadas (auditoría v6, ZKV-6; enmaindesde 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 yevm.binvolver 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
transferde 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 elevm.bindel 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.