Rand Protocol
Documentación
Guías

Programas

El modelo de programas confidenciales (un guest RISC-V demostrado en el zkVM, desplegado una sola vez y llamado con entradas privadas), con los comandos build, deploy, call, invoke y receipt, el estado de programa RPL-2 en chain 20 y una lista honesta de lo que todavía no es posible.

Use esta página para poner un programa en la cadena y llamarlo: qué es un programa, cómo construir uno, qué publica una llamada y qué oculta, cómo leer un recibo y los límites con los que se encontrará. Todo programa es código público; toda llamada es una prueba. Example programs contiene aplicaciones completas, y randprotocol/example-programs contiene el código fuente de los programas desplegados en chain 20, incluidos los RPL-2 (más abajo).

Qué es un programa

Un programa es código máquina RV32IM para el zkVM de Rand: { base_pc: u32, words: Vec<u32> }, con un máximo igual al límite de programa de la cadena (65 535 palabras en chain 20, como en todas las cadenas desde chain 13; 4096 en un génesis sin max_program_words, como chain 12), con base_pc múltiplo de 4 y con cada palabra decodificable como instrucción (una palabra que no se decodifica se rechaza en el despliegue, nunca en tiempo de ejecución). Chain 20, igual que chain 19, también se niega a desplegar un programa que ninguna llamada podría contener: más de 8180 palabras sin segmento público, o menos con uno (más abajo). Se direcciona por contenido y es inmutable:

program_id = blake3("rand-program" || base_pc || words)       // the id, with no public input
program_id = blake3("rand-program-2", base_pc || u32_le(len(words)) || words || u32_le(len(public)) || public)   // deployed with --public
code_hash  = hc = Poseidon2 digest of (base_pc, len, words)    // what calls are verified against

Una llamada es una prueba STARK de que el programa con digest hc se ejecutó sobre ciertas entradas privadas, no gastó más que un límite de gas declarado y publicó ocho palabras de salida. El nodo verifica la prueba, registra un recibo y no ocurre nada más: las salidas son datos, no efectos. Una llamada no puede mover RAND; quien llama mueve el valor en el bundle que paga la llamada, y las salidas de un programa significan lo que el programa y quien lo llama acuerden que significan. En chain 20 un Invoke es la excepción: su prueba avala una transición de estado declarada, que el libro mayor aplica a continuación (Program state (RPL-2)).

Lo que puede hacer el guest:

grupoinstrucciones
superiores / saltosLUI AUIPC JAL JALR
ramasBEQ BNE BLT BGE BLTU BGEU
cargas / almacenamientosLW SW LB LH LBU LHU SB SH
ALUADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND
RV32MMUL MULH MULHU MULHSU DIV DIVU REM REMU
sistemaECALL; nunca FENCE, CSR, EBREAK ni las extensiones A o C

Llamadas al sistema, por número en a7:

#syscallefecto
0HALTtermina la ejecución
1WRITE_OUTPUT slot wordfija la salida pública slot < 8; cada slot como máximo una vez, los slots sin escribir valen cero
2READ_INPUT idxdevuelve la palabra de entrada privada idx, vinculada al compromiso con sal H_IN
3POSEIDON2 ptr naplica el hash de n ≤ 4096 palabras in situ con la esponja Poseidon2
4KECCAK ptruna permutación Keccak-f[1600] in situ; hace que la prueba incluya la tabla keccak
5SHA256 ptruna compresión SHA-256 in situ; hace que la prueba incluya la tabla sha256
6READ_PUBLIC idxdevuelve la palabra idx del segmento público; en chain 20 el segmento público se fija en el despliegue (rand program deploy --public); en un génesis sin max_program_public_words está vacío y no lee nada
7POSEIDON2_LEN ptr nPOSEIDON2 con la longitud del mensaje incorporada a la esponja, de modo que [a] y [a, 0] producen hashes distintos (conjunto de restricciones 7, chain 16 y posteriores). rand-guest check en circuits aeacf31 todavía lo rechaza (sección siguiente)

Memoria: 32 registros con x0 igual a cero; una RAM que empieza en cero; toda dirección por debajo de 2³⁰; una pila de 64 KiB para los guests compilados. Un programa construido con rand-guest lleva sus datos de solo lectura e inicializados en el contenedor de imagen, y un prólogo de carga que forma parte del programa escribe esos datos antes de que se ejecute el código propio del programa (The RISC-V ISA). Los compromisos de nota, los nullifiers y las comprobaciones de Merkle no son syscalls, sino rutinas de biblioteca sobre POSEIDON2.

