evm2rv prende il bytecode runtime EVM, cioè l’output di solc --bin-runtime, e lo trasforma in un programma Rand. Scrive codice C, e rand-guest build compila il C in un’immagine RV32IM. L’immagine accetta lo stesso vettore di input del guest interprete EVM di Rand, evm.bin, e pubblica le stesse otto parole di output. Esegue il contratto in modo nativo invece di interpretarlo opcode per opcode. Questa pagina mostra l’intero percorso su un ERC-20.
Che cos’è
| input | bytecode runtime EVM, come testo esadecimale (.hex) o byte grezzi |
| output | un crate C di shim; rand-guest build ne ricava un contenitore di immagine |
| dove viene eseguito | off chain, sulla macchina dello sviluppatore; la chain vede solo l’immagine |
| cosa deve coincidere | lo status di evm.bin, le otto parole di output e gas_used |
| repository | evm2rv/ nel repository dei circuits |
| toolchain fissata | Rust 1.98.1, clang 23.1.1, il crate cc 1.4.6 |
Il traduttore accetta il codice runtime, non il codice di creazione. Una prova copre una sola chiamata a un contratto già distribuito, quindi il costruttore non viene mai eseguito. Lo stato che il costruttore scriverebbe fa invece parte del pre-stato, sotto forma di witness di storage.
Le fasi di traduzione sono due. Entrambe producono gli stessi risultati e lo stesso gas; ciascuna è un programma a sé, con il proprio hc.
| fase | flag | cosa fa |
|---|---|---|
| uno | --stage 1 | ogni opcode su uno stack in memoria |
| due | --stage 2, il valore predefinito | le parole di stack di ciascun blocco in variabili locali C, costanti risolte in fase di traduzione |
Il vettore di input è quello dell’interprete: il codice, il calldata, il chiamante e le altre parole d’ambiente, il limite di gas, la radice del pre-stato e un witness di storage per ogni slot toccato dalla chiamata. Gli output sono out[0], lo status (1 successo, 0 revert, 2 arresto eccezionale), e out[1..8], il digest EVM_OUT calcolato sull’hash del codice, entrambe le radici di stato, i dati restituiti e i log.
Modello di fiducia
Un verifier controlla un contratto tradotto in due passaggi.
hc == translate(bytecode). Rieseguievm2rvsul bytecode pubblicato con gli stessi--stagee--chain-id, ricompila con la toolchain fissata e confrontahc. Il crate generato fissaccesattamente e include il proprioCargo.lock.- Il code guard lega
hcal bytecode. La logica tradotta è compilata nell’immagine, maCODECOPYeCODESIZEleggono il codice presente nel vettore di input. Senza un controllo, uno stessohcpotrebbe girare con byte di codice scelti dal chiamante. Perciòcontract.ccontiene un digest Poseidon2 del bytecode sorgente (la sua lunghezza e i suoi byte), e lo shim calcola lo stesso hash sul codice del vettore di input prima che venga eseguito qualsiasi codice tradotto. Codice diverso viene rifiutato: status 2,gas_used0, arrestoOutOfBounds.
Il digest EVM_OUT lega anche il keccak256 del codice, come fa quello dell’interprete. La chain non registra il bytecode sorgente né il suo hash. Perché altri possano verificare hc, pubblica il bytecode, oppure il sorgente Solidity e l’esatta build di solc.
Il guard costa 1.008 cicli sul transfer. Un codice che differisce di un solo byte dalla sorgente viene rifiutato in 30.418 cicli alla fase due (tier 16).
Copertura e cosa genera un trap
Opcode. L’insieme Shanghai. Compila con --evm-version shanghai: i target più recenti emettono MCOPY e altri opcode di Cancun, che generano un trap.
Opcode d’ambiente:
| opcode | traduzione |
|---|---|
ADDRESS, CALLER, CALLVALUE, CALLDATASIZE, CODESIZE | dal vettore di input, come li legge l’interprete |
CHAINID | il valore di --chain-id, fissato in fase di traduzione, così hc lo lega; obbligatorio quando il codice contiene CHAINID |
ORIGIN | uguale a CALLER (una sola chiamata, nessun relayer) |
GASPRICE, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, SELFBALANCE, BASEFEE, BLOCKHASH | trap (status 2), come nell’interprete |
I nove opcode di contesto del blocco generano un trap finché non esiste un progetto per legarli a un blocco. Un input privato non può trasportare un fatto del blocco, perché un verifier non può aprire H_IN. Finché questa decisione non è presa, un contratto che legge l’ora o il numero di blocco (per esempio la scadenza di un permit) genera un trap.
Chiamate. CALL, CALLCODE, DELEGATECALL e STATICCALL eseguono un precompile quando il destinatario è l’indirizzo da 1 a 9. Qualsiasi altro destinatario genera un trap. Un valore diverso da zero in CALL o CALLCODE genera un trap: non esiste un modello di saldo.
Precompile. Tutti e nove girano in software. Alcuni sono troppo lenti da provare oggi, perché una prova contiene al massimo 2²⁰ righe:
| precompile | vettore | cicli | entra in 2²⁰ |
|---|---|---|---|
| 1 ecrecover | ValidKey | 14.510.525 | no |
| 2 sha256 | ”abc” / 200 byte | 2.791 / 5.289 | sì |
| 3 ripemd160 | ”abc” | 12.195 | sì |
| 4 identity | 100 byte | 4.498 | sì |
| 5 modexp | nagydani-1-square / -1-pow0x10001 | 122.332 / 874.548 | sì |
| 5 modexp | eip_example1 (256 bit) | 11.170.118 | no |
| 5 modexp | nagydani-5-pow0x10001 (1.024 byte) | 55.076.950 | no |
| 6 bn256 add | chfast1 | 978.262 | sì, per poco |
| 7 bn256 mul | chfast1 | 3.060.669 | no |
| 8 bn256 pairing | 1 coppia / 2 coppie | 1.853.656.212 / 2.019.000.469 | no |
| 9 blake2f | 12 round | 14.493 | sì, fino a circa 1.800 round |
La base e il modulo di modexp sono limitati a 1.024 byte ciascuno.
Divergenze accettate. Status, le otto parole e gas_used devono coincidere. Sono accettate tre differenze: il tipo di arresto può differire (il gas statico viene addebitato all’inizio di ogni blocco, quindi OutOfGas e un altro arresto possono scambiarsi; entrambi sono status 2 con tutto il gas consumato); la traduzione implementa CHAINID, ORIGIN e le chiamate ai precompile, che l’interprete tratta come trap; e il code guard rifiuta il codice diverso dalla sua sorgente.
I numeri
Chiamate ERC-20, con il code guard incluso. Cicli da rand-guest run; tier tra parentesi.
| chiamata | interprete (evm.bin) | fase uno | fase due (predefinita) |
|---|---|---|---|
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) |
| programma | parole | hc |
|---|---|---|
evm.bin, l’interprete | 18.009 | 7e1aea2b…854c08 |
| ERC-20, fase uno | 15.804 | 9cdb79e7…2fd048c8 |
| ERC-20, fase due | 11.686 | a0feae92a7311c9562495717100eb7aea71270a38ed435d06dc31387e0ea8ff6 |
La fase due esegue le tre chiamate rispettivamente in 54,5%, 56,2% e 55,0% dei cicli dell’interprete. Il transfer resta al tier 18: i suoi 66.235 cicli più 3.155 righe di digest fanno 69.390 righe, 3.855 oltre le 65.535 del tier 16.
Circa il 60% di un transfer alla fase due (circa 39.928 cicli) è l’harness che entrambe le immagini condividono: decodifica dell’input, verifica dei witness di storage, le chiamate keccak256 dell’ABI e il digest di output. Nessun lavoro di traduzione scende sotto questa soglia minima.
Procedura passo passo: un ERC-20, da Solidity a output coincidenti
Ogni comando parte dalla radice di un checkout dei circuits a 7ef3220, con la toolchain da Deploy a Rust program e clang 23.1.1 da Deploy a RISC-V program. L’output lungo è troncato dove indicato con ….
0. Compilare gli strumenti
(cd evm2rv && cargo +1.98.1 build --release)
(cd rand-guest && cargo +1.98.1 build --release)
1. Compilare il Solidity in bytecode runtime
Il contratto è il file committato guests-compiled/evm/contracts/ERC20.sol: un ERC-20 autonomo con il layout di storage di OpenZeppelin v4, pragma solidity 0.8.37. Usa esattamente solc 0.8.37; un’altra versione ottimizza in modo diverso e produce bytecode diverso.
| release | v0.8.37 da github.com/ethereum/solidity/releases |
| asset macOS | solc-macos, sha256 a27396e7732aa52e80ff89ad7bd8a2e46fec2a6dcc4ef20cd16e5e0c502d6821 |
| asset Linux | solc-static-linux, dalla stessa release |
$ 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
L’ultima riga è il bytecode runtime, di 1.296 byte. È identico byte per byte al file committato guests-compiled/evm/contracts/erc20.runtime.hex; SOLC=$PWD/solc bash guests-compiled/evm/contracts/build.sh lo verifica e stampa contracts: erc20.runtime.hex reproduces.
I flag contano:
--bin-runtime: il codice distribuito, non il codice di creazione.--evm-version shanghai: il target predefinito di 0.8.37 emetteMCOPY, che genera un trap.--optimize --optimize-runs 200: un profilo per contratti distribuiti.
2. Tradurre
$ 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
--outdeve trovarsi dentro un checkout dei circuits. Senza,evm2rvsi limita ad analizzare il codice.- Qualsiasi opcode che genera un trap verrebbe elencato qui come avviso, con il suo pc.
- Questa sessione è stata acquisita con
--chain-id 12. Questo ERC-20 non contieneCHAINID, quindi--chain-idnon cambia il suohc. Un contratto che leggeCHAINIDrichiede il flag; impostalo sulla chain su cui farai il deploy (--chain-id 19per la chain live).
contract.c termina con il digest del code guard:
const uint32_t *evm_code_digest(void) {
static const uint32_t d[8] = {0xf679280fu, 0x86c10e83u, 0x7e8f1f39u, 0xa2377de3u, 0x8a2e282fu, 0xc54cad42u, 0xddfaf009u, 0x7d28a8afu};
return d;
}
3. Costruire l’immagine
Il limite predefinito è di 4.096 parole, quindi passa --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
Questo è l’hc della fase due pubblicato. Il nome del crate non lo influenza.
4. Costruire un vettore di input ed eseguire un transfer
L’esempio erc20_vector stampa i vettori del test di parità come parole di input: transfer (ALICE invia a BOB 250 dei suoi 1.000), approve (ALICE consente a BOB di spendere 5) e transferFrom (BOB sposta 100 dei 1.000 di ALICE a fronte di un’allowance di 300). Per il tuo contratto, costruisci un EvmCall allo stesso modo in Rust; l’esempio è il modello.
$ (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. Confrontare con l’interprete
Lo stesso input sul guest interprete:
$ 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
E l’interprete eseguito nativamente sull’host, l’oracolo dei test:
$ (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
Il controllo in 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
Ripeti i passi 4 e 5 con approve e transferFrom; entrambi danno parole identiche. cd evm2rv && cargo +1.98.1 test --release esegue questi e altri cinque vettori in entrambi gli stadi, e verifica anche gas_used (70 test, più 2 prove ignorate).
| tradotto (secondo stadio) | interprete (evm.bin) | |
|---|---|---|
| otto parole in uscita | identiche | identiche |
| cicli, transfer | 66.235 | 121.638 |
| tier | 18 | 18 |
| parole del programma | 11.686 | 18.009 |
6. Deploy
rand program deploy evm2rv/target/mytoken/image.bin
Il wallet stampa l’id del programma, il numero di parole e hc, poi verifica il limite della chain prima di generare qualsiasi prova. L’immagine è di 11.686 parole, quindi serve una chain il cui genesis imposti max_program_words ad almeno 11.686. La chain 12 usava il valore predefinito di 4.096, perciò lì il deploy è stato rifiutato. La chain 13, la chain della v0.4, ha impostato max_program_words a 65.535, e ogni chain successiva mantiene quel valore. La chain 20, quella attiva (genesis 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135), mantiene inoltre i limiti di blocco, di busta della chiamata e di input pubblici della chain 13 e dimezza il limite della prova a 4 MiB (4.194.304 byte, dalla chain 17; le chain da 13 a 16 impostavano 8 MiB). Il limite sulle parole non è più l’unico ostacolo. Il genesis della chain 20 imposta hardening_v6 e, con esso, un deploy di più parole di programma di quante una chiamata ne possa contenere al limite di chiamata del tier 14 non è valido (ProgramUncallable): al massimo 8.180 parole senza input pubblico. Questa immagine è di 11.686 parole, quindi la chain 20 rifiuta il deploy e rifiuterebbe anche le chiamate: ogni chiamata ERC-20 viene provata al tier 16 o 18, sopra il limite di chiamata del tier 14 (passo 7). L’aggregazione dei blocchi è disattivata sulla chain 20 (il suo genesis non ha una sezione aggregation); i suoi nodi di bootstrap sono elencati in Eseguire un validatore. La commissione di deploy è 0.001 + 0.0001 × 11,686 = 1,1696 RAND, la commissione pagata dal deploy sulla chain 13 qui sotto.
Questo deploy è stato eseguito sulla chain 13 il 2026-09-19, quando la chain 13 era la chain attiva (profilo FRI di produzione, genesis 8123ccac1883a45750e4df6964fb7cd3f0b321798cde4c0ef406a0293939ece3), dal wallet di genesis shielded-1. Ogni chain successiva (dalla 14 alla 19) è stata avviata come chain nuova senza i programmi della chain 13, e l’immagine non è stata ridistribuita su nessuna di esse; la transazione e l’altezza qui sotto sono quindi quelle della chain 13 e non si risolvono più nell’explorer. L’id del programma è un hash del solo base_pc e delle parole, quindi la stessa immagine ottiene lo stesso id su qualsiasi chain che ammetta il deploy. L’output del wallet, con le righe di avanzamento omesse (…):
$ 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 programma | f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d |
| immagine | 11.686 parole; hc a0feae92… nella grafia di rand-guest, 92aefea0… in quella del wallet |
| bundle delle commissioni (bundle 2-in-2-out della chain 13) | tier 14, 1.419.655 byte, provato in 92,2 s |
| tx di deploy (chain 13) | c7a66dc4d9cdb7f9bfc4bde01f618c356e5abad7097e6bf7dc04e2e49c53acd0 |
| confermato all’altezza (chain 13) | 381 |
Dalla chain 14 il bundle che paga è l’unico bundle di asset nascosti 4-in-4-out che ogni transazione porta con sé, provato rispetto al binding della transazione. È ancora di tier 14: circa 100 s e circa 1,42 MB al profilo di produzione sulle chain 14 e 15. Dalla chain 17 un bundle porta anche una prova di autorizzazione di tier 10 (autorizzazione suddivisa), quindi sulle chain 18 e 19 il bundle delle commissioni di una transazione è di circa 1,49 MB di prova del bundle e circa 1,37 MB di prova di autorizzazione.
7. Call
Una chiamata transfer porta il vettore di 921 parole come input privati, entro il limite di input di qualsiasi chain (4.295 parole per impostazione predefinita, 16.071 sulla chain 20), e nessuna parola pubblica. Non può essere inviata alla chain 20: il programma non può esservi distribuito (passo 6), il transfer viene provato al tier 18 e, dalla fullnode v0.5.6 (2025-09-25), un nodo rifiuta con CallTierTooHigh qualsiasi prova di chiamata sopra il tier 14, prima di costruire una chiave del verifier. Il comando qui sotto prova in locale e la chain rifiuta il risultato; le misurazioni sono prove off-chain. rand call accetta ogni parola come --input ripetuto:
rand call f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d \
$(for w in $(cat evm2rv/target/transfer.words); do printf -- '--input %s ' "$w"; done)
Ora misurato. Una prova di tier 18 è stata interrotta su un laptop da 48 GB a 24,7 GB, ma su un droplet da 128 GB (m-16vcpu-128gb) la chiamata transfer tradotta è stata provata: 85,0 GB di RSS di picco, 3.230,5 s (53,8 min), una prova di 811.600 byte. È stata verificata in 101,4 s sul droplet e in 50,2 s con 3,66 GB di RSS su un laptop Apple-silicon da 48 GB (memoria di proving). Con la tabella piatta dei tier del ledger, la regola sulle chain senza una sezione gas (fino alla chain 17), la commissione di chiamata al tier 18 era di 0,0024 RAND. Le chain 18 e 19 prezzano invece una chiamata in base al suo limite di gas dichiarato e ai suoi byte, 100 unità per gas e 800 per KiB ai prezzi di genesis, e non ammettono alcuna chiamata sopra il tier 14.
La chiamata sulla chain 13, 2026-09-19
La chiamata approve(BOB, 5) (649 parole private, tier 16) è stata eseguita sulla chain 13 il 2026-09-19, quando era la chain attiva, profilo FRI di produzione, dallo stesso wallet. Nessuna chiamata è stata effettuata su alcuna chain successiva, dove il programma non è distribuito, e la chain 20 rifiuta questa prova di chiamata di tier 16 (CallTierTooHigh). L’output del wallet, con le righe di avanzamento omesse (…):
$ 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
…
| prova della chiamata | tier 16, 3.412.405 byte, provata in 388,7 s |
| bundle delle commissioni (bundle 2-in-2-out della chain 13) | tier 14, 1.420.423 byte, provato in 96,0 s |
| busta sigillata | 2.700 byte |
| tx di chiamata (chain 13) | 279e1f62abf3f177ca71b08c633e051c37bc4720118c79ddb00936f036c4bfa7 |
| ancorata a / confermata a | altezza 802 / altezza 919 |
| commissione | 0,00357 RAND |
ricevuta h_pub | null |
| output della ricevuta | uguali alle otto parole dell’interprete per lo stesso vettore |
| tempo totale, RSS di picco | 622,8 s, 22,9 GB, su un laptop M-series da 48 GB |
- La prova di produzione è di 3.412.405 byte, oltre il limite di 2 MiB per le prove della chain 12. La chain 12 non poteva accettare questa chiamata; il limite di 8 MiB della chain 13 sì. Le chain dalla 14 alla 16 hanno mantenuto lo stesso limite, e le chain dalla 17 alla 20 hanno impostato 4 MiB (4.194.304 byte), entro cui una prova di questa dimensione rientra ancora. Dalla v0.5.6 un nodo rifiuta la chiamata per il suo tier prima di guardarne la dimensione.
- La commissione include il termine per i byte della tabella della chain 13: la prova da 3,41 MB supera la franchigia di 2 MiB + 18.432 byte. Dalla chain 18 ogni chiamata paga per KiB dal byte 0.
Le otto parole della ricevuta sono quelle del passo 4. La chain non conserva alcuno stato EVM: il chiamante fornisce la radice dello stato precedente e i witness dello storage, e la radice dello stato successivo è all’interno del digest EVM_OUT.
Limiti
- Nessun contesto di blocco. I nove opcode di contesto di blocco generano un trap finché non esiste un progetto di vincolo al blocco.
- Nessun trasferimento di valore. Una
CALLcon valore diverso da zero genera un trap. Anche le chiamate a contratti diversi dai precompilati generano un trap. - ecrecover, bn256 mul, il pairing e modexp di grandi dimensioni oggi non possono essere provati. Ciascuno supera il limite di 2²⁰ righe del tier; richiedono tabelle di coprocessore.
- Nessuno stato EVM on chain. Una chiamata è una prova su una sola esecuzione; lo stato lo porta il chiamante.
- Né il deploy né alcuna chiamata ERC-20 sono ammissibili sulla chain 20. Con
hardening_v6della chain 20, un deploy di più parole di quante ne possa contenere una chiamata di tier 14 non è valido (ProgramUncallable; al massimo 8.180 parole), e l’immagine ne conta 11.686. Dal fullnode v0.5.6 la prova di una chiamata deve essere di tier 14 o inferiore (CallTierTooHigh);approvesi prova al tier 16,transferetransferFromal tier 18. Lo stesso controllo limita le tabelle di hash che una chiamata può dichiarare: 2¹² righe Keccak (128 permutazioni) e 2¹³ righe SHA-256 (128 compressioni). Le chiamate si provano comunque off chain, come di seguito. - L’indice dello storage ha una profondità di 32 bit.
evm-corecolloca una foglia di storage del contratto in base ai 32 bit più significativi dikeccak256(slot), quindi trovare uno slot in collisione con uno scelto costa circa 2³² hash Keccak e, finché questo contiene un valore diverso da zero, lo slot scelto resta congelato (nessuna falsificazione e nessun valore perso). La condizione è dormiente:evm.binconta 18.009 parole, e una chiamata al limite del tier 14 ne contiene al massimo 8.184, quindi nessun runtime EVM è chiamabile su alcuna chain. Il filedocs/confidential.mddel fullnode registra la precondizione accanto al limite delle chiamate (audit v6, ZKV-6; sumaindal 2026-09-30, dopo il tag v0.6.7): prima che una chain tolga il limite di chiamata del tier 14 o ammetta un runtime EVM abbastanza piccolo da essere chiamato, l’indice dello storage deve essere approfondito eevm.binriancorato. - Provato. Una prova di trasferimento di fase uno (tier 18) è stata terminata con SIGKILL su un laptop da 48 GB a 24,7 GB dopo 1.016 s, ma la chiamata
transferdi fase due è stata provata su un droplet da 128 GB (m-16vcpu-128gb): 85,0 GB di RSS di picco, 3.230,5 s (53,8 min), una prova di 811.600 byte, verificata in 101,4 s (droplet) e in 50,2 s con 3,66 GB di RSS (laptop da 48 GB). La prova di trasferimento conevm.bindell’interprete ha dato un risultato molto simile: 85,5 GB, 3.143,3 s (52,4 min), una prova di 805.108 byte — e le due prove portano parole di output pubblico identiche, a conferma della parità sul prover reale.approve(tier 16, tradotto) costa molto meno: 21,7 GB, 786,7 s (13,1 min), una prova di 798.930 byte. La traduzione abbassa il costo di proving solo quando sposta una chiamata in un tier inferiore. - La chain non registra il bytecode. Pubblicalo, così che altri possano ricostruire
hc.