Desde chain 18 (conjunto de restricciones 8) cada ejecución se mide en gas. Una instrucción ordinaria y las syscalls 0, 1, 2 y 6 cuestan 1 de gas cada una, POSEIDON2 sobre n palabras cuesta 3 + 3 × ⌈n / 4⌉, una permutación KECCAK cuesta 192 y una compresión SHA256 cuesta 64. Example programs contiene la tabla y lo que declara una llamada.

Cómo construir uno

Tres vías, todas producen palabras RV32IM:

El ensamblador integrado. El wallet incluye cinco guests ensamblados a mano y los ensambla en un archivo JSON { "base_pc", "words" }:

rand program build --guest private_payment --arg 1000 --out pp.json   # prints the program id
rand program build --guest balance_check --arg 1000 --out bc.json
# guests: fib, memcpy, bubble_sort, balance_check, private_payment, public_echo, rpl2_counter (v0.6.8); --arg is repeatable

Programas compilados, con rand-guest. El rand-guest del repositorio circuits compila un crate de Rust no_std (riscv32im-unknown-none-elf, Rust 1.98.1) o un directorio de C (clang 23.1.1) en un contenedor de imagen, comprueba cada palabra contra el decodificador de la máquina e imprime hc y el id del programa. La compilación es hermética, de modo que el mismo código fuente produce el mismo hc en cualquier máquina. Para chain 18 a 20, compile la cadena de herramientas en el commit aeacf31 de circuits (rama feat/v067-circuits), el commit al que fijan tanto fullnode v0.6.7 como v0.6.8. En ese commit rand-guest check solo lista las syscalls 0 a 6, de modo que un guest que llama a guest_sdk::poseidon2_len (syscall 7) se rechaza en la compilación aunque la máquina lo ejecute (zkp-circuits issue #1). Paso a paso: Deploy a Rust program y Deploy a RISC-V program.

Programas traducidos. sbpf2rv traduce un ELF sBPF de Solana, y evm2rv traduce bytecode de runtime de EVM, a programas cuyas salidas son idénticas, palabra por palabra, a las de los guests intérpretes de Rand: The Solana transpiler y The Ethereum transpiler.

El propio wallet no tiene compilador. rand program deploy acepta un .json, un .bin en bruto de palabras little-endian o el contenedor de imagen que escribe rand-guest (fullnode v0.3 y posteriores).

Despliegue

rand program deploy pp.json          # a bundle pays the floor; waits for the commit; prints the id
rand program show <id>               # id, base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest

La testnet es chain 20, y para desplegar, llamar o invocar en ella se necesita el wallet rand de fullnode v0.6.8 o posterior. Un wallet más antiguo no puede enviar allí en absoluto: el génesis de chain 20 establece binding_domain: 1, de modo que cada vinculación de prueba y cada mensaje firmado incluye el hash del génesis, mientras que las vinculaciones de un wallet v0.6.7 llevan solo el id de la cadena. El endpoint público https://rpc.randprotocol.org atiende la sincronización, los despliegues y las llamadas de un wallet (configuración).

Primero el wallet imprime el id del programa, el número de palabras y hc, y pide al nodo la comisión con rand_estimateFee, que rechaza un programa que supere el límite de la cadena antes de cualquier prueba. Después construye un bundle de autotransferencia de valor cero (el bundle de activos ocultos de 4 entradas y 4 salidas que lleva toda transacción; la comisión sale de sus ranuras de RAND) que paga 0,001 RAND + 100 000 unidades × palabras con sus propias notas (las palabras del segmento público cuentan como palabras de código), adjunta Action::Deploy { base_pc, words, public }, prueba el bundle contra la vinculación de la transacción y lo envía. Desde chain 17 son dos pruebas, y el wallet imprime una línea por cada una: una autorización de gasto de tier 10 (segundos, unos 1,37 MB) y el bundle de tier 14 (unos dos minutos en un portátil, unos 1,5 MB). Antes de chain 17 era solo la prueba del bundle, unos 100 s y unos 1,42 MB en chain 15. La comisión de despliegue no tiene término de gas ni de bytes: el gas solo fija el precio de las llamadas. El nodo comprueba que cada palabra se decodifica, calcula hc, almacena el registro y empieza a construir la clave del verificador en segundo plano. El código es público; quién lo pagó no lo es: rand_getProgram no tiene campo deployer. Desplegar las mismas palabras dos veces es idempotente.

→ {"method":"rand_getProgram","params":["675adeea…0065"]}
← { "id": "675adeea…0065", "base_pc": 0, "words_len": 42, "code_hash": "…", "deployed_at": 10,
    "public_words_len": 0, "public_digest": null }
→ {"method":"rand_getProgramCode","params":["675adeea…0065"]}
← { "base_pc": 0, "words": [ … ] }

Llamada

rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>

El wallet obtiene del nodo el código y el segmento público y comprueba que su hash coincide con el id. Ejecuta la llamada en el emulador e imprime el gas y la comisión antes de probar nada:

gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)

A continuación sella la transcripción de entrada (más abajo), prueba la llamada localmente con el perfil FRI de la cadena y con las entradas como vector privado, prueba el bundle que paga la comisión, envía, espera e imprime el recibo. En chain 20 la prueba de la llamada queda vinculada a su propia transacción (una vinculación que incluye el hash del génesis) y el bundle son dos pruebas, de modo que una llamada son tres: la llamada, la autorización de gasto de tier 10 y el bundle de tier 14. Las dos líneas anteriores corresponden a la llamada de la votación anónima en chain 18 el 2026-09-30 (An anonymous vote).

La tarifa. En la chain 20 el mínimo es BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉, donde BUNDLE_BASE es 0,001 RAND, los bytes son la prueba de la llamada más el sobre de entradas, y los precios partieron en 100 y 800 unidades (una unidad es 10⁻⁹ RAND) y se mueven con la ocupación de los bloques. La chain 20 los limita a 10.000 y 80.000 unidades, y solo los bytes que pagan el precio por byte (las pruebas y los sobres de las llamadas) cuentan para su carga (byte_load: "paying"); en las chains 18 y 19 los precios no tenían techo y los bytes de todas las transacciones movían el precio por byte. El esquema plano del ledger, 0.001 RAND + call_fee(tier), que era la regla en la chain 15, no se aplica allí. La wallet fija la tarifa antes de que exista la prueba, así que valora la prueba en el tope de 4 MiB de la chain y añade dos escalones de precio de margen. Cuatro llamadas medidas en la chain 18 costaron entre 0,0057 y 0,0075 RAND cada una (la aritmética). rand fee call <tier> --gas <limit> --bytes <B> pide al nodo el mínimo mismo. private_payment lee cuatro saldos privados y, si suman al menos su umbral, publica el excedente: salidas [1, 0, 25, 0, 0, 0, 0, 0]. La chain ve la prueba, el tier y esas ocho palabras; nunca ve los saldos.

Opciones: --tier T fuerza un tier; --gas-limit <N|max> declara un límite de gas exacto o el máximo del tier en lugar del valor por defecto, con el gas exacto redondeado hacia arriba a un múltiplo de 2^(tier − 2); --fee <RAND> fija la tarifa; --expect-public <FILE> se niega a continuar antes de generar la prueba salvo que el segmento público del programa sean las palabras de ese archivo; --auditor <rand1…> cifra también la transcripción para un auditor designado; --print-call-key imprime la clave por llamada; --no-envelope no publica ninguna transcripción; --cuda selecciona el backend de GPU (sin probar en hardware e incompatible con cifrar un sobre). La chain 20 rechaza --cuda en una llamada sin más: una prueba de llamada ligada a su transacción se genera en el backend de CPU.

Recibos

Se guarda un recibo por cada llamada confirmada, y se recalcula cuando un nodo sincroniza o verifica su chain:

→ {"method":"rand_getReceipt","params":["b082…09b0"]}
← { "tx": "b082…09b0", "program": "675adeea…0065", "tier": 14,
    "outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 105, "index": 0, "h_in": "9c0e…7f", "h_pub": null }

h_in es el compromiso con sal de la prueba sobre las entradas privadas; por sí solo no revela nada. h_pub es el digest del segmento público del programa fijado al desplegarlo, el public_digest del programa, o null si el programa se desplegó sin uno. No existe un campo effect, y el límite de gas declarado no figura en el recibo. El explorador muestra el mismo objeto como receipt en GET /transactions/:hash para una call, lista las llamadas de un programa en GET /programs/:id y filtra GET /transactions?kind=call&program=<id>.

Sobres de entrada: revelar una llamada más tarde

Por defecto, rand call publica la transcripción (salt, inputs) de la llamada cifrada en un sobre de entradas de llamada, de modo que las entradas puedan mostrarse después sin una segunda prueba:

partbyteswhat it is
body12 + 16 + 4·n + 16salt ‖ inputs bajo una clave nueva por llamada K_call
to_sender60K_call envuelta con la clave de visualización saliente del emisor
kem_ct1,088 or 0una encapsulación ML-KEM-768 dirigida al auditor, vacía si no hay auditor
to_auditor60 or 0K_call envuelta bajo el secreto de esa encapsulación

Los datos asociados del cuerpo son H_IN, de modo que un sobre trasladado a otra llamada no se abre para nadie. La chain solo comprueba el tamaño (como máximo max_call_envelope_bytes: 65.536 bytes en la chain 20, 18.432 en un génesis sin el campo) y lo sirve tal cual con rand_getCallEnvelope(tx). Tres claves lo abren y ninguna más: la clave de visualización del emisor (todas las llamadas que hizo esa wallet), la clave por llamada (esa llamada) y la clave de visualización del auditor (esa llamada).

rand open-call <txhash>                    # as the caller
rand open-call <txhash> --as-auditor       # as the auditor
rand open-call <txhash> --call-key <hex>   # with the per-call key alone

open-call abre la transcripción, comprueba que input_digest(salt, inputs) == H_IN, vuelve a ejecutar el programa con las entradas recuperadas mediante el emulador, compara las salidas con las del recibo, y termina con código distinto de cero si falla cualquiera de las dos comprobaciones, de modo que un script no pueda confundir una advertencia con un sí. --no-envelope es irreversible: una vez perdida la sal, nadie, ni siquiera el emisor, puede abrir esa llamada.

Desde la v0.6.8, la nueva ejecución se hace sobre el segmento público sobre el que se generó la prueba: las palabras públicas del programa fijadas al desplegarlo y, con hardening_v6, tras ellas el vínculo de llamada de ocho palabras (incidencia #116 de fullnode). En la v0.6.7, open-call solo funcionaba con un programa desplegado sin segmento público; con uno, imprimía las entradas y luego se detenía con re-running the program on these inputs failed: PublicIndex(0), terminando con código distinto de cero sin ejecutar ninguna de las dos comprobaciones.

Estado del programa (RPL-2)

La chain 20 es la primera chain con RPL-2, activado por la sección program_state de su génesis. Un programa obtiene tres cosas que antes no tenía (fullnode docs/program-state.md, crates/randprotocol-core/src/ledger/program_state.rs en la v0.6.8):

celdasun mapa de una clave de 8 palabras a un valor de 8 palabras, por programa. Una celda ausente se lee como ocho ceros; escribir ocho ceros la elimina. Comprometidas en la raíz de estado
una bóvedaun saldo público por activo (0 es RAND). El valor entra mediante el bundle de un invoke y sale como notas que calcula la chain
un token propioun token registrado con rand token create --program <id> (autoridad de acuñación Program(id), sin suministro inicial) solo lo acuñan y queman los invokes de ese programa

Las celdas y los saldos de la bóveda son públicos, como el suministro de un token. Nada en ellos es una nota.

Invoke (Action 33) es una llamada que además declara una transición, y su prueba de llamada demuestra que el programa acepta exactamente esa transición:

Invoke     { program, proof, input_envelope, transition }
Transition { reads:  [{ key, value }]     // cells read, with the values read; keys strictly ascending
             writes: [{ key, value }]     // cells written; keys strictly ascending
             inflow: none | deposit | burn
             pays:   [{ asset, amount, recipient, r, envelope }]   // notes out of the vault
             mints:  [{ asset, amount, recipient, r, envelope }] } // new units of the program's own token

Lo que entra es lo que quema el bundle: burn_r es RAND depositado en la bóveda; burn_a de burn_asset es un token depositado (flujo de entrada deposit) o destruido (flujo de entrada burn, solo el token propio del programa). Lo que sale es una nota calculada por la chain por cada pago, primero los pagos y luego las acuñaciones, con el importe y la pk del destinatario públicos, como en una acuñación. El ledger aplica la transición solo si el programa existe, la forma es válida, cada celda de reads sigue conteniendo el valor declarado, la bóveda (tras el depósito de esta misma transacción) cubre cada pago, cada acuñación es del token propio del programa y las pruebas se verifican. Una lectura que ya no coincide se rechaza como StaleRead; la wallet vuelve a leer y a generar la prueba.

En qué se diferencia un invoke de una llamada.

Call (RPL-1)Invoke (RPL-2)
segmento público sobre el que se genera la pruebapublic ‖ call_bindingpublic ‖ call_binding ‖ context, la transición como palabras
qué hace la chain si tiene éxitoguarda un recibo; las salidas son datosguarda el recibo y aplica la transición: escribe celdas, mueve la bóveda, añade notas de pago
valorsolo la transferencia propia del bundledepósitos en la bóveda mediante las quemas del bundle; pagos y acuñaciones desde ella
depende del estadonola prueba no (el segmento se construye a partir de la transacción), pero sí la comprobación de lecturas, en la admisión y en el pool
tarifael mínimo de la llamadael mínimo de la llamada más cell_fee por celda creada

Un programa lee la palabra de contexto i con READ_PUBLIC(public_len + 8 + i). La palabra 0 es la versión (1), las palabras 1 a 4 son los recuentos de lecturas, escrituras, pagos y acuñaciones, luego burn_r, el tipo de flujo de entrada, burn_asset y burn_a, después cada lectura y escritura como clave y valor (8 palabras cada una), y por último cada pago como activo e importe. Los destinatarios no están en el contexto: el programa decide los importes, el vínculo fija a quién se paga. Un programa debe restringir cada palabra de contexto que se le muestra y no debe tener ninguna ruta que entre en pánico, porque el panic handler de guest-sdk detiene la ejecución y una ejecución detenida es una ejecución demostrable.

Límites de la chain 20 (rand_getLimits.program_state):

limitchain 20
cell_fee10.000.000 unidades, 0,01 RAND, por celda creada (una celda escrita con valor distinto de cero donde no había ninguna); reescribir o borrar una celda es gratis
max_reads8 celdas
max_writes8 celdas
max_payouts4 notas, pagos y acuñaciones en conjunto

La regla del segmento suele ser más estricta: el segmento completo debe caber en la tabla pública sobre la que se genera la prueba de una llamada al mismo programa, 127 palabras para un programa sin entrada pública. Eso deja 119 palabras de contexto, por ejemplo tres lecturas, tres escrituras y cuatro pagos.

Comandos y RPC (wallet v0.6.8):

rand program build --guest rpl2_counter --out counter.json   # one cell, incremented by one per invoke
rand program invoke <id> --transition step.json [--input N | --inputs-file F.json]
rand program state <id> [--cell <64 hex>]                   # rand_getProgramCell / rand_getProgramCells
rand program vault <id>                                      # rand_getProgramVault

program invoke comprueba cada entrada de reads contra la celda activa antes de generar la prueba y termina con código 3 ante una lectura obsoleta, antes de generar la prueba o en la inclusión, de modo que un script vuelve a leer y reintenta. rand_estimateFee con "kind": "invoke" cotiza la tarifa. El formato del archivo de transición está en docs/cli.md de fullnode. Los programas RPL-2 completos desplegados en la chain 20 (amm, stableswap, orderbook, stablecoin, lending, perp, crowdfund, stoploss) y los RPL-1 (sealed-auction, ballot, eligibility, private-join, credit-score) están en randprotocol/example-programs.

Qué es público y qué permanece oculto

públicaoculto
el id del programa y su código; el nivel; el límite de gas declarado; las ocho salidas; H_IN; la comisión y los campos públicos del bundle; el tamaño del envelope; si la llamada usó Keccak o SHA-256, y aproximadamente con qué frecuencia (la cabecera de la prueba declara la altura de cada tabla de hash)las entradas privadas; registros, memoria, ramas; el recuento real de ciclos y el gas exacto; la sal de H_IN; quién la llamó y con qué pagó

Dos pruebas de la misma ejecución son bytes distintos. Eso no bastaba en la chain 15 y anteriores: una llamada con 31 palabras privadas o menos probaba una tabla demasiado pequeña para ocultar, y sus entradas podían recuperarse a partir de la prueba. Desde v0.6, el prover fija un mínimo de 128 filas en esas tablas, y el conjunto de restricciones 7 (chain 16 y posteriores) oculta con un blindaje los totales por tabla que aún se filtraban. hc es vinculante pero no ocultante: cualquiera que pueda enumerar programas candidatos puede confirmar cuál fue desplegado.

Lo que aún no es posible

Tal como lo expone el fullnode:

  • Sin llamadas entre programas y sin estado fuera de RPL-2. Un Call es una prueba sobre una ejecución; nada pasa de una llamada a otra salvo lo que el llamante vuelve a aportar. En la chain 20, un Invoke lee y escribe las celdas y la bóveda del propio programa (arriba); ninguna chain anterior a la 20 tiene estado de programa, y un invoke no puede tocar las celdas de otro programa.
  • Sin transferencias impulsadas por el programa fuera de una bóveda. El efecto de tipo 1, la transferencia pagada por el programa a una cuenta, se eliminó junto con las cuentas, y las salidas de un Call son declaraciones. En la chain 20, un Invoke paga desde la bóveda del propio programa, como máximo cuatro notes, con importes y destinatarios públicos.
  • Sin compilador en la wallet. Los programas son los guests ensamblados incorporados, o imágenes compiladas con rand-guest en el repositorio de circuits.
  • La entrada pública se fija en el despliegue, no en cada llamada. El conjunto de restricciones 6 añadió READ_PUBLIC y H_PUB. En la chain 20, un despliegue puede incluir hasta 32 768 palabras públicas, el id del programa las vincula y la prueba de cada llamada se verifica contra su digest. No hay palabras públicas por llamada.
  • Las syscalls de hash generan pruebas grandes. Una prueba que incluye keccak ocupa unos 3,2 MB, por encima del límite predeterminado de 2 MiB para pruebas; una prueba que incluye sha256 añade unos 400 KB. El límite de la chain 20 es de 4 194 304 bytes (4 MiB, como en las chains 17 a 19; era de 8 MiB en las chains 13 a 16), que admite ambas: en la chain 18, una llamada de nivel 12 con la tabla de Keccak midió 3,35 MB, y una llamada de nivel 14 con la tabla de SHA-256, 1,91 MB (Example programs). Además, una llamada está limitada a 128 permutaciones Keccak-f y 128 compresiones SHA-256 (más abajo), y cada KiB de prueba se cobra al precio por byte.
  • Los programas EVM y Solana no se despliegan en la chain 20, y sus llamadas superan el límite de nivel de llamada. Los guests intérpretes (evm.bin, 18 009 palabras; sbpf.bin, 8 317) y las imágenes traducidas (el ERC-20, 11 686 palabras; SPL Token, 65 096) superan el límite predeterminado de 4096 palabras y quedan por debajo de las 65 535 que fijó la chain 13 y que toda chain posterior ha mantenido. Ambas imágenes traducidas se desplegaron en la chain 13 el 2026-09-19, SPL Token con su ELF como entrada pública. Las chains 14 y 15 se crearon desde cero, y en ninguna de las dos se volvió a desplegar ninguna de las imágenes. La chain 20, como la 19, se niega a desplegar cualquiera de las cuatro: según su regla hardening_v6, un programa que ninguna llamada de nivel 14 podría contener, es decir, más de 8180 palabras sin segmento público y menos con uno, se rechaza en el despliegue (ProgramUncallable), para que no se pague una comisión de despliegue por un programa que nadie puede llamar. Una llamada ERC-20 se prueba en el nivel 16 o 18: el approve de nivel 16 se llamó en la chain 13 el 2026-09-19 con una prueba de producción de 3 412 405 bytes (Ethereum transpiler), y la chain 20 rechaza cualquier llamada por encima del nivel 14 (siguiente punto), de modo que hoy no es admisible ninguna llamada ERC-20 en ella. Las llamadas a SPL Token son de nivel 20, que requiere unos 330 GB; no existe ninguna prueba de llamada a SPL (proving memory). Los opcodes de contexto de bloque de EVM provocan un trap, y no hay estado EVM en la chain.
  • Una llamada se prueba en el nivel 14 o inferior. Desde fullnode v0.5.6 (2026-09-25), se rechaza una prueba de llamada por encima del nivel 14 (CallTierTooHigh), y también la que tenga más de 128 permutaciones Keccak-f o 128 compresiones SHA-256. El approve ERC-20 de nivel 16 de la chain 13 sería rechazado en la chain 20, como lo habría sido en las chains 15, 18 y 19, y lo mismo ocurriría con toda llamada ERC-20 de nivel 18 o SPL Token de nivel 20; esas siguen probándose fuera de la chain (the budget).
  • La prueba con GPU nunca se ha ejecutado en hardware. --cuda requiere una compilación con --features cuda y PTX confirmado; ninguno de los dos existe en la flota. En la chain 20, una prueba de llamada se genera en el backend de CPU sea cual sea la compilación.
  • hc no es ocultante, como se indicó arriba, y una solicitud de testigo sigue revelando al nodo qué hoja gasta una wallet.

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